You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

537 lines
14 KiB

2 days ago
  1. <template>
  2. <div class="erf-attachment-manager">
  3. <!-- 操作按钮 -->
  4. <div style="margin-bottom: 10px">
  5. <el-button
  6. type="primary"
  7. size="small"
  8. v-if="!disabled"
  9. @click="handleUpload">
  10. 上传附件
  11. </el-button>
  12. <el-button
  13. type="primary"
  14. size="small"
  15. @click="handleDownload">
  16. 下载
  17. </el-button>
  18. </div>
  19. <!-- 附件列表表格 -->
  20. <el-table
  21. :height="height"
  22. :data="dataList"
  23. ref="table"
  24. v-loading="queryLoading"
  25. border
  26. @selection-change="handleSelectionChange"
  27. style="width: 100%;">
  28. <el-table-column type="selection" width="55" align="center"/>
  29. <el-table-column
  30. prop="fileName"
  31. label="文件名"
  32. min-width="200"
  33. align="left"
  34. header-align="center"
  35. show-overflow-tooltip>
  36. </el-table-column>
  37. <el-table-column
  38. prop="fileType"
  39. label="文件类型"
  40. min-width="100"
  41. align="center"
  42. header-align="center">
  43. </el-table-column>
  44. <el-table-column
  45. prop="createdBy"
  46. label="上传人"
  47. min-width="100"
  48. align="center"
  49. header-align="center">
  50. </el-table-column>
  51. <el-table-column
  52. prop="createDate"
  53. label="上传时间"
  54. min-width="160"
  55. align="center"
  56. header-align="center">
  57. </el-table-column>
  58. <el-table-column
  59. prop="orderRef5"
  60. label="备注"
  61. min-width="160"
  62. align="center"
  63. header-align="center">
  64. </el-table-column>
  65. <el-table-column
  66. label="操作"
  67. width="150"
  68. align="center"
  69. header-align="center"
  70. fixed="right">
  71. <template slot-scope="{row}">
  72. <el-link
  73. style="cursor:pointer; margin-right: 10px;"
  74. @click="previewFile(row)">
  75. 预览
  76. </el-link>
  77. <el-link
  78. style="cursor:pointer; color: #F56C6C;"
  79. v-if="!disabled"
  80. @click="handleRemove(row)">
  81. 删除
  82. </el-link>
  83. </template>
  84. </el-table-column>
  85. </el-table>
  86. <!-- 上传对话框 -->
  87. <el-dialog
  88. title="上传附件"
  89. :visible.sync="ossVisible"
  90. width="380px"
  91. append-to-body
  92. :close-on-click-modal="false">
  93. <el-form :inline="true" label-position="top" label-width="80px">
  94. <el-row>
  95. <el-form-item label="申请单号">
  96. <el-input v-model="ossForm.orderRef2" readonly style="width: 310px"></el-input>
  97. </el-form-item>
  98. </el-row>
  99. </el-form>
  100. <!-- 文件上传区域 -->
  101. <div style="margin: 15px 0;">
  102. <div style="margin-bottom: 10px;">
  103. 选择文件:
  104. <span style="color: #67C23A; font-size: 12px; margin-left: 10px;">
  105. <i class="el-icon-picture-outline"></i> 可支持直接 Ctrl+V 粘贴图片不需要点击上传按钮
  106. </span>
  107. </div>
  108. <el-upload
  109. drag
  110. action="#"
  111. ref="upload"
  112. :file-list="fileList"
  113. :on-remove="onRemoveFile"
  114. :on-change="onChangeFile"
  115. multiple
  116. :auto-upload="false"
  117. style="text-align: left;">
  118. <i class="el-icon-upload"></i>
  119. <div class="el-upload__text">将文件拖到此处<em>点击上传</em><em style="color: #67C23A;">Ctrl+V 粘贴图片</em></div>
  120. <div class="el-upload__tip" slot="tip">
  121. 支持pdfdwgdxfdocdocxxlsxlsxjpgpng格式
  122. </div>
  123. </el-upload>
  124. </div>
  125. <!-- 备注区域 -->
  126. <div style="margin: 15px 0;">
  127. <div style="margin-bottom: 10px;">备注:</div>
  128. <el-input
  129. type="textarea"
  130. v-model="ossForm.remark"
  131. resize="none"
  132. :autosize="{minRows: 2, maxRows: 2}">
  133. </el-input>
  134. </div>
  135. <span slot="footer" class="dialog-footer">
  136. <el-button type="primary" :loading="uploadLoading" @click="handleUploadFiles">保存</el-button>
  137. <el-button @click="ossVisible = false">关闭</el-button>
  138. </span>
  139. </el-dialog>
  140. </div>
  141. </template>
  142. <script>
  143. import { ossUpload, queryOss, removeOss, previewOssFileById, previewOssFileById2 } from '@/api/oss/oss'
  144. export default {
  145. name: 'ErfAttachmentManager',
  146. props: {
  147. // 申请单号
  148. applyNo: {
  149. type: String,
  150. required: true
  151. },
  152. // 是否禁用编辑
  153. disabled: {
  154. type: Boolean,
  155. default: false
  156. },
  157. // 表格高度
  158. height: {
  159. type: [String, Number],
  160. default: '30vh'
  161. }
  162. },
  163. data() {
  164. return {
  165. dataList: [],
  166. queryLoading: false,
  167. uploadLoading: false,
  168. selectionDataList: [],
  169. ossVisible: false,
  170. ossForm: {
  171. orderRef1: 'ERF', // 模块标识
  172. orderRef2: '', // 申请单号
  173. orderRef6: 'EXP_APPLY', // 业务类型标识
  174. remark: ''
  175. },
  176. fileList: [],
  177. pasteImageCounter: 0 // 粘贴图片计数器,用于生成文件名
  178. }
  179. },
  180. mounted() {
  181. this.handleQuery()
  182. },
  183. watch: {
  184. applyNo(newVal) {
  185. if (newVal) {
  186. this.handleQuery()
  187. }
  188. },
  189. // 监听对话框关闭,移除粘贴事件监听器
  190. ossVisible(newVal) {
  191. if (!newVal) {
  192. document.removeEventListener('paste', this.handlePaste)
  193. }
  194. }
  195. },
  196. beforeDestroy() {
  197. // 组件销毁时移除监听器
  198. document.removeEventListener('paste', this.handlePaste)
  199. },
  200. methods: {
  201. /**
  202. * 查询附件列表
  203. */
  204. handleQuery() {
  205. if (!this.applyNo) {
  206. return
  207. }
  208. let params = {
  209. orderRef1: 'ERF',
  210. orderRef2: this.applyNo,
  211. orderRef6: 'EXP_APPLY'
  212. }
  213. this.queryLoading = true
  214. queryOss(params).then(({data}) => {
  215. this.queryLoading = false
  216. if (data && data.code === 0) {
  217. this.dataList = data.rows || []
  218. } else {
  219. this.dataList = []
  220. this.$message.warning(data.msg || '查询失败')
  221. }
  222. }).catch(error => {
  223. this.queryLoading = false
  224. this.$message.error('查询异常')
  225. })
  226. },
  227. /**
  228. * 打开上传对话框
  229. */
  230. handleUpload() {
  231. this.$nextTick(() => {
  232. if (this.$refs.upload) {
  233. this.$refs.upload.clearFiles()
  234. }
  235. })
  236. this.fileList = []
  237. this.ossForm.orderRef2 = this.applyNo
  238. this.ossForm.remark = ''
  239. this.ossVisible = true
  240. this.pasteImageCounter = 0
  241. // 添加粘贴事件监听器
  242. this.$nextTick(() => {
  243. document.addEventListener('paste', this.handlePaste)
  244. })
  245. },
  246. /**
  247. * 文件选择变化
  248. */
  249. onRemoveFile(file, fileList) {
  250. this.fileList = fileList
  251. },
  252. onChangeFile(file, fileList) {
  253. this.fileList = fileList
  254. },
  255. /**
  256. * 处理粘贴事件支持直接粘贴图片
  257. */
  258. handlePaste(event) {
  259. // 只在对话框打开时处理粘贴
  260. if (!this.ossVisible) {
  261. return
  262. }
  263. const items = (event.clipboardData || event.originalEvent.clipboardData).items
  264. for (let i = 0; i < items.length; i++) {
  265. const item = items[i]
  266. // 检查是否为图片类型
  267. if (item.type.indexOf('image') !== -1) {
  268. event.preventDefault() // 阻止默认粘贴行为
  269. const blob = item.getAsFile()
  270. if (!blob) continue
  271. // 获取文件扩展名
  272. const extension = blob.type.split('/')[1] || 'png'
  273. // 尝试使用原始文件名,如果没有则使用简洁的默认名称
  274. let fileName = blob.name
  275. if (!fileName || fileName === 'image.png' || fileName === 'blob') {
  276. // 截图或无名称的情况,使用简洁的默认名称
  277. this.pasteImageCounter++
  278. fileName = `粘贴图片${this.pasteImageCounter}.${extension}`
  279. }
  280. // 创建 File 对象(保留原始文件名)
  281. const file = new File([blob], fileName, { type: blob.type })
  282. // 创建一个符合 el-upload 格式的文件对象
  283. const uploadFile = {
  284. name: fileName,
  285. size: file.size,
  286. type: file.type,
  287. raw: file,
  288. uid: Date.now() + this.pasteImageCounter,
  289. status: 'ready'
  290. }
  291. // 添加到文件列表
  292. this.fileList.push(uploadFile)
  293. // 手动触发 el-upload 的文件列表更新
  294. if (this.$refs.upload) {
  295. this.$refs.upload.uploadFiles.push(uploadFile)
  296. }
  297. this.$message.success(`已粘贴图片: ${fileName}`)
  298. break // 只处理第一张图片
  299. }
  300. }
  301. },
  302. /**
  303. * 执行上传
  304. */
  305. handleUploadFiles() {
  306. if (this.fileList.length === 0) {
  307. this.$message.error('请选择文件')
  308. return
  309. }
  310. let formData = new FormData()
  311. for (let i = 0; i < this.fileList.length; i++) {
  312. formData.append('file', this.fileList[i].raw)
  313. }
  314. formData.append('orderRef1', 'ERF')
  315. formData.append('orderRef2', this.ossForm.orderRef2)
  316. formData.append('orderRef6', 'EXP_APPLY')
  317. formData.append('createdBy', this.$store.state.user.name)
  318. formData.append('orderRef5', this.ossForm.remark)
  319. this.uploadLoading = true
  320. ossUpload(formData).then(({data}) => {
  321. this.uploadLoading = false
  322. if (data && data.code === 0) {
  323. this.$message.success('上传成功')
  324. this.handleQuery()
  325. this.ossVisible = false
  326. } else {
  327. this.$message.warning(data.msg || '上传失败')
  328. }
  329. }).catch(error => {
  330. this.uploadLoading = false
  331. this.$message.error('上传异常')
  332. })
  333. },
  334. /**
  335. * 删除附件
  336. */
  337. handleRemove(row) {
  338. this.$confirm('确认删除该附件吗?', '提示', {
  339. confirmButtonText: '确定',
  340. cancelButtonText: '取消',
  341. type: 'warning'
  342. }).then(() => {
  343. let ids = [row.id]
  344. removeOss(ids).then(({data}) => {
  345. if (data && data.code === 0) {
  346. this.$message.success('删除成功')
  347. this.handleQuery()
  348. } else {
  349. this.$message.warning(data.msg || '删除失败')
  350. }
  351. }).catch(error => {
  352. this.$message.error('删除异常')
  353. })
  354. }).catch(() => {})
  355. },
  356. /**
  357. * 预览文件
  358. */
  359. previewFile(row) {
  360. let type = ''
  361. let fileType = row.fileType.toLowerCase()
  362. // 图片类型
  363. let image = ['jpg', 'jpeg', 'png', 'gif', 'bmp']
  364. if (image.includes(fileType)) {
  365. type = 'image/' + fileType
  366. }
  367. // 视频类型
  368. let video = ['mp4', 'avi', 'mov', 'wmv', 'flv']
  369. if (video.includes(fileType)) {
  370. type = 'video/' + fileType
  371. }
  372. // 文本类型
  373. if (fileType === 'txt') {
  374. type = 'text/plain;charset=utf-8'
  375. }
  376. // Excel类型
  377. if (fileType === 'xlsx' || fileType === 'xls') {
  378. type = 'excel'
  379. }
  380. // Word类型
  381. if (fileType === 'docx') {
  382. type = 'word'
  383. }
  384. // PDF类型
  385. if (fileType === 'pdf') {
  386. type = 'application/pdf;charset-UTF-8'
  387. }
  388. // Office文件不支持预览
  389. if (fileType === 'doc' || fileType === 'ppt' || fileType === 'pptx') {
  390. this.$message.warning('该文件格式暂不支持预览,请下载后查看')
  391. return
  392. }
  393. // CAD文件不支持预览
  394. if (fileType === 'dwg' || fileType === 'dxf') {
  395. this.$message.warning('CAD文件暂不支持预览,请下载后使用CAD软件查看')
  396. return
  397. }
  398. if (type === '') {
  399. this.$message.warning('该文件格式暂不支持预览')
  400. return
  401. }
  402. let params = {
  403. id: row.id,
  404. fileType: type
  405. }
  406. previewOssFileById2(params).then(({data}) => {
  407. if (type === 'excel' || type === 'word') {
  408. type = 'application/pdf;charset=UTF-8'
  409. }
  410. const blob = new Blob([data], { type: type })
  411. const fileURL = URL.createObjectURL(blob)
  412. if (type === 'xls' || type === 'docx' || type === 'xlsx') {
  413. const { href } = this.$router.resolve({
  414. name: 'pre',
  415. query: {
  416. src: fileURL,
  417. type: 'pdf'
  418. }
  419. })
  420. window.open(href, '_blank')
  421. } else {
  422. // 在新标签页中打开文件预览
  423. window.open(fileURL, '_blank')
  424. }
  425. })
  426. },
  427. /**
  428. * 选择变化
  429. */
  430. handleSelectionChange(val) {
  431. this.selectionDataList = val
  432. },
  433. /**
  434. * 下载选中的附件
  435. */
  436. handleDownload() {
  437. if (this.selectionDataList.length === 0) {
  438. this.$message.warning('请选择要下载的附件')
  439. return
  440. }
  441. let selectList = this.selectionDataList.map(item => ({ ...item }))
  442. for (let i = 0; i < selectList.length; i++) {
  443. let params = {
  444. id: selectList[i].id
  445. }
  446. previewOssFileById(params).then((response) => {
  447. const blob = new Blob([response.data], { type: response.headers['content-type'] })
  448. const link = document.createElement('a')
  449. link.href = URL.createObjectURL(blob)
  450. link.setAttribute('download', selectList[i].fileName)
  451. link.target = '_blank'
  452. link.click()
  453. URL.revokeObjectURL(link.href)
  454. })
  455. }
  456. this.$refs.table.clearSelection()
  457. }
  458. }
  459. }
  460. </script>
  461. <style scoped>
  462. .erf-attachment-manager {
  463. }
  464. /* 表格样式优化 */
  465. .el-table >>> .el-table__header th {
  466. background-color: #F5F7FA;
  467. color: #606266;
  468. font-weight: bold;
  469. }
  470. .el-table >>> .el-table__row td {
  471. padding: 8px 0;
  472. }
  473. </style>