AttachmentUpload.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <template>
  2. <div class="attachment-upload">
  3. <!-- 上传按钮 -->
  4. <el-upload
  5. action="#"
  6. :file-list="uploadFileList"
  7. :before-upload="beforeUpload"
  8. :http-request="handleUpload"
  9. :show-file-list="false"
  10. :multiple="true"
  11. :disabled="!canUpload"
  12. >
  13. <el-button type="primary" :disabled="!canUpload">
  14. <el-icon><UploadFilled /></el-icon>
  15. 添加附件
  16. </el-button>
  17. </el-upload>
  18. <!-- 已上传附件列表 -->
  19. <div v-if="localFileList.length > 0" class="attachment-list">
  20. <div v-for="file in localFileList" :key="file.id" class="attachment-item">
  21. <div class="attachment-row-top">
  22. <el-icon :size="18" class="file-icon"><Document /></el-icon>
  23. <span class="file-name">{{ file.fileName }}</span>
  24. </div>
  25. <div class="attachment-row-bottom">
  26. <span class="upload-time" v-if="file.createTime">{{ formatDate(file.createTime) }}</span>
  27. <div class="attachment-actions">
  28. <el-button type="primary" link size="small" @click="handlePreview(file)">
  29. <el-icon><View /></el-icon>
  30. 预览
  31. </el-button>
  32. <el-button type="primary" link size="small" @click="handleDownload(file)">
  33. <el-icon><Download /></el-icon>
  34. 下载
  35. </el-button>
  36. <el-button type="danger" link size="small" @click="handleDelete(file)">
  37. <el-icon><Delete /></el-icon>
  38. 删除
  39. </el-button>
  40. </div>
  41. </div>
  42. </div>
  43. </div>
  44. </div>
  45. </template>
  46. <script setup lang="ts">
  47. import { ref, onMounted } from 'vue'
  48. import { UploadFilled, Document, View, Download, Delete } from '@element-plus/icons-vue'
  49. import { ElMessage, ElMessageBox } from 'element-plus'
  50. import { TaskOrderFileApi } from '@/api/pressure2/taskOrderFile'
  51. import { buildFileUrl } from '@/utils'
  52. import { formatDate } from '@/utils/formatTime'
  53. import request from '@/config/axios'
  54. const props = defineProps({
  55. /** 任务单ID */
  56. orderId: {
  57. type: String,
  58. default: ''
  59. },
  60. /** 检验设备单ID */
  61. orderItemId: {
  62. type: String,
  63. default: ''
  64. },
  65. /** 设备类型 200锅炉 300管道 */
  66. equipMainType: {
  67. type: Number,
  68. default: 0
  69. },
  70. /** 是否可上传 */
  71. canUpload: {
  72. type: Boolean,
  73. default: true
  74. }
  75. })
  76. // 本地文件列表
  77. const localFileList = ref<Recordable[]>([])
  78. // 上传组件显示用的空列表
  79. const uploadFileList = ref<Recordable[]>([])
  80. // 页面加载时自动获取附件列表
  81. onMounted(() => {
  82. fetchFileList()
  83. })
  84. const beforeUpload = (file: File) => {
  85. const maxSize = 50 * 1024 * 1024
  86. if (file.size > maxSize) {
  87. ElMessage.error('文件大小不能超过50MB')
  88. return false
  89. }
  90. return true
  91. }
  92. // 处理文件上传
  93. const handleUpload = async (options: Recordable) => {
  94. const { file } = options
  95. const formData = new FormData()
  96. formData.append('file', file)
  97. formData.append('orderId', props.orderId || '')
  98. formData.append('orderItemId', props.orderItemId || '')
  99. formData.append('equipMainType', String(props.equipMainType))
  100. try {
  101. const res = await TaskOrderFileApi.uploadAndCreate(formData)
  102. if (res) {
  103. ElMessage.success('文件上传成功')
  104. fetchFileList()
  105. }
  106. } catch (error: any) {
  107. ElMessage.error(error?.message || '文件上传失败')
  108. }
  109. }
  110. // 预览附件
  111. const handlePreview = (file: Recordable) => {
  112. const url = buildFileUrl(file.fileUrl)
  113. window.open(url, '_blank')
  114. }
  115. // 下载附件 - 使用 blob 方式确保触发下载而不是浏览器打开
  116. const handleDownload = async (file: Recordable) => {
  117. try {
  118. const url = buildFileUrl(file.fileUrl)
  119. const blob = await request.download({ url })
  120. const blobUrl = URL.createObjectURL(blob as unknown as Blob)
  121. const link = document.createElement('a')
  122. link.href = blobUrl
  123. link.download = file.fileName || '下载文件'
  124. document.body.appendChild(link)
  125. link.click()
  126. document.body.removeChild(link)
  127. URL.revokeObjectURL(blobUrl)
  128. } catch (error: any) {
  129. ElMessage.error('下载失败')
  130. }
  131. }
  132. // 删除附件
  133. const handleDelete = async (file: Recordable) => {
  134. try {
  135. await ElMessageBox.confirm('确认删除该附件?', '提示', {
  136. confirmButtonText: '确定',
  137. cancelButtonText: '取消',
  138. type: 'warning'
  139. })
  140. await TaskOrderFileApi.deleteFile(file.id)
  141. ElMessage.success('删除成功')
  142. fetchFileList()
  143. } catch (error: any) {
  144. if (error !== 'cancel') {
  145. ElMessage.error(error?.message || '删除失败')
  146. }
  147. }
  148. }
  149. // 刷新附件列表
  150. const fetchFileList = async () => {
  151. if (!props.orderItemId) return
  152. try {
  153. const res = await TaskOrderFileApi.getPage({
  154. orderItemId: props.orderItemId,
  155. pageNo: 1,
  156. pageSize: 100
  157. })
  158. if (res?.list) {
  159. localFileList.value = res.list
  160. }
  161. } catch (error: any) {
  162. console.error('获取附件列表失败:', error)
  163. }
  164. }
  165. </script>
  166. <style lang="scss" scoped>
  167. .attachment-upload {
  168. .attachment-list {
  169. margin-top: 12px;
  170. .attachment-item {
  171. display: flex;
  172. flex-direction: column;
  173. padding: 10px 12px;
  174. margin-bottom: 8px;
  175. border: 1px solid var(--el-border-color-light);
  176. border-radius: 4px;
  177. background-color: var(--el-fill-color-lighter);
  178. transition: background-color 0.2s;
  179. &:hover {
  180. background-color: var(--el-fill-color-light);
  181. }
  182. .attachment-row-top {
  183. display: flex;
  184. align-items: flex-start;
  185. gap: 8px;
  186. margin-bottom: 6px;
  187. .file-icon {
  188. flex-shrink: 0;
  189. margin-top: 2px;
  190. color: var(--el-color-primary);
  191. }
  192. .file-name {
  193. flex: 1;
  194. font-size: 13px;
  195. line-height: 1.5;
  196. word-break: break-all;
  197. color: var(--el-text-color-primary);
  198. }
  199. }
  200. .attachment-row-bottom {
  201. display: flex;
  202. align-items: center;
  203. justify-content: space-between;
  204. padding-left: 26px;
  205. .upload-time {
  206. font-size: 12px;
  207. color: var(--el-text-color-secondary);
  208. }
  209. .attachment-actions {
  210. display: flex;
  211. align-items: center;
  212. gap: 4px;
  213. margin-left: auto;
  214. }
  215. }
  216. }
  217. }
  218. }
  219. </style>