|
|
@@ -0,0 +1,240 @@
|
|
|
+<template>
|
|
|
+ <div class="attachment-upload">
|
|
|
+ <!-- 上传按钮 -->
|
|
|
+ <el-upload
|
|
|
+ action="#"
|
|
|
+ :file-list="uploadFileList"
|
|
|
+ :before-upload="beforeUpload"
|
|
|
+ :http-request="handleUpload"
|
|
|
+ :show-file-list="false"
|
|
|
+ :multiple="true"
|
|
|
+ :disabled="!canUpload"
|
|
|
+ >
|
|
|
+ <el-button type="primary" :disabled="!canUpload">
|
|
|
+ <el-icon><UploadFilled /></el-icon>
|
|
|
+ 添加附件
|
|
|
+ </el-button>
|
|
|
+ </el-upload>
|
|
|
+
|
|
|
+ <!-- 已上传附件列表 -->
|
|
|
+ <div v-if="localFileList.length > 0" class="attachment-list">
|
|
|
+ <div v-for="file in localFileList" :key="file.id" class="attachment-item">
|
|
|
+ <div class="attachment-row-top">
|
|
|
+ <el-icon :size="18" class="file-icon"><Document /></el-icon>
|
|
|
+ <span class="file-name">{{ file.fileName }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="attachment-row-bottom">
|
|
|
+ <span class="upload-time" v-if="file.createTime">{{ formatDate(file.createTime) }}</span>
|
|
|
+ <div class="attachment-actions">
|
|
|
+ <el-button type="primary" link size="small" @click="handlePreview(file)">
|
|
|
+ <el-icon><View /></el-icon>
|
|
|
+ 预览
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary" link size="small" @click="handleDownload(file)">
|
|
|
+ <el-icon><Download /></el-icon>
|
|
|
+ 下载
|
|
|
+ </el-button>
|
|
|
+ <el-button type="danger" link size="small" @click="handleDelete(file)">
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
+import { UploadFilled, Document, View, Download, Delete } from '@element-plus/icons-vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import { TaskOrderFileApi } from '@/api/pressure2/taskOrderFile'
|
|
|
+import { buildFileUrl } from '@/utils'
|
|
|
+import { formatDate } from '@/utils/formatTime'
|
|
|
+import request from '@/config/axios'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ /** 任务单ID */
|
|
|
+ orderId: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ /** 检验设备单ID */
|
|
|
+ orderItemId: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ /** 设备类型 200锅炉 300管道 */
|
|
|
+ equipMainType: {
|
|
|
+ type: Number,
|
|
|
+ default: 0
|
|
|
+ },
|
|
|
+ /** 是否可上传 */
|
|
|
+ canUpload: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 本地文件列表
|
|
|
+const localFileList = ref<Recordable[]>([])
|
|
|
+// 上传组件显示用的空列表
|
|
|
+const uploadFileList = ref<Recordable[]>([])
|
|
|
+
|
|
|
+// 页面加载时自动获取附件列表
|
|
|
+onMounted(() => {
|
|
|
+ fetchFileList()
|
|
|
+})
|
|
|
+
|
|
|
+const beforeUpload = (file: File) => {
|
|
|
+ const maxSize = 50 * 1024 * 1024
|
|
|
+ if (file.size > maxSize) {
|
|
|
+ ElMessage.error('文件大小不能超过50MB')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+// 处理文件上传
|
|
|
+const handleUpload = async (options: Recordable) => {
|
|
|
+ const { file } = options
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('file', file)
|
|
|
+ formData.append('orderId', props.orderId || '')
|
|
|
+ formData.append('orderItemId', props.orderItemId || '')
|
|
|
+ formData.append('equipMainType', String(props.equipMainType))
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await TaskOrderFileApi.uploadAndCreate(formData)
|
|
|
+ if (res) {
|
|
|
+ ElMessage.success('文件上传成功')
|
|
|
+ fetchFileList()
|
|
|
+ }
|
|
|
+ } catch (error: any) {
|
|
|
+ ElMessage.error(error?.message || '文件上传失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 预览附件
|
|
|
+const handlePreview = (file: Recordable) => {
|
|
|
+ const url = buildFileUrl(file.fileUrl)
|
|
|
+ window.open(url, '_blank')
|
|
|
+}
|
|
|
+
|
|
|
+// 下载附件 - 使用 blob 方式确保触发下载而不是浏览器打开
|
|
|
+const handleDownload = async (file: Recordable) => {
|
|
|
+ try {
|
|
|
+ const url = buildFileUrl(file.fileUrl)
|
|
|
+ const blob = await request.download({ url })
|
|
|
+ const blobUrl = URL.createObjectURL(blob as unknown as Blob)
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.href = blobUrl
|
|
|
+ link.download = file.fileName || '下载文件'
|
|
|
+ document.body.appendChild(link)
|
|
|
+ link.click()
|
|
|
+ document.body.removeChild(link)
|
|
|
+ URL.revokeObjectURL(blobUrl)
|
|
|
+ } catch (error: any) {
|
|
|
+ ElMessage.error('下载失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 删除附件
|
|
|
+const handleDelete = async (file: Recordable) => {
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('确认删除该附件?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ await TaskOrderFileApi.deleteFile(file.id)
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ fetchFileList()
|
|
|
+ } catch (error: any) {
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ ElMessage.error(error?.message || '删除失败')
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 刷新附件列表
|
|
|
+const fetchFileList = async () => {
|
|
|
+ if (!props.orderItemId) return
|
|
|
+ try {
|
|
|
+ const res = await TaskOrderFileApi.getPage({
|
|
|
+ orderItemId: props.orderItemId,
|
|
|
+ pageNo: 1,
|
|
|
+ pageSize: 100
|
|
|
+ })
|
|
|
+ if (res?.list) {
|
|
|
+ localFileList.value = res.list
|
|
|
+ }
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('获取附件列表失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.attachment-upload {
|
|
|
+ .attachment-list {
|
|
|
+ margin-top: 12px;
|
|
|
+
|
|
|
+ .attachment-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 10px 12px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
+ border-radius: 4px;
|
|
|
+ background-color: var(--el-fill-color-lighter);
|
|
|
+ transition: background-color 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background-color: var(--el-fill-color-light);
|
|
|
+ }
|
|
|
+
|
|
|
+ .attachment-row-top {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+
|
|
|
+ .file-icon {
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-top: 2px;
|
|
|
+ color: var(--el-color-primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-name {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.5;
|
|
|
+ word-break: break-all;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .attachment-row-bottom {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding-left: 26px;
|
|
|
+
|
|
|
+ .upload-time {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ }
|
|
|
+
|
|
|
+ .attachment-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ margin-left: auto;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|