From 3381840f164396afbb2cc6ab395a5cb2854aa8a5 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 21 五月 2026 15:16:07 +0800
Subject: [PATCH] 进销存pro 1.web端设备巡检要求也可以上传图片

---
 src/views/equipmentManagement/inspectionManagement/index.vue                  |   27 +
 src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue |  680 +++++++++++++++++++++++++++++++++++++
 src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue   |  335 +++++++++++-------
 3 files changed, 901 insertions(+), 141 deletions(-)

diff --git a/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
new file mode 100644
index 0000000..36256c8
--- /dev/null
+++ b/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
@@ -0,0 +1,680 @@
+<template>
+  <FormDialog
+    v-model="dialogVisible"
+    title="涓婁紶宸℃璁板綍"
+    width="980px"
+    @close="handleClose"
+    @cancel="handleClose"
+  >
+    <main class="upload-content">
+      <el-card v-if="taskInfo" class="section-card">
+        <el-descriptions :column="1" border>
+          <el-descriptions-item label="宸℃浠诲姟鍚嶇О">
+            {{ taskInfo.taskName || "-" }}
+          </el-descriptions-item>
+          <el-descriptions-item label="宸℃椤圭洰">
+            {{ taskInfo.inspectionProject || "-" }}
+          </el-descriptions-item>
+          <el-descriptions-item label="澶囨敞">
+            {{ taskInfo.remarks || "-" }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </el-card>
+
+      <el-card class="section-card">
+        <h3>宸℃鐘舵��</h3>
+        <el-radio-group v-model="hasException">
+          <el-radio-button :value="false">姝e父</el-radio-button>
+          <el-radio-button :value="true">瀛樺湪寮傚父</el-radio-button>
+        </el-radio-group>
+      </el-card>
+
+      <el-card v-if="hasException === true" class="section-card">
+        <h3>寮傚父鎻忚堪</h3>
+        <el-input
+          v-model="abnormalDescription"
+          type="textarea"
+          maxlength="500"
+          show-word-limit
+          :rows="4"
+          placeholder="璇锋弿杩板紓甯告儏鍐�..."
+        />
+      </el-card>
+
+      <el-card v-if="hasException === true" class="section-card">
+        <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="upload-buttons">
+          <el-upload
+            :show-file-list="false"
+            :http-request="uploadFile"
+            :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading"
+            accept="image/*"
+          >
+            <el-button type="primary" :loading="uploading">
+              <el-icon><Camera /></el-icon>
+              閫夋嫨鍥剧墖
+            </el-button>
+          </el-upload>
+
+          <el-upload
+            :show-file-list="false"
+            :http-request="uploadFile"
+            :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading"
+            accept="video/*"
+          >
+            <el-button type="success" :loading="uploading">
+              <el-icon><VideoCamera /></el-icon>
+              閫夋嫨瑙嗛
+            </el-button>
+          </el-upload>
+        </div>
+
+        <el-progress
+          v-if="uploading"
+          :percentage="uploadProgress"
+          class="upload-progress"
+        />
+
+        <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">
+              <el-image
+                v-if="file.type === 'image' || !file.type"
+                :src="file.url || file.tempFilePath || file.path || file.downloadUrl"
+                fit="cover"
+                class="file-preview"
+                :preview-src-list="[file.url || file.tempFilePath || file.path || file.downloadUrl]"
+                preview-teleported
+              />
+
+              <div v-else class="video-preview" @click="previewVideo(file)">
+                <el-icon><VideoCamera /></el-icon>
+                <span>瑙嗛</span>
+              </div>
+
+              <el-button
+                class="delete-btn"
+                type="danger"
+                circle
+                size="small"
+                @click="removeFile(index)"
+              >
+                <el-icon><Close /></el-icon>
+              </el-button>
+            </div>
+
+            <div class="file-info">
+              <div class="file-name">
+                {{ file.bucketFilename || file.name || (file.type === "image" ? "鍥剧墖" : "瑙嗛") }}
+              </div>
+              <div class="file-size">{{ formatFileSize(file.size) }}</div>
+            </div>
+          </div>
+        </div>
+
+        <el-empty
+          v-else
+          :description="`璇烽�夋嫨瑕佷笂浼犵殑${getUploadTypeText()}鍥剧墖鎴栬棰慲"
+        />
+
+        <el-alert
+          class="upload-summary"
+          type="info"
+          :closable="false"
+          :title="`鐢熶骇鍓嶏細${beforeModelValue.length}涓枃浠� | 鐢熶骇涓細${afterModelValue.length}涓枃浠� | 鐢熶骇鍚庯細${issueModelValue.length}涓枃浠禶"
+        />
+      </el-card>
+
+      <el-result
+        v-if="hasException === false"
+        icon="success"
+        title="璁惧杩愯姝e父"
+        sub-title="鏃犻渶涓婁紶鐓х墖"
+      />
+    </main>
+
+    <template #footer>
+      <footer class="footer-buttons">
+        <el-button type="primary" @click="submitUpload">鎻愪氦</el-button>
+        <el-button v-if="hasException === true" type="warning" @click="goToRepair">
+          鏂板鎶ヤ慨
+        </el-button>
+        <el-button @click="handleClose">鍙栨秷</el-button>
+      </footer>
+    </template>
+  </FormDialog>
+
+  <el-dialog
+    v-model="showVideoDialog"
+    :title="currentVideoFile?.originalFilename || currentVideoFile?.name || '瑙嗛棰勮'"
+    width="720px"
+  >
+    <video
+      v-if="currentVideoFile"
+      :src="currentVideoFile.url || currentVideoFile.downloadUrl"
+      class="video-player"
+      controls
+      autoplay
+    />
+  </el-dialog>
+</template>
+
+<script setup>
+import { computed, ref } from "vue";
+import { useRouter } from "vue-router";
+import { ElLoading, ElMessage, ElMessageBox } from "element-plus";
+import { Camera, Close, VideoCamera } from "@element-plus/icons-vue";
+import axios from "axios";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { uploadInspectionTask } from "@/api/inspectionManagement/index.js";
+import { getToken } from "@/utils/auth";
+
+const emit = defineEmits(["closeDia", "success"]);
+const router = useRouter();
+
+const dialogVisible = ref(false);
+const taskInfo = ref(null);
+const uploading = ref(false);
+const uploadProgress = ref(0);
+
+const beforeModelValue = ref([]);
+const afterModelValue = ref([]);
+const issueModelValue = ref([]);
+
+const currentUploadType = ref("before");
+const hasException = ref(null);
+const abnormalDescription = ref("");
+
+const showVideoDialog = ref(false);
+const currentVideoFile = ref(null);
+
+const uploadConfig = {
+  action: "/common/upload",
+  limit: 10,
+  fileSize: 50,
+  fileType: ["jpg", "jpeg", "png", "mp4", "mov"],
+};
+
+const uploadFileUrl = computed(
+  () => `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}`
+);
+
+const processFileUrl = fileUrl => {
+  if (!fileUrl) return "";
+
+  let currentUrl = String(fileUrl);
+  if (currentUrl.includes("\\")) {
+    const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads");
+    if (uploadsIndex > -1) {
+      currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`;
+    } else {
+      const fileName = currentUrl.split("\\").pop();
+      currentUrl = `/uploads/${fileName}`;
+    }
+  }
+
+  if (currentUrl && !currentUrl.startsWith("http")) {
+    if (!currentUrl.startsWith("/")) {
+      currentUrl = `/${currentUrl}`;
+    }
+    currentUrl = __BASE_API__ + currentUrl;
+  }
+
+  return currentUrl;
+};
+
+const normalizeList = (list, fileType) => {
+  if (!Array.isArray(list)) return [];
+
+  return list.filter(Boolean).map(item => {
+    let currentType = item.type;
+    if (!currentType && item.contentType) {
+      currentType = item.contentType.startsWith("video") ? "video" : "image";
+    } else if (!currentType) {
+      currentType = fileType || "image";
+    }
+
+    return {
+      ...item,
+      url: processFileUrl(item.url || item.previewURL || item.downloadUrl || item.path || ""),
+      downloadUrl: processFileUrl(
+        item.downloadUrl || item.url || item.previewURL || item.path || ""
+      ),
+      name: item.name || item.originalFilename || item.bucketFilename,
+      tempId: item.tempId || item.id || item.tempFileId,
+      tempFileId: item.tempFileId || item.tempId || item.id,
+      size: item.size || item.byteSize || 0,
+      type: currentType,
+      status: "success",
+      uid: item.uid || `${Date.now()}-${Math.random()}`,
+    };
+  });
+};
+
+const resetState = () => {
+  taskInfo.value = null;
+  beforeModelValue.value = [];
+  afterModelValue.value = [];
+  issueModelValue.value = [];
+  currentUploadType.value = "before";
+  hasException.value = null;
+  abnormalDescription.value = "";
+  uploading.value = false;
+  uploadProgress.value = 0;
+  showVideoDialog.value = false;
+  currentVideoFile.value = null;
+};
+
+const openDialog = row => {
+  const raw = JSON.parse(JSON.stringify(row?.__raw || row || {}));
+  taskInfo.value = raw;
+
+  beforeModelValue.value = normalizeList(
+    raw.commonFileListBeforeVO || raw.commonFileListBefore || [],
+    "image"
+  );
+  afterModelValue.value = normalizeList(
+    raw.commonFileListVO || raw.commonFileList || [],
+    "image"
+  );
+  issueModelValue.value = normalizeList(
+    raw.commonFileListAfterVO || raw.commonFileListAfter || [],
+    "image"
+  );
+
+  abnormalDescription.value = raw.abnormalDescription || "";
+
+  if (raw.hasException !== undefined && raw.hasException !== null) {
+    hasException.value = raw.hasException;
+  } else if (raw.inspectionResult !== undefined && raw.inspectionResult !== null) {
+    hasException.value = String(raw.inspectionResult) === "0";
+  } else {
+    hasException.value = null;
+  }
+
+  if (
+    hasException.value !== true &&
+    (beforeModelValue.value.length || afterModelValue.value.length || issueModelValue.value.length)
+  ) {
+    hasException.value = true;
+  }
+
+  dialogVisible.value = true;
+};
+
+const handleClose = () => {
+  dialogVisible.value = false;
+  resetState();
+  emit("closeDia");
+};
+
+const getCurrentFiles = () => {
+  if (currentUploadType.value === "before") return beforeModelValue.value;
+  if (currentUploadType.value === "after") return afterModelValue.value;
+  if (currentUploadType.value === "issue") return issueModelValue.value;
+  return [];
+};
+
+const getUploadTypeText = () => {
+  if (currentUploadType.value === "before") return "鐢熶骇鍓�";
+  if (currentUploadType.value === "after") return "鐢熶骇涓�";
+  if (currentUploadType.value === "issue") return "鐢熶骇鍚�";
+  return "";
+};
+
+const getTabType = () => {
+  if (currentUploadType.value === "before") return 10;
+  if (currentUploadType.value === "after") return 11;
+  if (currentUploadType.value === "issue") return 12;
+  return 10;
+};
+
+const previewVideo = file => {
+  currentVideoFile.value = file;
+  showVideoDialog.value = true;
+};
+
+const uploadFile = async uploadRequest => {
+  const rawFile = uploadRequest.file;
+
+  if (getCurrentFiles().length >= uploadConfig.limit) {
+    ElMessage.warning(`鏈�澶氬彧鑳介�夋嫨${uploadConfig.limit}涓枃浠禶);
+    return;
+  }
+
+  const ext = rawFile.name.split(".").pop()?.toLowerCase();
+  if (!uploadConfig.fileType.includes(ext)) {
+    ElMessage.warning(`鏂囦欢鏍煎紡涓嶆敮鎸侊紝璇蜂笂浼� ${uploadConfig.fileType.join("/")} 鏍煎紡`);
+    return;
+  }
+
+  if (rawFile.size > uploadConfig.fileSize * 1024 * 1024) {
+    ElMessage.warning(`鏂囦欢澶у皬涓嶈兘瓒呰繃 ${uploadConfig.fileSize}MB`);
+    return;
+  }
+
+  const token = getToken();
+  if (!token) {
+    ElMessage.warning("鐢ㄦ埛鏈櫥褰�");
+    return;
+  }
+
+  const formData = new FormData();
+  formData.append("files", rawFile);
+  formData.append("type", getTabType());
+
+  uploading.value = true;
+  uploadProgress.value = 0;
+
+  try {
+    const { data } = await axios.post(uploadFileUrl.value, formData, {
+      headers: {
+        Authorization: `Bearer ${token}`,
+        "Content-Type": "multipart/form-data",
+      },
+      onUploadProgress: event => {
+        if (event.total) {
+          uploadProgress.value = Math.round((event.loaded / event.total) * 100);
+        }
+      },
+    });
+
+    if (data.code !== 200) {
+      ElMessage.error(data.msg || "涓婁紶澶辫触");
+      return;
+    }
+
+    const resultData = Array.isArray(data.data) ? data.data[0] : data.data;
+    const finalUrl = processFileUrl(
+      resultData.url || resultData.previewURL || resultData.downloadUrl || ""
+    );
+    const finalName = resultData.name || resultData.originalFilename || resultData.bucketFilename;
+    const finalId = resultData.tempId || resultData.id || resultData.tempFileId;
+
+    const uploadedFile = {
+      ...resultData,
+      url: finalUrl,
+      downloadUrl: finalUrl,
+      name: finalName,
+      tempId: finalId,
+      tempFileId: resultData.tempFileId || finalId,
+      size: rawFile.size || resultData.size || resultData.byteSize || 0,
+      type: rawFile.type?.startsWith("video") ? "video" : "image",
+      status: "success",
+      uid: `${Date.now()}-${Math.random()}`,
+    };
+
+    getCurrentFiles().push(uploadedFile);
+    ElMessage.success("涓婁紶鎴愬姛");
+  } catch (error) {
+    ElMessage.error(error?.message || "涓婁紶澶辫触");
+  } finally {
+    uploading.value = false;
+  }
+};
+
+const buildFileItem = item => ({
+  id: item?.id,
+  tempId: item?.tempId,
+  tempFileId: item?.tempFileId,
+  url: item?.downloadUrl || item?.url || "",
+  downloadUrl: item?.downloadUrl || item?.url || "",
+  name: item?.name,
+  bucketFilename: item?.bucketFilename || item?.name,
+  originalFilename: item?.originalFilename || item?.name,
+  size: item?.size || 0,
+  byteSize: item?.byteSize || item?.size || 0,
+  contentType: item?.contentType || "",
+  type: item?.type,
+});
+
+const submitUpload = async () => {
+  if (hasException.value === null) {
+    ElMessage.warning("璇烽�夋嫨宸℃鐘舵��");
+    return;
+  }
+
+  if (hasException.value === true) {
+    const totalFiles =
+      beforeModelValue.value.length +
+      afterModelValue.value.length +
+      issueModelValue.value.length;
+
+    if (!totalFiles) {
+      ElMessage.warning("璇蜂笂浼犲紓甯哥収鐗囨垨瑙嗛");
+      return;
+    }
+
+    if (!abnormalDescription.value.trim()) {
+      ElMessage.warning("璇峰~鍐欏紓甯告弿杩�");
+      return;
+    }
+  }
+
+  const loading = ElLoading.service({
+    text: "鎻愪氦涓�...",
+    background: "rgba(0, 0, 0, 0.3)",
+  });
+
+  try {
+    const allFiles = [
+      ...beforeModelValue.value,
+      ...afterModelValue.value,
+      ...issueModelValue.value,
+    ];
+
+    const tempFileIds = allFiles
+      .map(item => item?.tempId ?? item?.tempFileId ?? item?.id)
+      .filter(Boolean);
+
+    const {
+      createTime,
+      updateTime,
+      storageBlobDTO,
+      commonFileListAfterVO,
+      commonFileListVO,
+      commonFileListBeforeVO,
+      commonFileListAfter,
+      commonFileList,
+      commonFileListBefore,
+      __raw,
+      ...baseTaskInfo
+    } = taskInfo.value || {};
+
+    const submitData = {
+      ...baseTaskInfo,
+      commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem),
+      commonFileListDTO: afterModelValue.value.map(buildFileItem),
+      commonFileListAfterDTO: issueModelValue.value.map(buildFileItem),
+      hasException: hasException.value,
+      inspectionResult: hasException.value ? 0 : 1,
+      abnormalDescription: abnormalDescription.value,
+      tempFileIds,
+    };
+
+    const result = await uploadInspectionTask(submitData);
+
+    if (result && (result.code === 200 || result.success)) {
+      ElMessage.success("鎻愪氦鎴愬姛");
+      dialogVisible.value = false;
+      resetState();
+      emit("success");
+      emit("closeDia");
+    } else {
+      ElMessage.error(result?.msg || result?.message || "鎻愪氦澶辫触");
+    }
+  } catch (error) {
+    ElMessage.error(error?.message || "鎻愪氦澶辫触");
+  } finally {
+    loading.close();
+  }
+};
+
+const removeFile = async index => {
+  try {
+    await ElMessageBox.confirm("纭畾瑕佸垹闄よ繖涓枃浠跺悧锛�", "纭鍒犻櫎", {
+      type: "warning",
+    });
+    getCurrentFiles().splice(index, 1);
+  } catch {}
+};
+
+const goToRepair = () => {
+  const taskData = {
+    taskId: taskInfo.value?.taskId || taskInfo.value?.id,
+    taskName: taskInfo.value?.taskName,
+    inspectionLocation: taskInfo.value?.inspectionLocation,
+    inspector: taskInfo.value?.inspector,
+    hasException: hasException.value,
+    inspectionResult: hasException.value ? 0 : 1,
+    commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem),
+    commonFileListDTO: afterModelValue.value.map(buildFileItem),
+    commonFileListAfterDTO: issueModelValue.value.map(buildFileItem),
+    uploadedFiles: {
+      before: beforeModelValue.value,
+      after: afterModelValue.value,
+      issue: issueModelValue.value,
+    },
+  };
+
+  sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskData));
+  router.push("/equipmentManagement/repair/add");
+};
+
+const formatFileSize = size => {
+  if (!size) return "0 B";
+
+  const units = ["B", "KB", "MB", "GB"];
+  let index = 0;
+  let fileSize = size;
+
+  while (fileSize >= 1024 && index < units.length - 1) {
+    fileSize /= 1024;
+    index += 1;
+  }
+
+  return `${fileSize.toFixed(2)} ${units[index]}`;
+};
+
+defineExpose({
+  openDialog,
+});
+</script>
+
+<style scoped>
+.inspection-upload-page {
+  min-height: 70vh;
+  background: #f5f7fa;
+  padding: 20px 20px 90px;
+  box-sizing: border-box;
+}
+
+.upload-content {
+  max-width: 960px;
+  margin: 20px auto 0;
+}
+
+.section-card {
+  margin-bottom: 16px;
+}
+
+.section-card h3 {
+  margin: 0 0 16px;
+  font-size: 16px;
+}
+
+.upload-buttons {
+  display: flex;
+  gap: 12px;
+  margin: 16px 0;
+}
+
+.upload-progress {
+  margin-bottom: 16px;
+}
+
+.file-list {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+  gap: 12px;
+}
+
+.file-preview-container {
+  position: relative;
+  aspect-ratio: 1;
+  border-radius: 8px;
+  overflow: hidden;
+  background: #f2f3f5;
+}
+
+.file-preview {
+  width: 100%;
+  height: 100%;
+}
+
+.video-preview {
+  width: 100%;
+  height: 100%;
+  background: #303133;
+  color: #fff;
+  display: flex;
+  gap: 6px;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+}
+
+.delete-btn {
+  position: absolute;
+  top: 6px;
+  right: 6px;
+}
+
+.file-info {
+  margin-top: 6px;
+  font-size: 12px;
+}
+
+.file-name {
+  color: #606266;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.file-size {
+  color: #909399;
+  margin-top: 2px;
+}
+
+.upload-summary {
+  margin-top: 16px;
+}
+
+.footer-buttons {
+  position: sticky;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  padding: 14px 20px 0;
+  background: #f5f7fa;
+  display: flex;
+  justify-content: center;
+  gap: 12px;
+}
+
+.video-player {
+  width: 100%;
+  max-height: 70vh;
+  background: #000;
+}
+</style>
diff --git a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
index 66867e3..b5604fe 100644
--- a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
+++ b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -1,210 +1,226 @@
 <template>
   <div>
-    <el-dialog title="鏌ョ湅闄勪欢"
-               v-model="dialogVisitable" width="800px" @close="cancel">
+    <el-dialog title="鏌ョ湅闄勪欢" v-model="dialogVisitable" width="800px" @close="cancel">
       <div class="upload-container">
-        <!-- 鐢熶骇鍓� -->
         <div class="form-container">
           <div class="title">鐢熶骇鍓�</div>
-          
-          <!-- 鍥剧墖鍒楄〃 -->
-          <div style="display: flex; flex-wrap: wrap;">
-            <img v-for="(item, index) in beforeProductionImgs" :key="index"
-                 @click="showMedia(beforeProductionImgs, index, 'image')"
-                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+
+          <div class="media-list">
+            <img
+              v-for="(item, index) in beforeProductionImgs"
+              :key="`before-img-${index}`"
+              :src="item"
+              alt=""
+              class="media-image"
+              @click="showMedia(beforeProductionImgs, index, 'image')"
+            />
           </div>
-          
-          <!-- 瑙嗛鍒楄〃 -->
-          <div style="display: flex; flex-wrap: wrap;">
+
+          <div class="media-list">
             <div
-                v-for="(videoUrl, index) in beforeProductionVideos"
-                :key="index"
-                @click="showMedia(beforeProductionVideos, index, 'video')"
-                style="position: relative; margin: 10px; cursor: pointer;"
+              v-for="(videoUrl, index) in beforeProductionVideos"
+              :key="`before-video-${index}`"
+              class="video-item"
+              @click="showMedia(beforeProductionVideos, index, 'video')"
             >
-              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
-                <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+              <div class="video-thumb">
+                <img src="@/assets/images/video.png" alt="鎾斁" class="video-icon" />
               </div>
-              <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+              <div class="video-text">鐐瑰嚮鎾斁</div>
             </div>
           </div>
         </div>
-        
-        <!-- 鐢熶骇鍚� -->
+
+        <div class="form-container">
+          <div class="title">鐢熶骇涓�</div>
+
+          <div class="media-list">
+            <img
+              v-for="(item, index) in afterProductionImgs"
+              :key="`during-img-${index}`"
+              :src="item"
+              alt=""
+              class="media-image"
+              @click="showMedia(afterProductionImgs, index, 'image')"
+            />
+          </div>
+
+          <div class="media-list">
+            <div
+              v-for="(videoUrl, index) in afterProductionVideos"
+              :key="`during-video-${index}`"
+              class="video-item"
+              @click="showMedia(afterProductionVideos, index, 'video')"
+            >
+              <div class="video-thumb">
+                <img src="@/assets/images/video.png" alt="鎾斁" class="video-icon" />
+              </div>
+              <div class="video-text">鐐瑰嚮鎾斁</div>
+            </div>
+          </div>
+        </div>
+
         <div class="form-container">
           <div class="title">鐢熶骇鍚�</div>
-          
-          <!-- 鍥剧墖鍒楄〃 -->
-          <div style="display: flex; flex-wrap: wrap;">
-            <img v-for="(item, index) in afterProductionImgs" :key="index"
-                 @click="showMedia(afterProductionImgs, index, 'image')"
-                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+
+          <div class="media-list">
+            <img
+              v-for="(item, index) in productionIssuesImgs"
+              :key="`after-img-${index}`"
+              :src="item"
+              alt=""
+              class="media-image"
+              @click="showMedia(productionIssuesImgs, index, 'image')"
+            />
           </div>
-          
-          <!-- 瑙嗛鍒楄〃 -->
-          <div style="display: flex; flex-wrap: wrap;">
+
+          <div class="media-list">
             <div
-                v-for="(videoUrl, index) in afterProductionVideos"
-                :key="index"
-                @click="showMedia(afterProductionVideos, index, 'video')"
-                style="position: relative; margin: 10px; cursor: pointer;"
+              v-for="(videoUrl, index) in productionIssuesVideos"
+              :key="`after-video-${index}`"
+              class="video-item"
+              @click="showMedia(productionIssuesVideos, index, 'video')"
             >
-              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
-                <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+              <div class="video-thumb">
+                <img src="@/assets/images/video.png" alt="鎾斁" class="video-icon" />
               </div>
-              <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
-            </div>
-          </div>
-        </div>
-        
-        <!-- 鐢熶骇闂 -->
-        <div class="form-container">
-          <div class="title">鐢熶骇闂</div>
-          
-          <!-- 鍥剧墖鍒楄〃 -->
-          <div style="display: flex; flex-wrap: wrap;">
-            <img v-for="(item, index) in productionIssuesImgs" :key="index"
-                 @click="showMedia(productionIssuesImgs, index, 'image')"
-                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
-          </div>
-          
-          <!-- 瑙嗛鍒楄〃 -->
-          <div style="display: flex; flex-wrap: wrap;">
-            <div
-                v-for="(videoUrl, index) in productionIssuesVideos"
-                :key="index"
-                @click="showMedia(productionIssuesVideos, index, 'video')"
-                style="position: relative; margin: 10px; cursor: pointer;"
-            >
-              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
-                <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
-              </div>
-              <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+              <div class="video-text">鐐瑰嚮鎾斁</div>
             </div>
           </div>
         </div>
       </div>
     </el-dialog>
-    
-    <!-- 缁熶竴濯掍綋鏌ョ湅鍣� -->
+
     <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
       <div class="media-viewer-content" @click.stop>
-        <!-- 鍥剧墖 -->
         <vue-easy-lightbox
-            v-if="mediaType === 'image'"
-            :visible="isMediaViewerVisible"
-            :imgs="mediaList"
-            :index="currentMediaIndex"
-            @hide="closeMediaViewer"
-        ></vue-easy-lightbox>
-        
-        <!-- 瑙嗛 -->
-        <div v-else-if="mediaType === 'video'" style="position: relative;">
-          <video
-              :src="mediaList[currentMediaIndex]"
-              autoplay
-              controls
-              style="max-width: 90vw; max-height: 80vh;"
-          />
+          v-if="mediaType === 'image'"
+          :visible="isMediaViewerVisible"
+          :imgs="mediaList"
+          :index="currentMediaIndex"
+          @hide="closeMediaViewer"
+        />
+
+        <div v-else-if="mediaType === 'video'" class="video-player-wrap">
+          <video :src="mediaList[currentMediaIndex]" autoplay controls class="video-player" />
         </div>
       </div>
     </div>
   </div>
 </template>
-<script setup>
-import { ref } from 'vue';
-import VueEasyLightbox from 'vue-easy-lightbox';
-const { proxy } = getCurrentInstance();
 
-// 鎺у埗寮圭獥鏄剧ず
+<script setup>
+import { ref } from "vue";
+import VueEasyLightbox from "vue-easy-lightbox";
+
 const dialogVisitable = ref(false);
 
-// 鍥剧墖鏁扮粍
 const beforeProductionImgs = ref([]);
 const afterProductionImgs = ref([]);
 const productionIssuesImgs = ref([]);
 
-// 瑙嗛鏁扮粍
 const beforeProductionVideos = ref([]);
 const afterProductionVideos = ref([]);
 const productionIssuesVideos = ref([]);
 
-// 濯掍綋鏌ョ湅鍣ㄧ姸鎬�
 const isMediaViewerVisible = ref(false);
 const currentMediaIndex = ref(0);
-const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
-const mediaType = ref('image'); // image | video
+const mediaList = ref([]);
+const mediaType = ref("image");
 
-// 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
-function processItems(items) {
+const processFileUrl = fileUrl => {
+  if (!fileUrl) return "";
+
+  let currentUrl = String(fileUrl);
+  if (currentUrl.includes("\\")) {
+    const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads");
+    if (uploadsIndex > -1) {
+      currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`;
+    } else {
+      const fileName = currentUrl.split("\\").pop();
+      currentUrl = `/uploads/${fileName}`;
+    }
+  }
+
+  if (currentUrl && !currentUrl.startsWith("http")) {
+    if (!currentUrl.startsWith("/")) {
+      currentUrl = `/${currentUrl}`;
+    }
+    currentUrl = __BASE_API__ + currentUrl;
+  }
+
+  return currentUrl;
+};
+
+const processItems = items => {
   const images = [];
   const videos = [];
-  
-  // 妫�鏌� items 鏄惁瀛樺湪涓斾负鏁扮粍
-  if (!items || !Array.isArray(items)) {
+
+  if (!Array.isArray(items)) {
     return { images, videos };
   }
-  
+
   items.forEach(item => {
-    if (!item || !item.previewURL || !item.contentType) return;
+    if (!item) return;
 
-    
-    // 澶勭悊鏂囦欢 URL
-    const fileUrl = item.previewURL;
-    const contentType = String(item.contentType).toLowerCase();
+    const fileUrl = processFileUrl(
+      item.previewURL || item.url || item.downloadUrl || item.path || ""
+    );
+    const contentType = String(item.contentType || "").toLowerCase();
 
-    // 鏍规嵁 contentType 鍒ゆ柇鏄浘鐗囪繕鏄棰�
-    if (contentType.startsWith('image/')) {
-      images.push(fileUrl);
-    } else if (contentType.startsWith('video/')) {
+    if (!fileUrl) return;
+
+    if (contentType.startsWith("video/")) {
       videos.push(fileUrl);
+      return;
     }
-  });
-  
-  return { images, videos };
-}
 
-// 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
-const openDialog = async (row) => {
-  // 浣跨敤姝g‘鐨勫瓧娈靛悕锛歝ommonFileListBefore, commonFileListAfter
-  const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBeforeVO || []);
-  const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfterVO || []);
-  const { images: issueImgs, videos: issueVids } = processItems(row.commonFileListVO || []);
-  
+    images.push(fileUrl);
+  });
+
+  return { images, videos };
+};
+
+const openDialog = row => {
+  const { images: beforeImgs, videos: beforeVids } = processItems(
+    row.commonFileListBeforeVO || []
+  );
+  const { images: afterImgs, videos: afterVids } = processItems(
+    row.commonFileListVO || []
+  );
+  const { images: issueImgs, videos: issueVids } = processItems(
+    row.commonFileListAfterVO || []
+  );
+
   beforeProductionImgs.value = beforeImgs;
   beforeProductionVideos.value = beforeVids;
-  
   afterProductionImgs.value = afterImgs;
   afterProductionVideos.value = afterVids;
-  
   productionIssuesImgs.value = issueImgs;
   productionIssuesVideos.value = issueVids;
-  
   dialogVisitable.value = true;
 };
 
-// 鏄剧ず濯掍綋锛堝浘鐗� or 瑙嗛锛�
-function showMedia(mediaArray, index, type) {
-  mediaList.value = mediaArray;
+const showMedia = (items, index, type) => {
+  mediaList.value = items;
   currentMediaIndex.value = index;
   mediaType.value = type;
   isMediaViewerVisible.value = true;
-}
+};
 
-// 鍏抽棴濯掍綋鏌ョ湅鍣�
-function closeMediaViewer() {
+const closeMediaViewer = () => {
   isMediaViewerVisible.value = false;
   mediaList.value = [];
-  mediaType.value = 'image';
-}
+  mediaType.value = "image";
+};
 
-// 琛ㄥ崟鍏抽棴鏂规硶
 const cancel = () => {
   dialogVisitable.value = false;
 };
 
 defineExpose({ openDialog });
 </script>
+
 <style scoped lang="scss">
 .upload-container {
   display: flex;
@@ -213,7 +229,7 @@
   padding: 20px;
   border: 1px solid #dcdfe6;
   box-sizing: border-box;
-  
+
   .form-container {
     flex: 1;
     width: 100%;
@@ -229,7 +245,7 @@
   padding-left: 10px;
   position: relative;
   margin: 6px 0;
-  
+
   &::before {
     content: "";
     position: absolute;
@@ -241,12 +257,48 @@
   }
 }
 
+.media-list {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.media-image {
+  max-width: 100px;
+  height: 100px;
+  margin: 5px;
+  cursor: pointer;
+}
+
+.video-item {
+  position: relative;
+  margin: 10px;
+  cursor: pointer;
+}
+
+.video-thumb {
+  width: 160px;
+  height: 90px;
+  background-color: #333;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.video-icon {
+  width: 30px;
+  height: 30px;
+  opacity: 0.8;
+}
+
+.video-text {
+  text-align: center;
+  font-size: 12px;
+  color: #666;
+}
+
 .media-viewer-overlay {
   position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
+  inset: 0;
   background-color: rgba(0, 0, 0, 0.8);
   z-index: 9999;
   display: flex;
@@ -260,4 +312,13 @@
   max-height: 90vh;
   overflow: hidden;
 }
-</style>
\ No newline at end of file
+
+.video-player-wrap {
+  position: relative;
+}
+
+.video-player {
+  max-width: 90vw;
+  max-height: 80vh;
+}
+</style>
diff --git a/src/views/equipmentManagement/inspectionManagement/index.vue b/src/views/equipmentManagement/inspectionManagement/index.vue
index 809fd4f..fa531f1 100644
--- a/src/views/equipmentManagement/inspectionManagement/index.vue
+++ b/src/views/equipmentManagement/inspectionManagement/index.vue
@@ -82,6 +82,9 @@
     <form-dia ref="formDia"
               @closeDia="handleQuery"></form-dia>
     <view-files ref="viewFiles"></view-files>
+    <upload-files ref="uploadFiles"
+                  @success="handleQuery"
+                  @closeDia="handleQuery"></upload-files>
   </div>
 </template>
 
@@ -93,6 +96,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";
 
   // 鎺ュ彛寮曞叆
@@ -106,6 +110,7 @@
   const { proxy } = getCurrentInstance();
   const formDia = ref();
   const viewFiles = ref();
+  const uploadFiles = ref();
 
   // 鏌ヨ鍙傛暟
   const queryParams = reactive({
@@ -211,8 +216,9 @@
 
     const operationConfig = {
       label: "鎿嶄綔",
-      width: 130,
+      width: operations.length > 1 ? 180 : 130,
       fixed: "right",
+			align: 'center',
       dataType: "action",
       operation: operations
         .map(op => {
@@ -221,6 +227,12 @@
               return {
                 name: "缂栬緫",
                 clickFun: handleAdd,
+                color: "#409EFF",
+              };
+            case "upload":
+              return {
+                name: "涓婁紶",
+                clickFun: openUploadDialog,
                 color: "#409EFF",
               };
             case "viewFile":
@@ -253,14 +265,14 @@
       ];
       operationsArr.value = ["edit"];
     } else if (value === "task") {
-      const operationColumn = getOperationColumn(["viewFile"]);
+      const operationColumn = getOperationColumn(["upload", "viewFile"]);
       // 瀹氭椂浠诲姟璁板綍涓嶅睍绀�"鏄惁鍚敤"鍒�
       const taskColumns = columns.value.filter(col => col.prop !== "isEnabled");
       tableColumns.value = [
         ...taskColumns,
         ...(operationColumn ? [operationColumn] : []),
       ];
-      operationsArr.value = ["viewFile"];
+      operationsArr.value = ["upload", "viewFile"];
     }
     pageNum.value = 1;
     pageSize.value = 10;
@@ -302,6 +314,7 @@
         // 澶勭悊 inspector 瀛楁锛屽皢瀛楃涓茶浆鎹负鏁扮粍锛堥�傜敤浜庢墍鏈夋儏鍐碉級
         tableData.value = rawData.map(item => {
           const processedItem = { ...item };
+          processedItem.__raw = { ...item };
 
           // 澶勭悊 inspector 瀛楁
           if (processedItem.inspector) {
@@ -351,6 +364,12 @@
   const viewFile = row => {
     nextTick(() => {
       viewFiles.value?.openDialog(row);
+    });
+  };
+
+  const openUploadDialog = row => {
+    nextTick(() => {
+      uploadFiles.value?.openDialog(row);
     });
   };
 
@@ -419,4 +438,4 @@
     color: #909399;
     font-size: 14px;
   }
-</style>
\ No newline at end of file
+</style>

--
Gitblit v1.9.3