| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="showUploadDialog" |
| | | title="ä¸ä¼ å·¡æ£è®°å½" |
| | | width="560px" |
| | | :before-close="closeUploadDialog" |
| | | > |
| | | <el-tabs v-model="currentUploadType"> |
| | | <el-tab-pane label="ç产å" name="before" /> |
| | | <el-tab-pane label="ç产ä¸" name="after" /> |
| | | <el-tab-pane label="ç产å" name="issue" /> |
| | | </el-tabs> |
| | | |
| | | <div class="exception-section"> |
| | | <div class="section-title">æ¯å¦åå¨å¼å¸¸ï¼</div> |
| | | <el-radio-group v-model="hasException"> |
| | | <el-radio :value="false">æ£å¸¸</el-radio> |
| | | <el-radio :value="true">åå¨å¼å¸¸</el-radio> |
| | | </el-radio-group> |
| | | </div> |
| | | |
| | | <div class="upload-buttons"> |
| | | <el-upload |
| | | ref="uploadRef" |
| | | v-model:file-list="uploadFileList" |
| | | :action="uploadUrl" |
| | | :headers="uploadHeaders" |
| | | :show-file-list="false" |
| | | :accept="uploadAccept" |
| | | :multiple="false" |
| | | :before-upload="handleBeforeUpload" |
| | | :on-success="handleUploadSuccess" |
| | | :on-error="handleUploadError" |
| | | :on-progress="handleUploadProgress" |
| | | :disabled="uploading || getCurrentFiles().length >= uploadConfig.limit" |
| | | > |
| | | <el-button |
| | | type="primary" |
| | | :loading="uploading" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit" |
| | | > |
| | | éæ©å¾ç/è§é¢ |
| | | </el-button> |
| | | </el-upload> |
| | | </div> |
| | | |
| | | <el-progress |
| | | v-if="uploading" |
| | | :percentage="uploadProgress" |
| | | style="margin: 12px 0" |
| | | /> |
| | | |
| | | <div v-if="getCurrentFiles().length" class="file-list"> |
| | | <div |
| | | v-for="(file, index) in getCurrentFiles()" |
| | | :key="file.uid || file.id || index" |
| | | class="file-item" |
| | | > |
| | | <div class="file-preview-container"> |
| | | <img |
| | | v-if="isImageFile(file)" |
| | | :src="file.url || file.downloadUrl" |
| | | class="file-preview" |
| | | @click="previewAttachment(file)" |
| | | /> |
| | | <div |
| | | v-else |
| | | class="video-preview" |
| | | @click="previewAttachment(file)" |
| | | > |
| | | è§é¢ |
| | | </div> |
| | | |
| | | <button class="delete-btn" @click="removeFile(index)">x</button> |
| | | </div> |
| | | |
| | | <div class="file-name"> |
| | | {{ file.bucketFilename || file.name || "éä»¶" }} |
| | | </div> |
| | | <div class="file-size"> |
| | | {{ formatFileSize(file.size || file.byteSize) }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <el-empty |
| | | v-else |
| | | :description="`è¯·éæ©è¦ä¸ä¼ ç${getUploadTypeText()}å¾çæè§é¢`" |
| | | /> |
| | | |
| | | <div class="upload-summary"> |
| | | ç产åï¼{{ beforeModelValue.length }} 个 | |
| | | ç产ä¸ï¼{{ afterModelValue.length }} 个 | |
| | | ç产åï¼{{ issueModelValue.length }} 个 |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitUpload">æäº¤</el-button> |
| | | <el-button @click="closeUploadDialog">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <el-dialog |
| | | v-model="showVideoDialog" |
| | | :title="currentVideoFile?.originalFilename || 'è§é¢é¢è§'" |
| | | width="720px" |
| | | > |
| | | <video |
| | | v-if="currentVideoFile" |
| | | :src="currentVideoFile.url || currentVideoFile.downloadUrl" |
| | | class="video-player" |
| | | controls |
| | | autoplay |
| | | /> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, ref } from "vue"; |
| | | import { ElLoading, ElMessage, ElMessageBox } from "element-plus"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import { uploadInspectionTask } from "@/api/inspectionManagement/index.js"; |
| | | |
| | | const emit = defineEmits(["closeDia", "success"]); |
| | | |
| | | const showUploadDialog = ref(false); |
| | | const uploading = ref(false); |
| | | const uploadProgress = ref(0); |
| | | const uploadRef = ref(null); |
| | | const uploadFileList = ref([]); |
| | | |
| | | const beforeModelValue = ref([]); |
| | | const afterModelValue = ref([]); |
| | | const issueModelValue = ref([]); |
| | | const currentUploadType = ref("before"); |
| | | const hasException = ref(null); |
| | | const currentTask = ref(null); |
| | | |
| | | const showVideoDialog = ref(false); |
| | | const currentVideoFile = ref(null); |
| | | |
| | | const uploadConfig = { |
| | | action: "/file/upload", |
| | | limit: 10, |
| | | fileSize: 50, |
| | | fileType: ["jpg", "jpeg", "png", "gif", "webp", "mp4", "mov", "avi", "wmv"], |
| | | }; |
| | | |
| | | const uploadUrl = `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}`; |
| | | const uploadHeaders = { |
| | | Authorization: `Bearer ${getToken()}`, |
| | | }; |
| | | const uploadAccept = computed(() => |
| | | uploadConfig.fileType.map(item => `.${item}`).join(",") |
| | | ); |
| | | const filePreviewBase = __BASE_API__; |
| | | |
| | | const cloneData = value => JSON.parse(JSON.stringify(value || {})); |
| | | |
| | | const normalizeFileUrl = rawUrl => { |
| | | if (!rawUrl || typeof rawUrl !== "string") return ""; |
| | | |
| | | let fileUrl = rawUrl.trim(); |
| | | if (!fileUrl) return ""; |
| | | if (/^https?:\/\//i.test(fileUrl)) return fileUrl; |
| | | |
| | | if (fileUrl.indexOf("\\") > -1) { |
| | | const uploadsIndex = fileUrl.toLowerCase().indexOf("uploads"); |
| | | if (uploadsIndex > -1) { |
| | | const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, "/"); |
| | | fileUrl = `/${relativePath}`; |
| | | } else { |
| | | const parts = fileUrl.split("\\"); |
| | | const fileName = parts[parts.length - 1]; |
| | | fileUrl = `/uploads/${fileName}`; |
| | | } |
| | | } |
| | | |
| | | if (!fileUrl.startsWith("http")) { |
| | | if (!fileUrl.startsWith("/")) { |
| | | fileUrl = `/${fileUrl}`; |
| | | } |
| | | fileUrl = `${filePreviewBase}${fileUrl}`; |
| | | } |
| | | |
| | | return fileUrl; |
| | | }; |
| | | |
| | | const mapExistingFile = (file, type) => ({ |
| | | ...file, |
| | | id: file?.id, |
| | | tempId: file?.tempId ?? file?.tempFileId ?? file?.id, |
| | | tempFileId: file?.tempFileId ?? file?.id, |
| | | url: normalizeFileUrl(file?.url || file?.downloadUrl || file?.fileUrl || ""), |
| | | downloadUrl: normalizeFileUrl( |
| | | file?.downloadUrl || file?.url || file?.fileUrl || "" |
| | | ), |
| | | bucketFilename: |
| | | file?.bucketFilename || file?.originalFilename || file?.fileName || file?.name, |
| | | originalFilename: |
| | | file?.originalFilename || file?.bucketFilename || file?.fileName || file?.name, |
| | | size: file?.size || file?.byteSize, |
| | | byteSize: file?.byteSize || file?.size, |
| | | contentType: file?.contentType || "", |
| | | type, |
| | | uid: file?.uid || `${type}-${file?.id || file?.url || Math.random()}`, |
| | | }); |
| | | |
| | | const resetDialogState = () => { |
| | | beforeModelValue.value = []; |
| | | afterModelValue.value = []; |
| | | issueModelValue.value = []; |
| | | currentUploadType.value = "before"; |
| | | hasException.value = null; |
| | | currentTask.value = null; |
| | | uploadProgress.value = 0; |
| | | uploading.value = false; |
| | | uploadFileList.value = []; |
| | | uploadRef.value?.clearFiles?.(); |
| | | }; |
| | | |
| | | const openDialog = task => { |
| | | const rawTask = cloneData(task?.__raw || task); |
| | | currentTask.value = { |
| | | ...rawTask, |
| | | taskId: rawTask.taskId || rawTask.id, |
| | | storageBlobDTO: [], |
| | | }; |
| | | |
| | | beforeModelValue.value = Array.isArray(rawTask.commonFileListBefore) |
| | | ? rawTask.commonFileListBefore.map(file => mapExistingFile(file, 10)) |
| | | : []; |
| | | afterModelValue.value = Array.isArray(rawTask.commonFileListAfter) |
| | | ? rawTask.commonFileListAfter.map(file => mapExistingFile(file, 11)) |
| | | : []; |
| | | issueModelValue.value = Array.isArray(rawTask.commonFileList) |
| | | ? rawTask.commonFileList.map(file => mapExistingFile(file, 12)) |
| | | : []; |
| | | |
| | | currentUploadType.value = "before"; |
| | | hasException.value = |
| | | typeof rawTask.hasException === "boolean" ? rawTask.hasException : null; |
| | | uploadFileList.value = []; |
| | | showUploadDialog.value = true; |
| | | }; |
| | | |
| | | const closeUploadDialog = () => { |
| | | showUploadDialog.value = false; |
| | | resetDialogState(); |
| | | emit("closeDia"); |
| | | }; |
| | | |
| | | const getCurrentFiles = () => { |
| | | if (currentUploadType.value === "before") return beforeModelValue.value; |
| | | if (currentUploadType.value === "after") return afterModelValue.value; |
| | | return issueModelValue.value; |
| | | }; |
| | | |
| | | const getUploadTypeText = () => { |
| | | if (currentUploadType.value === "before") return "ç产å"; |
| | | if (currentUploadType.value === "after") return "ç产ä¸"; |
| | | return "ç产å"; |
| | | }; |
| | | |
| | | const getTabType = () => { |
| | | if (currentUploadType.value === "before") return 10; |
| | | if (currentUploadType.value === "after") return 11; |
| | | return 12; |
| | | }; |
| | | |
| | | const handleBeforeUpload = file => { |
| | | if (getCurrentFiles().length >= uploadConfig.limit) { |
| | | ElMessage.warning(`æå¤åªè½éæ©${uploadConfig.limit}个æä»¶`); |
| | | return false; |
| | | } |
| | | |
| | | const ext = file.name.split(".").pop()?.toLowerCase(); |
| | | if (!uploadConfig.fileType.includes(ext)) { |
| | | ElMessage.warning(`æä»¶æ ¼å¼ä¸æ¯æï¼è¯·ä¸ä¼ ${uploadConfig.fileType.join("/")} æ ¼å¼`); |
| | | return false; |
| | | } |
| | | |
| | | const maxSize = uploadConfig.fileSize * 1024 * 1024; |
| | | if (file.size > maxSize) { |
| | | ElMessage.warning(`æä»¶å¤§å°ä¸è½è¶
è¿ ${uploadConfig.fileSize}MB`); |
| | | return false; |
| | | } |
| | | |
| | | uploading.value = true; |
| | | uploadProgress.value = 0; |
| | | return true; |
| | | }; |
| | | |
| | | const handleUploadProgress = event => { |
| | | if (event?.percent) { |
| | | uploadProgress.value = Math.round(event.percent); |
| | | } |
| | | }; |
| | | |
| | | const handleUploadSuccess = (response, file) => { |
| | | uploading.value = false; |
| | | uploadProgress.value = 0; |
| | | uploadFileList.value = []; |
| | | uploadRef.value?.clearFiles?.(); |
| | | |
| | | const uploadedFile = response?.data; |
| | | if (response?.code !== 200 || !uploadedFile) { |
| | | ElMessage.error(response?.msg || "ä¸ä¼ ååºæ°æ®æ ¼å¼é误"); |
| | | return; |
| | | } |
| | | |
| | | const type = getTabType(); |
| | | const objectUrl = file?.raw ? URL.createObjectURL(file.raw) : ""; |
| | | const fileData = { |
| | | id: uploadedFile.id, |
| | | tempId: uploadedFile.tempId ?? uploadedFile.tempFileId ?? uploadedFile.id, |
| | | tempFileId: uploadedFile.tempFileId ?? uploadedFile.id, |
| | | url: normalizeFileUrl(uploadedFile.url || uploadedFile.downloadUrl || objectUrl), |
| | | downloadUrl: normalizeFileUrl( |
| | | uploadedFile.downloadUrl || uploadedFile.url || objectUrl |
| | | ), |
| | | bucketFilename: |
| | | uploadedFile.bucketFilename || uploadedFile.originalFilename || file.name, |
| | | originalFilename: |
| | | uploadedFile.originalFilename || uploadedFile.bucketFilename || file.name, |
| | | size: uploadedFile.size || uploadedFile.byteSize || file.size, |
| | | byteSize: uploadedFile.byteSize || uploadedFile.size || file.size, |
| | | createTime: uploadedFile.createTime || Date.now(), |
| | | contentType: uploadedFile.contentType || file.raw?.type || "", |
| | | type, |
| | | uid: `${Date.now()}-${Math.random()}`, |
| | | }; |
| | | |
| | | if (currentUploadType.value === "before") { |
| | | beforeModelValue.value.push(fileData); |
| | | } else if (currentUploadType.value === "after") { |
| | | afterModelValue.value.push(fileData); |
| | | } else { |
| | | issueModelValue.value.push(fileData); |
| | | } |
| | | |
| | | ElMessage.success("ä¸ä¼ æå"); |
| | | }; |
| | | |
| | | const handleUploadError = error => { |
| | | uploading.value = false; |
| | | uploadProgress.value = 0; |
| | | uploadFileList.value = []; |
| | | uploadRef.value?.clearFiles?.(); |
| | | ElMessage.error(error?.message || "ä¸ä¼ 失败"); |
| | | }; |
| | | |
| | | const removeFile = async index => { |
| | | try { |
| | | await ElMessageBox.confirm("ç¡®å®è¦å é¤è¿ä¸ªæä»¶åï¼", "确认å é¤", { |
| | | type: "warning", |
| | | }); |
| | | |
| | | if (currentUploadType.value === "before") { |
| | | beforeModelValue.value.splice(index, 1); |
| | | } else if (currentUploadType.value === "after") { |
| | | afterModelValue.value.splice(index, 1); |
| | | } else { |
| | | issueModelValue.value.splice(index, 1); |
| | | } |
| | | |
| | | ElMessage.success("å 餿å"); |
| | | } catch {} |
| | | }; |
| | | |
| | | const buildSubmitFiles = () => { |
| | | const list = [ |
| | | ...beforeModelValue.value, |
| | | ...afterModelValue.value, |
| | | ...issueModelValue.value, |
| | | ]; |
| | | |
| | | return list.map(item => ({ |
| | | ...item, |
| | | url: item?.downloadUrl || item?.url || "", |
| | | })); |
| | | }; |
| | | |
| | | const submitUpload = async () => { |
| | | if (hasException.value === null) { |
| | | ElMessage.warning("è¯·éæ©æ¯å¦åå¨å¼å¸¸"); |
| | | return; |
| | | } |
| | | |
| | | const files = buildSubmitFiles(); |
| | | if (!files.length) { |
| | | ElMessage.warning("请å
ä¸ä¼ æä»¶"); |
| | | return; |
| | | } |
| | | |
| | | const loadingInstance = ElLoading.service({ |
| | | text: "æäº¤ä¸...", |
| | | background: "rgba(0, 0, 0, 0.3)", |
| | | }); |
| | | |
| | | try { |
| | | const tempFileIds = files |
| | | .map(item => item?.tempId ?? item?.tempFileId ?? item?.id) |
| | | .filter(Boolean); |
| | | |
| | | const payload = { |
| | | ...currentTask.value, |
| | | hasException: hasException.value, |
| | | storageBlobDTO: files, |
| | | tempFileIds, |
| | | commonFileListBefore: beforeModelValue.value, |
| | | commonFileListAfter: afterModelValue.value, |
| | | commonFileList: issueModelValue.value, |
| | | }; |
| | | |
| | | const result = await uploadInspectionTask(payload); |
| | | if (result?.code === 200 || result?.success) { |
| | | ElMessage.success("æäº¤æå"); |
| | | showUploadDialog.value = false; |
| | | resetDialogState(); |
| | | emit("success"); |
| | | emit("closeDia"); |
| | | } else { |
| | | ElMessage.error(result?.msg || result?.message || "æäº¤å¤±è´¥"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || "æäº¤å¤±è´¥"); |
| | | } finally { |
| | | loadingInstance.close(); |
| | | } |
| | | }; |
| | | |
| | | const goToRepair = () => { |
| | | const taskInfo = { |
| | | taskId: currentTask.value?.taskId || currentTask.value?.id, |
| | | taskName: currentTask.value?.taskName, |
| | | inspectionLocation: currentTask.value?.inspectionLocation, |
| | | inspector: currentTask.value?.inspector, |
| | | uploadedFiles: { |
| | | before: beforeModelValue.value, |
| | | after: afterModelValue.value, |
| | | issue: issueModelValue.value, |
| | | }, |
| | | }; |
| | | |
| | | sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskInfo)); |
| | | window.location.href = "/equipmentManagement/repair"; |
| | | }; |
| | | |
| | | const previewAttachment = file => { |
| | | if (isImageFile(file)) { |
| | | window.open(file.url || file.downloadUrl, "_blank"); |
| | | } else { |
| | | currentVideoFile.value = file; |
| | | showVideoDialog.value = true; |
| | | } |
| | | }; |
| | | |
| | | const isImageFile = file => { |
| | | if (file?.contentType?.startsWith("image/")) return true; |
| | | const name = |
| | | file?.bucketFilename || file?.originalFilename || file?.name || ""; |
| | | const ext = name.split(".").pop()?.toLowerCase(); |
| | | return ["jpg", "jpeg", "png", "gif", "webp"].includes(ext); |
| | | }; |
| | | |
| | | const formatFileSize = size => { |
| | | if (!size) return ""; |
| | | if (size < 1024) return `${size}B`; |
| | | if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)}KB`; |
| | | return `${(size / 1024 / 1024).toFixed(1)}MB`; |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .exception-section { |
| | | margin-bottom: 16px; |
| | | padding: 12px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .section-title { |
| | | font-weight: 600; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .upload-buttons { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .file-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .file-item { |
| | | width: 120px; |
| | | padding: 8px; |
| | | border: 1px solid #e5e5e5; |
| | | border-radius: 10px; |
| | | background: #fff; |
| | | text-align: center; |
| | | } |
| | | |
| | | .file-preview-container { |
| | | position: relative; |
| | | } |
| | | |
| | | .file-preview { |
| | | width: 90px; |
| | | height: 90px; |
| | | object-fit: cover; |
| | | border-radius: 8px; |
| | | border: 1px solid #eee; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .video-preview { |
| | | width: 90px; |
| | | height: 90px; |
| | | margin: 0 auto; |
| | | border-radius: 8px; |
| | | background: #eef3f8; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: #409eff; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .delete-btn { |
| | | position: absolute; |
| | | top: -6px; |
| | | right: 6px; |
| | | width: 22px; |
| | | height: 22px; |
| | | border: none; |
| | | border-radius: 50%; |
| | | color: #fff; |
| | | background: #f56c6c; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .file-name { |
| | | margin-top: 6px; |
| | | font-size: 12px; |
| | | color: #333; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .file-size { |
| | | font-size: 11px; |
| | | color: #999; |
| | | } |
| | | |
| | | .upload-summary { |
| | | margin-top: 16px; |
| | | padding: 10px; |
| | | background: #f8f9fa; |
| | | border-left: 3px solid #409eff; |
| | | font-size: 13px; |
| | | color: #666; |
| | | } |
| | | |
| | | .video-player { |
| | | width: 100%; |
| | | max-height: 70vh; |
| | | background: #000; |
| | | } |
| | | </style> |