From ae7277fa376166ef2bd66253dd16337a35229d10 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 21 五月 2026 10:11:32 +0800
Subject: [PATCH] 阳光彩印 1.web端设备巡检要求也可以上传图片

---
 multiple/config.json                                                          |    2 
 vite.config.js                                                                |    4 
 src/views/equipmentManagement/inspectionManagement/index.vue                  |   27 ++
 src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue |  580 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 606 insertions(+), 7 deletions(-)

diff --git a/multiple/config.json b/multiple/config.json
index 04c80e8..5044486 100644
--- a/multiple/config.json
+++ b/multiple/config.json
@@ -20,7 +20,7 @@
   "YGYS": {
     "env": {
       "VITE_APP_TITLE": "闃冲厜鍗板埛淇℃伅绠$悊",
-      "VITE_BASE_API": "http://1.15.17.182:9022",
+      "VITE_BASE_API": "http://1.15.17.182:9023",
       "VITE_JAVA_API": "http://1.15.17.182:9022"
     },
     "screen": "screen/login-background.png",
diff --git a/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
new file mode 100644
index 0000000..a907b61
--- /dev/null
+++ b/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
@@ -0,0 +1,580 @@
+<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">姝e父</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>
diff --git a/src/views/equipmentManagement/inspectionManagement/index.vue b/src/views/equipmentManagement/inspectionManagement/index.vue
index 35f82d5..d80d29a 100644
--- a/src/views/equipmentManagement/inspectionManagement/index.vue
+++ b/src/views/equipmentManagement/inspectionManagement/index.vue
@@ -76,6 +76,8 @@
     <form-dia ref="formDia"
               @closeDia="handleQuery"></form-dia>
     <view-files ref="viewFiles"></view-files>
+    <upload-files ref="uploadFiles"
+                  @closeDia="handleQuery"></upload-files>
   </div>
 </template>
 
@@ -87,6 +89,7 @@
   // 缁勪欢寮曞叆
   import PIMTable from "@/components/PIMTable/PIMTable.vue";
   import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue";
+  import UploadFiles from "@/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue";
   import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue";
 
   // 鎺ュ彛寮曞叆
@@ -100,6 +103,7 @@
   const { proxy } = getCurrentInstance();
   const formDia = ref();
   const viewFiles = ref();
+  const uploadFiles = ref();
 
   // 鏌ヨ鍙傛暟
   const queryParams = reactive({
@@ -184,8 +188,9 @@
 
     const operationConfig = {
       label: "鎿嶄綔",
-      width: 130,
+      width: operations.length > 1 ? 180 : 130,
       fixed: "right",
+			align: 'center',
       dataType: "action",
       operation: operations
         .map(op => {
@@ -194,6 +199,12 @@
               return {
                 name: "缂栬緫",
                 clickFun: handleAdd,
+                color: "#409EFF",
+              };
+            case "upload":
+              return {
+                name: "涓婁紶",
+                clickFun: openUploadFiles,
                 color: "#409EFF",
               };
             case "viewFile":
@@ -226,12 +237,12 @@
       ];
       operationsArr.value = ["edit"];
     } else if (value === "task") {
-      const operationColumn = getOperationColumn(["viewFile"]);
+      const operationColumn = getOperationColumn(["upload", "viewFile"]);
       tableColumns.value = [
         ...columns.value,
         ...(operationColumn ? [operationColumn] : []),
       ];
-      operationsArr.value = ["viewFile"];
+      operationsArr.value = ["upload", "viewFile"];
     }
     pageNum.value = 1;
     pageSize.value = 10;
@@ -273,6 +284,7 @@
         // 澶勭悊 inspector 瀛楁锛屽皢瀛楃涓茶浆鎹负鏁扮粍锛堥�傜敤浜庢墍鏈夋儏鍐碉級
         tableData.value = rawData.map(item => {
           const processedItem = { ...item };
+          processedItem.__raw = { ...item };
 
           // 澶勭悊 inspector 瀛楁
           if (processedItem.inspector) {
@@ -322,6 +334,13 @@
   const viewFile = row => {
     nextTick(() => {
       viewFiles.value?.openDialog(row);
+    });
+  };
+
+  // 涓婁紶闄勪欢
+  const openUploadFiles = row => {
+    nextTick(() => {
+      uploadFiles.value?.openDialog(row);
     });
   };
 
@@ -390,4 +409,4 @@
     color: #909399;
     font-size: 14px;
   }
-</style>
\ No newline at end of file
+</style>
diff --git a/vite.config.js b/vite.config.js
index af1ed35..4e392ae 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -8,11 +8,11 @@
   const { VITE_APP_ENV } = env;
   const baseUrl =
       env.VITE_APP_ENV === "development"
-          ? "http://1.15.17.182:9023"
+          ? "http://1.15.17.182:9038"
           : env.VITE_BASE_API;
   const javaUrl =
       env.VITE_APP_ENV === "development"
-          ? "http://1.15.17.182:9023"
+          ? "http://1.15.17.182:9039"
           : env.VITE_JAVA_API;
   return {
     define:{

--
Gitblit v1.9.3