yuan
8 天以前 97586104d7808d8ea8fb5f3e5d4c60e2a7943667
Merge remote-tracking branch 'origin/dev_pro_河南鹤壁' into dev_pro_河南鹤壁
已修改16个文件
1979 ■■■■■ 文件已修改
src/views/customerService/afterSalesHandling/components/formDia.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/feedbackRegistration/components/formDia.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/feedbackRegistration/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue 1194 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue 417 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/formDia.vue 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/index.vue 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/detail.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/formDia.vue 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/index.vue 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/afterSalesHandling/components/formDia.vue
@@ -82,6 +82,33 @@
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="评分:" prop="rating">
                            <el-rate
                                v-model="form.rating"
                                :disabled="operationType === 'view'"
                                :max="5"
                                show-text
                                :texts="['非常不满意', '不满意', '一般', '满意', '非常满意']"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="24">
                        <el-form-item label="评价内容描述:" prop="evaluation">
                            <el-input
                                v-model="form.evaluation"
                                placeholder="请输入评价内容描述"
                                clearable
                                :disabled="operationType === 'view'"
                                type="textarea"
                                :rows="3"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <el-row :gutter="30">
                <el-col :span="12">
@@ -148,6 +175,8 @@
        disposeUserId: "",
        disDate: "",
        disRes: "",
        rating: null,
        evaluation: "",
    },
    rules: {
        feedbackDate: [{required: true, message: "请选择", trigger: "change"}],
src/views/customerService/feedbackRegistration/components/formDia.vue
@@ -326,7 +326,7 @@
      form.value.customerId = null;
    }
    getSalesLedger({
      customerName: form.value.customerName,
      customerId: form.value.customerId,
    }).then(res => {
      if (res.code === 200) {
        associatedSalesOrderNumberOptions.value = res.data.records.map(item => ({
@@ -434,7 +434,7 @@
      form.value = { ...row };
      if (form.value.customerName) {
        const res = await getSalesLedger({
          customerName: form.value.customerName,
          customerId: form.value.customerId,
        });
        if (res?.code === 200) {
          console.log(res);
src/views/customerService/feedbackRegistration/index.vue
@@ -149,14 +149,14 @@
    {
      icon: markRaw(FolderOpened),
      count: 0,
      label: "已处理",
      label: "待处理",
      color: "#ff9a2e",
      bgColor: "#fff5e6",
    },
    {
      icon: markRaw(UserFilled),
      count: 0,
      label: "已完成",
      label: "已处理",
      color: "#00b42a",
      bgColor: "#e6f7ed",
    },
@@ -424,8 +424,8 @@
      if (res.code === 200) {
        const statsData = Array.isArray(res.data) ? res.data : [];
        statsList.value[0].count = getStatsCountByStatus(statsData, 3);
        statsList.value[1].count = getStatsCountByStatus(statsData, 2);
        statsList.value[2].count = getStatsCountByStatus(statsData, 1);
        statsList.value[1].count = getStatsCountByStatus(statsData, 1);
        statsList.value[2].count = getStatsCountByStatus(statsData, 2);
      }
    });
  };
src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
@@ -1,117 +1,131 @@
<template>
  <FormDialog
    v-model="dialogVisible"
    title="上传巡检记录"
    width="980px"
    @close="handleClose"
    @cancel="handleClose"
  >
  <FormDialog v-model="dialogVisible"
              :title="operationType === 'view' ? '巡检记录详情' : '上传巡检记录'"
              width="980px"
              :operation-type="operationType"
              @close="handleClose"
              @cancel="handleClose">
    <main class="upload-content">
      <el-card v-if="taskInfo" class="section-card">
        <el-descriptions :column="1" border>
      <el-card v-if="taskInfo"
               class="section-card">
        <el-descriptions :column="2"
                         border>
          <el-descriptions-item label="巡检任务名称">
            {{ taskInfo.taskName || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="巡检项目">
            {{ taskInfo.inspectionProject || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="执行巡检人">
            <template v-if="formattedInspector && formattedInspector.length">
              <el-tag v-for="tag in formattedInspector"
                      :key="tag"
                      size="small"
                      style="margin-right: 4px">
                {{ tag }}
              </el-tag>
            </template>
            <span v-else>--</span>
          </el-descriptions-item>
          <el-descriptions-item label="频次">
            {{ formatFrequencyType(taskInfo.frequencyType) || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="开始日期与时间">
            {{ formatFrequencyDetail(taskInfo.frequencyDetail) || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="登记人">
            {{ taskInfo.registrant || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="登记日期">
            {{ formatDateTime(taskInfo.createTime) || "-" }}
          </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-group v-model="hasException"
                        :disabled="operationType === 'view'">
          <el-radio-button :value="false">正常</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">
      <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-input v-model="abnormalDescription"
                  type="textarea"
                  maxlength="500"
                  show-word-limit
                  :rows="4"
                  :disabled="operationType === 'view'"
                  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-card v-if="hasException === true"
               class="section-card">
        <div class="upload-buttons"
             v-if="operationType !== 'view'">
          <el-upload :show-file-list="false"
                     :http-request="uploadFile"
                     :disabled="beforeModelValue.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-upload :show-file-list="false"
                     :http-request="uploadFile"
                     :disabled="beforeModelValue.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"
          >
        <el-progress v-if="uploading"
                     :percentage="uploadProgress"
                     class="upload-progress" />
        <div v-if="beforeModelValue.length"
             class="file-list">
          <div v-for="(file, index) in beforeModelValue"
               :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>
              <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 class="delete-btn"
                         type="danger"
                         circle
                         size="small"
                         v-if="operationType !== 'view'"
                         @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" ? "图片" : "视频") }}
@@ -120,561 +134,559 @@
            </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-empty v-else
                  :description="operationType === 'view' ? '暂无异常照片或视频' : '请选择要上传的巡检图片或视频'" />
      </el-card>
      <el-result
        v-if="hasException === false"
        icon="success"
        title="设备运行正常"
        sub-title="无需上传照片"
      />
      <el-result v-if="hasException === false"
                 icon="success"
                 title="设备运行正常"
                 :sub-title="operationType === 'view' ? '' : '无需上传照片'" />
    </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 type="primary"
                   v-if="operationType !== 'view'"
                   @click="submitUpload">提交</el-button>
        <el-button v-if="hasException === true && operationType !== 'view'"
                   type="warning"
                   @click="goToRepair">
          新增报修
        </el-button>
        <el-button @click="handleClose">取消</el-button>
        <el-button @click="handleClose">{{ operationType === 'view' ? '关闭' : '取消' }}</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 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";
  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 dayjs from "dayjs";
  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 emit = defineEmits(["closeDia", "success"]);
  const router = useRouter();
const dialogVisible = ref(false);
const taskInfo = ref(null);
const uploading = ref(false);
const uploadProgress = ref(0);
  const dialogVisible = ref(false);
  const taskInfo = ref(null);
  const uploading = ref(false);
  const uploadProgress = ref(0);
  const operationType = ref("add"); // add, view
const beforeModelValue = ref([]);
const afterModelValue = ref([]);
const issueModelValue = ref([]);
  const beforeModelValue = 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}`;
  const formattedInspector = computed(() => {
    if (!taskInfo.value?.inspector) return [];
    if (Array.isArray(taskInfo.value.inspector)) return taskInfo.value.inspector;
    if (typeof taskInfo.value.inspector === "string") {
      return taskInfo.value.inspector
        .split(",")
        .map(s => s.trim())
        .filter(s => s);
    }
  }
  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)",
    return [taskInfo.value.inspector];
  });
  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 formatFrequencyType = type => {
    const mapping = {
      DAILY: "每日",
      WEEKLY: "每周",
      MONTHLY: "每月",
      QUARTERLY: "季度",
    };
    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,
    },
    return mapping[type] || type;
  };
  sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskData));
  router.push("/equipmentManagement/repair/add");
};
  const formatFrequencyDetail = detail => {
    if (typeof detail !== "string") return detail;
    const replacements = {
      MON: "周一",
      TUE: "周二",
      WED: "周三",
      THU: "周四",
      FRI: "周五",
      SAT: "周六",
      SUN: "周日",
    };
    return detail.replace(
      /MON|TUE|WED|THU|FRI|SAT|SUN/g,
      match => replacements[match]
    );
  };
const formatFileSize = size => {
  if (!size) return "0 B";
  const formatDateTime = date => {
    if (!date) return "-";
    return dayjs(date).format("YYYY-MM-DD HH:mm:ss");
  };
  const units = ["B", "KB", "MB", "GB"];
  let index = 0;
  let fileSize = size;
  const hasException = ref(null);
  const abnormalDescription = ref("");
  while (fileSize >= 1024 && index < units.length - 1) {
    fileSize /= 1024;
    index += 1;
  }
  const showVideoDialog = ref(false);
  const currentVideoFile = ref(null);
  return `${fileSize.toFixed(2)} ${units[index]}`;
};
  const uploadConfig = {
    action: "/common/upload",
    limit: 10,
    fileSize: 50,
    fileType: ["jpg", "jpeg", "png", "mp4", "mov"],
  };
defineExpose({
  openDialog,
});
  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 = [];
    hasException.value = null;
    abnormalDescription.value = "";
    uploading.value = false;
    uploadProgress.value = 0;
    showVideoDialog.value = false;
    currentVideoFile.value = null;
    operationType.value = "add";
  };
  const openDialog = (type, row) => {
    operationType.value = type || "add";
    const raw = JSON.parse(JSON.stringify(row?.__raw || row || {}));
    taskInfo.value = raw;
    beforeModelValue.value = normalizeList(
      raw.commonFileListBeforeVO || raw.commonFileListBefore || [],
      "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) {
      hasException.value = true;
    }
    dialogVisible.value = true;
  };
  const handleClose = () => {
    dialogVisible.value = false;
    resetState();
    emit("closeDia");
  };
  const previewVideo = file => {
    currentVideoFile.value = file;
    showVideoDialog.value = true;
  };
  const uploadFile = async uploadRequest => {
    const rawFile = uploadRequest.file;
    if (beforeModelValue.value.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", 10); // 生产前固定为10
    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()}`,
      };
      beforeModelValue.value.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;
      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];
      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: [],
        commonFileListAfterDTO: [],
        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",
      });
      beforeModelValue.value.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: [],
      commonFileListAfterDTO: [],
      uploadedFiles: {
        before: beforeModelValue.value,
        after: [],
        issue: [],
      },
    };
    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;
}
  .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;
}
  .upload-content {
    max-width: 960px;
    margin: 20px auto 0;
  }
.section-card {
  margin-bottom: 16px;
}
  .section-card {
    margin-bottom: 16px;
  }
.section-card h3 {
  margin: 0 0 16px;
  font-size: 16px;
}
  .section-card h3 {
    margin: 0 0 16px;
    font-size: 16px;
  }
.upload-buttons {
  display: flex;
  gap: 12px;
  margin: 16px 0;
}
  .upload-buttons {
    display: flex;
    gap: 12px;
    margin: 16px 0;
  }
.upload-progress {
  margin-bottom: 16px;
}
  .upload-progress {
    margin-bottom: 16px;
  }
.file-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px;
}
  .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-container {
    position: relative;
    aspect-ratio: 1;
    border-radius: 8px;
    overflow: hidden;
    background: #f2f3f5;
  }
.file-preview {
  width: 100%;
  height: 100%;
}
  .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;
}
  .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;
}
  .delete-btn {
    position: absolute;
    top: 6px;
    right: 6px;
  }
.file-info {
  margin-top: 6px;
  font-size: 12px;
}
  .file-info {
    margin-top: 6px;
    font-size: 12px;
  }
.file-name {
  color: #606266;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
  .file-name {
    color: #606266;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
.file-size {
  color: #909399;
  margin-top: 2px;
}
  .file-size {
    color: #909399;
    margin-top: 2px;
  }
.upload-summary {
  margin-top: 16px;
}
  .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;
}
  .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;
}
  .video-player {
    width: 100%;
    max-height: 70vh;
    background: #000;
  }
</style>
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -1,37 +1,35 @@
<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 class="title">图片列表</div>
          <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')"
            />
            <img v-for="(item, index) in beforeProductionImgs"
                 :key="`before-img-${index}`"
                 :src="item"
                 alt=""
                 class="media-image"
                 @click="showMedia(beforeProductionImgs, index, 'image')" />
          </div>
          <div class="media-list">
            <div
              v-for="(videoUrl, index) in beforeProductionVideos"
              :key="`before-video-${index}`"
              class="video-item"
              @click="showMedia(beforeProductionVideos, index, 'video')"
            >
            <div v-for="(videoUrl, index) in beforeProductionVideos"
                 :key="`before-video-${index}`"
                 class="video-item"
                 @click="showMedia(beforeProductionVideos, index, 'video')">
              <div class="video-thumb">
                <img src="@/assets/images/video.png" alt="播放" class="video-icon" />
                <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="form-container">
          <div class="title">生产中</div>
          <div class="media-list">
@@ -87,22 +85,25 @@
              <div class="video-text">点击播放</div>
            </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"
        />
        <div v-else-if="mediaType === 'video'" class="video-player-wrap">
          <video :src="mediaList[currentMediaIndex]" autoplay controls class="video-player" />
    <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" />
        <div v-else-if="mediaType === 'video'"
             class="video-player-wrap">
          <video :src="mediaList[currentMediaIndex]"
                 autoplay
                 controls
                 class="video-player" />
        </div>
      </div>
    </div>
@@ -110,215 +111,215 @@
</template>
<script setup>
import { ref } from "vue";
import VueEasyLightbox from "vue-easy-lightbox";
  import { ref } from "vue";
  import VueEasyLightbox from "vue-easy-lightbox";
const dialogVisitable = ref(false);
  const dialogVisitable = ref(false);
const beforeProductionImgs = ref([]);
const afterProductionImgs = ref([]);
const productionIssuesImgs = ref([]);
  const beforeProductionImgs = ref([]);
  const afterProductionImgs = ref([]);
  const productionIssuesImgs = ref([]);
const beforeProductionVideos = ref([]);
const afterProductionVideos = ref([]);
const productionIssuesVideos = 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");
  const isMediaViewerVisible = ref(false);
  const currentMediaIndex = ref(0);
  const mediaList = ref([]);
  const mediaType = ref("image");
const processFileUrl = fileUrl => {
  if (!fileUrl) return "";
  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}`;
    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}`;
    if (currentUrl && !currentUrl.startsWith("http")) {
      if (!currentUrl.startsWith("/")) {
        currentUrl = `/${currentUrl}`;
      }
      currentUrl = __BASE_API__ + currentUrl;
    }
    currentUrl = __BASE_API__ + currentUrl;
  }
  return currentUrl;
};
    return currentUrl;
  };
const processItems = items => {
  const images = [];
  const videos = [];
  const processItems = items => {
    const images = [];
    const videos = [];
  if (!Array.isArray(items)) {
    if (!Array.isArray(items)) {
      return { images, videos };
    }
    items.forEach(item => {
      if (!item) return;
      const fileUrl = processFileUrl(
        item.previewURL || item.url || item.downloadUrl || item.path || ""
      );
      const contentType = String(item.contentType || "").toLowerCase();
      if (!fileUrl) return;
      if (contentType.startsWith("video/")) {
        videos.push(fileUrl);
        return;
      }
      images.push(fileUrl);
    });
    return { images, videos };
  }
  };
  items.forEach(item => {
    if (!item) return;
    const fileUrl = processFileUrl(
      item.previewURL || item.url || item.downloadUrl || item.path || ""
  const openDialog = row => {
    const { images: beforeImgs, videos: beforeVids } = processItems(
      row.commonFileListBeforeVO || []
    );
    const contentType = String(item.contentType || "").toLowerCase();
    const { images: afterImgs, videos: afterVids } = processItems(
      row.commonFileListVO || []
    );
    const { images: issueImgs, videos: issueVids } = processItems(
      row.commonFileListAfterVO || []
    );
    if (!fileUrl) return;
    beforeProductionImgs.value = beforeImgs;
    beforeProductionVideos.value = beforeVids;
    afterProductionImgs.value = afterImgs;
    afterProductionVideos.value = afterVids;
    productionIssuesImgs.value = issueImgs;
    productionIssuesVideos.value = issueVids;
    dialogVisitable.value = true;
  };
    if (contentType.startsWith("video/")) {
      videos.push(fileUrl);
      return;
    }
  const showMedia = (items, index, type) => {
    mediaList.value = items;
    currentMediaIndex.value = index;
    mediaType.value = type;
    isMediaViewerVisible.value = true;
  };
    images.push(fileUrl);
  });
  const closeMediaViewer = () => {
    isMediaViewerVisible.value = false;
    mediaList.value = [];
    mediaType.value = "image";
  };
  return { images, videos };
};
  const cancel = () => {
    dialogVisitable.value = false;
  };
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;
};
const showMedia = (items, index, type) => {
  mediaList.value = items;
  currentMediaIndex.value = index;
  mediaType.value = type;
  isMediaViewerVisible.value = true;
};
const closeMediaViewer = () => {
  isMediaViewerVisible.value = false;
  mediaList.value = [];
  mediaType.value = "image";
};
const cancel = () => {
  dialogVisitable.value = false;
};
defineExpose({ openDialog });
  defineExpose({ openDialog });
</script>
<style scoped lang="scss">
.upload-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  .upload-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
    border: 1px solid #dcdfe6;
    box-sizing: border-box;
  .form-container {
    flex: 1;
    width: 100%;
    margin-bottom: 20px;
    .form-container {
      flex: 1;
      width: 100%;
      margin-bottom: 20px;
    }
  }
}
.title {
  font-size: 14px;
  color: #165dff;
  line-height: 20px;
  font-weight: 600;
  padding-left: 10px;
  position: relative;
  margin: 6px 0;
  .title {
    font-size: 14px;
    color: #165dff;
    line-height: 20px;
    font-weight: 600;
    padding-left: 10px;
    position: relative;
    margin: 6px 0;
  &::before {
    content: "";
    position: absolute;
    left: 0;
    top: 3px;
    width: 4px;
    height: 14px;
    background-color: #165dff;
    &::before {
      content: "";
      position: absolute;
      left: 0;
      top: 3px;
      width: 4px;
      height: 14px;
      background-color: #165dff;
    }
  }
}
.media-list {
  display: flex;
  flex-wrap: wrap;
}
  .media-list {
    display: flex;
    flex-wrap: wrap;
  }
.media-image {
  max-width: 100px;
  height: 100px;
  margin: 5px;
  cursor: pointer;
}
  .media-image {
    max-width: 100px;
    height: 100px;
    margin: 5px;
    cursor: pointer;
  }
.video-item {
  position: relative;
  margin: 10px;
  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-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-icon {
    width: 30px;
    height: 30px;
    opacity: 0.8;
  }
.video-text {
  text-align: center;
  font-size: 12px;
  color: #666;
}
  .video-text {
    text-align: center;
    font-size: 12px;
    color: #666;
  }
.media-viewer-overlay {
  position: fixed;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.8);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}
  .media-viewer-overlay {
    position: fixed;
    inset: 0;
    background-color: rgba(0, 0, 0, 0.8);
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
  }
.media-viewer-content {
  position: relative;
  max-width: 90vw;
  max-height: 90vh;
  overflow: hidden;
}
  .media-viewer-content {
    position: relative;
    max-width: 90vw;
    max-height: 90vh;
    overflow: hidden;
  }
.video-player-wrap {
  position: relative;
}
  .video-player-wrap {
    position: relative;
  }
.video-player {
  max-width: 90vw;
  max-height: 80vh;
}
  .video-player {
    max-width: 90vw;
    max-height: 80vh;
  }
</style>
src/views/equipmentManagement/inspectionManagement/index.vue
@@ -231,6 +231,12 @@
      operation: operations
        .map(op => {
          switch (op) {
            case "view":
              return {
                name: "详情",
                clickFun: openViewDialog,
                color: "#409EFF",
              };
            case "edit":
              return {
                name: "编辑",
@@ -273,14 +279,14 @@
      ];
      operationsArr.value = ["edit"];
    } else if (value === "task") {
      const operationColumn = getOperationColumn(["upload", "viewFile"]);
      const operationColumn = getOperationColumn(["view", "upload", "viewFile"]);
      // 巡检记录不展示"是否启用"列
      const taskColumns = columns.value.filter(col => col.prop !== "isEnabled");
      tableColumns.value = [
        ...taskColumns,
        ...(operationColumn ? [operationColumn] : []),
      ];
      operationsArr.value = ["upload", "viewFile"];
      operationsArr.value = ["view", "upload", "viewFile"];
    }
    pageNum.value = 1;
    pageSize.value = 10;
@@ -377,7 +383,13 @@
  const openUploadDialog = row => {
    nextTick(() => {
      uploadFiles.value?.openDialog(row);
      uploadFiles.value?.openDialog("add", row);
    });
  };
  const openViewDialog = row => {
    nextTick(() => {
      uploadFiles.value?.openDialog("view", row);
    });
  };
src/views/equipmentManagement/upkeep/Form/formDia.vue
@@ -101,7 +101,7 @@
                <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-time-picker v-model="form.frequencyDetail" placeholder="选择时间" format="HH:mm"
                                                        value-format="HH:mm" />
                                                    value-format="HH:mm" />
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
@@ -116,7 +116,7 @@
                            <el-option label="周日" value="SUN"/>
                        </el-select>
                        <el-time-picker v-model="form.time" placeholder="选择时间" format="HH:mm"
                                                        value-format="HH:mm"  style="width: 50%"/>
                                                    value-format="HH:mm"  style="width: 50%"/>
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
@@ -141,6 +141,23 @@
                            format="MM,DD,HH:mm"
                            value-format="MM,DD,HH:mm"
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="24">
                    <el-form-item label="定时任务">
                        <el-switch
                            v-model="form.isActive"
                            :active-value="1"
                            :inactive-value="0"
                            active-text="开启"
                            inactive-text="关闭"
                            inline-prompt
                        />
                        <span style="margin-left: 12px; color: #909399; font-size: 12px;">
                            {{ form.isActive === 1 ? '任务将按计划执行' : '任务暂停执行' }}
                        </span>
                    </el-form-item>
                </el-col>
            </el-row>
@@ -184,7 +201,8 @@
        time: '',
        deviceModel: undefined, // 规格型号
        registrationDate: '',
        maintenancePerson: '' // 保养人
        maintenancePerson: '', // 保养人
        isActive: 0 // 定时任务开关:0=关闭,1=开启
    },
    rules: {
        taskId: [{ required: true, message: "请选择设备", trigger: "change" },],
@@ -271,7 +289,8 @@
        time: '',
        deviceModel: undefined,
        registrationDate: '',
        maintenancePerson: ''
        maintenancePerson: '',
        isActive: 0
    }
}
@@ -294,21 +313,21 @@
                }
                delete payload.inspector
                delete payload.inspectorIds
                delete payload.active
                if (payload.frequencyType === 'WEEKLY') {
                    let frequencyDetail = ''
                    frequencyDetail = payload.week + ',' + payload.time
                    payload.frequencyDetail = frequencyDetail
                }
                // 录入日期:直接使用表单里的 registrationDate 字段
                // 一些默认状态字段
                if (payload.status === undefined || payload.status === null || payload.status === '') {
                    payload.status = '0' // 默认状态,可按实际枚举调整
                }
                payload.active = true
                payload.deleted = 0
                if (operationType.value === 'edit') {
                    await deviceMaintenanceTaskEdit(payload)
                } else {
src/views/equipmentManagement/upkeep/index.vue
@@ -63,6 +63,12 @@
            }"
                    @selection-change="handleScheduledSelectionChange"
                    @pagination="changeScheduledPage">
            <template #isActiveRef="{ row }">
              <el-tag v-if="row.isActive === 1"
                      type="success">开启</el-tag>
              <el-tag v-else
                      type="info">关闭</el-tag>
            </template>
            <template #statusRef="{ row }">
              <el-tag v-if="row.status === 1"
                      type="success">启用</el-tag>
@@ -308,7 +314,7 @@
    {
      prop: "frequencyType",
      label: "频次",
      minWidth: 150,
      minWidth: 50,
      // PIMTable 使用的是 formatData,而不是 Element-Plus 的 formatter
      formatData: cell =>
        ({
@@ -321,7 +327,7 @@
    {
      prop: "frequencyDetail",
      label: "开始日期与时间",
      minWidth: 150,
      minWidth: 130,
      // 同样改用 formatData,PIMTable 内部会把单元格值传进来
      formatData: cell => {
        if (typeof cell !== "string") return "";
@@ -342,6 +348,14 @@
        );
      },
    },
    {
      prop: "isActive",
      label: "定时任务",
      dataType: "slot",
      slot: "isActiveRef",
      align: "center",
      minWidth: 100,
    },
    { prop: "maintenancePerson", label: "保养人", minWidth: 100 },
    { prop: "registrant", label: "登记人", minWidth: 100 },
    {
@@ -349,7 +363,7 @@
      label: "登记日期",
      minWidth: 100,
      formatData: cell =>
        cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
        cell ? dayjs(cell).format("YYYY-MM-DD") : "-",
    },
    {
      fixed: "right",
@@ -357,7 +371,7 @@
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "200px",
      width: "150px",
    },
  ]);
src/views/procurementManagement/procurementLedger/detail.vue
@@ -7,7 +7,7 @@
            <span class="readonly-text">{{ form.purchaseContractNumber || '--' }}</span>
          </el-form-item>
        </el-col>
        <el-col :span="12">
        <el-col v-if="showSalesContractBinding" :span="12">
          <el-form-item label="销售合同号:">
            <span class="readonly-text">{{ form.salesContractNo || '--' }}</span>
          </el-form-item>
@@ -129,6 +129,8 @@
import { getPurchaseById } from "@/api/procurementManagement/procurementLedger"
const visible = ref(false)
// 是否显示销售合同号绑定
const showSalesContractBinding = false
const form = ref({})
const productData = ref([])
const fileList = ref([])
src/views/procurementManagement/procurementLedger/index.vue
@@ -11,7 +11,7 @@
            <el-input v-model="searchForm.purchaseContractNumber" style="width: 240px" placeholder="请输入"
              @change="handleQuery" clearable :prefix-icon="Search" />
          </el-form-item>
          <el-form-item label="销售合同号:">
          <el-form-item v-if="showSalesContractBinding" label="销售合同号:">
            <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search"
              @change="handleQuery" />
          </el-form-item>
@@ -76,7 +76,7 @@
        </el-table-column>
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="采购合同号" prop="purchaseContractNumber" width="160" show-overflow-tooltip />
        <el-table-column label="销售合同号" prop="salesContractNo" width="160" show-overflow-tooltip />
        <el-table-column v-if="showSalesContractBinding" label="销售合同号" prop="salesContractNo" width="160" show-overflow-tooltip />
        <el-table-column label="供应商名称" prop="supplierName" width="160" show-overflow-tooltip />
        <el-table-column label="项目名称" prop="projectName" width="320" show-overflow-tooltip />
        <el-table-column label="审批状态" prop="approvalStatus" width="100" show-overflow-tooltip>
@@ -124,7 +124,7 @@
              <el-input v-model="form.purchaseContractNumber" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
          <el-col v-if="showSalesContractBinding" :span="12">
            <el-form-item label="销售合同号:" prop="salesLedgerId">
              <el-select v-model="form.salesLedgerId" placeholder="请选择" filterable clearable @change="salesLedgerChange">
                <el-option v-for="item in salesContractList" :key="item.id" :label="item.salesContractNo" :value="item.id" />
@@ -439,6 +439,9 @@
import FileUpload from "@/components/AttachmentUpload/file/index.vue";
const userStore = useUserStore();
// 是否显示销售合同号绑定
const showSalesContractBinding = false;
// 订单审批状态显示文本
const approvalStatusText = {
@@ -1034,7 +1037,6 @@
    if (form.value.salesLedgerId == -1) {
      form.value.salesLedgerId = null;
    }
    console.log(form.value, "form.value===========");
    dialogFormVisible.value = true;
  } catch (error) {
    console.error("打开表单失败:", error);
@@ -1536,9 +1538,7 @@
};
// 销售合同选择改变方法
const salesLedgerChange = async row => {
  console.log("row", row);
  var index = salesContractList.value.findIndex(item => item.id == row);
  console.log("index", index);
  if (index > -1) {
    await querygProductInfoByContractNo();
  }
src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -102,12 +102,8 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测结果:" prop="checkResult">
              <el-select v-model="form.checkResult" :disabled="isViewMode">
                <el-option label="合格" value="合格" />
                <el-option label="不合格" value="不合格" />
                <el-option label="部分合格" value="部分合格" />
              </el-select>
            <el-form-item label="合格率:">
              <el-tag :type="passRateTagType">{{ passRateDisplayText }}</el-tag>
            </el-form-item>
          </el-col>
        </el-row>
@@ -188,7 +184,6 @@
    qualifiedQuantity: "",
    unqualifiedQuantity: "",
    checkCompany: "",
    checkResult: "",
  },
  rules: {
    checkTime: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -202,12 +197,42 @@
    qualifiedQuantity: [{ required: true, message: "请输入", trigger: "blur" }],
    unqualifiedQuantity: [{ required: true, message: "请输入", trigger: "blur" }],
    checkCompany: [{ required: false, message: "请输入", trigger: "blur" }],
    checkResult: [{ required: true, message: "请输入", trigger: "change" }],
  },
});
const { form, rules } = toRefs(data);
// 是否为查看模式
const isViewMode = computed(() => operationType.value === 'view');
const passRateValue = computed(() => {
  const fromApi = form.value.passRate;
  if (fromApi != null && fromApi !== '') {
    const n = Number(fromApi);
    if (!Number.isNaN(n)) return n;
  }
  const quantity = Number(form.value.quantity);
  const qualified = Number(form.value.qualifiedQuantity);
  if (!quantity || Number.isNaN(quantity)) return null;
  const qualifiedNum = Number.isNaN(qualified) ? 0 : qualified;
  return (qualifiedNum / quantity) * 100;
});
const passRateDisplayText = computed(() => {
  const params = passRateValue.value;
  if (params == null || params === '') return '—';
  const n = Number(params);
  if (Number.isNaN(n)) return '—';
  return `${n.toFixed(2)}%`;
});
const passRateTagType = computed(() => {
  const params = passRateValue.value;
  if (params == null || params === '') return 'info';
  const n = Number(params);
  if (Number.isNaN(n)) return 'info';
  if (n >= 100) return 'success';
  if (n >= 90) return 'warning';
  return 'danger';
});
// 编辑时:productMainId 或 purchaseLedgerId 任一有值则工序、数量置灰
const processQuantityDisabled = computed(() => {
  const v = form.value || {};
src/views/qualityManagement/finalInspection/index.vue
@@ -143,22 +143,31 @@
    width: 120
  },
  {
    label: "检测结果",
    prop: "checkResult",
    label: "合格率",
    prop: "passRate",
    width: 100,
    dataType: "tag",
    formatData: (params) => {
      if (params == null || params === '') return '—';
      const n = Number(params);
      if (Number.isNaN(n)) return '—';
      return `${n.toFixed(2)}%`;
    },
    formatType: (params) => {
      if (params == '不合格') {
        return "danger";
      } else if (params == '合格') {
        return "success";
      } else {
        return 'danger';
      }
      if (params == null || params === '') return 'info';
      const n = Number(params);
      if (Number.isNaN(n)) return 'info';
      if (n === 100) return 'success';
      if (n >= 75) return 'primary';
      if (n >= 50) return 'warning';
      if (n >= 25) return 'danger';
      return 'danger';
    },
  },
  {
    label: "提交状态",
    prop: "inspectState",
    dataType: "tag",
    formatData: (params) => {
      if (params) {
        return "已提交";
@@ -166,6 +175,13 @@
        return "未提交";
      }
    },
    formatType: (params) => {
      if (params) {
        return "success";
      } else {
        return "info";
      }
    },
  },
  {
    dataType: "action",
src/views/qualityManagement/processInspection/components/formDia.vue
@@ -139,16 +139,8 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测结果:"
                          prop="checkResult">
              <el-select v-model="form.checkResult" :disabled="isViewMode">
                <el-option label="合格"
                           value="合格" />
                <el-option label="不合格"
                           value="不合格" />
                <el-option label="部分合格"
                           value="部分合格" />
              </el-select>
            <el-form-item label="合格率:">
              <el-tag :type="passRateTagType">{{ passRateDisplayText }}</el-tag>
            </el-form-item>
          </el-col>
        </el-row>
@@ -250,7 +242,6 @@
      qualifiedQuantity: "",
      unqualifiedQuantity: "",
      checkCompany: "",
      checkResult: "",
    },
    rules: {
      checkTime: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -264,13 +255,43 @@
      qualifiedQuantity: [{ required: true, message: "请输入", trigger: "blur" }],
      unqualifiedQuantity: [{ required: true, message: "请输入", trigger: "blur" }],
      checkCompany: [{ required: false, message: "请输入", trigger: "blur" }],
      checkResult: [{ required: true, message: "请输入", trigger: "change" }],
    },
  });
  const userList = ref([]);
  const { form, rules } = toRefs(data);
  // 是否为查看模式
  const isViewMode = computed(() => operationType.value === 'view');
  const passRateValue = computed(() => {
    const fromApi = form.value.passRate;
    if (fromApi != null && fromApi !== '') {
      const n = Number(fromApi);
      if (!Number.isNaN(n)) return n;
    }
    const quantity = Number(form.value.quantity);
    const qualified = Number(form.value.qualifiedQuantity);
    if (!quantity || Number.isNaN(quantity)) return null;
    const qualifiedNum = Number.isNaN(qualified) ? 0 : qualified;
    return (qualifiedNum / quantity) * 100;
  });
  const passRateDisplayText = computed(() => {
    const params = passRateValue.value;
    if (params == null || params === '') return '—';
    const n = Number(params);
    if (Number.isNaN(n)) return '—';
    return `${n.toFixed(2)}%`;
  });
  const passRateTagType = computed(() => {
    const params = passRateValue.value;
    if (params == null || params === '') return 'info';
    const n = Number(params);
    if (Number.isNaN(n)) return 'info';
    if (n >= 100) return 'success';
    if (n >= 90) return 'warning';
    return 'danger';
  });
  // 编辑时:productMainId 或 purchaseLedgerId 任一有值则工序、数量置灰
  const processQuantityDisabled = computed(() => {
    const v = form.value || {};
@@ -338,7 +359,6 @@
      unit: "",
      quantity: "",
      checkCompany: "",
      checkResult: "",
    };
    testStandardOptions.value = [];
    tableData.value = [];
src/views/qualityManagement/processInspection/index.vue
@@ -142,22 +142,31 @@
    width: 120
  },
  {
    label: "检测结果",
    prop: "checkResult",
    label: "合格率",
    prop: "passRate",
    width: 100,
    dataType: "tag",
    formatData: (params) => {
      if (params == null || params === '') return '—';
      const n = Number(params);
      if (Number.isNaN(n)) return '—';
      return `${n.toFixed(2)}%`;
    },
    formatType: (params) => {
      if (params == '不合格') {
        return "danger";
      } else if (params == '合格') {
        return "success";
      } else {
        return 'danger';
      }
      if (params == null || params === '') return 'info';
      const n = Number(params);
      if (Number.isNaN(n)) return 'info';
      if (n === 100) return 'success';
      if (n >= 75) return 'primary';
      if (n >= 50) return 'warning';
      if (n >= 25) return 'danger';
      return 'danger';
    },
  },
    {
        label: "提交状态",
        prop: "inspectState",
        dataType: "tag",
        formatData: (params) => {
            if (params) {
                return "已提交";
@@ -165,6 +174,13 @@
                return "未提交";
            }
        },
        formatType: (params) => {
            if (params) {
                return "success";
            } else {
                return "info";
            }
        },
    },
  {
    dataType: "action",
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -107,12 +107,8 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测结果:" prop="checkResult">
              <el-select v-model="form.checkResult" :disabled="isViewMode">
                <el-option label="合格" value="合格"/>
                <el-option label="不合格" value="不合格"/>
                <el-option label="部分合格" value="部分合格"/>
              </el-select>
            <el-form-item label="合格率:">
              <el-tag :type="passRateTagType">{{ passRateDisplayText }}</el-tag>
            </el-form-item>
          </el-col>
        </el-row>
@@ -194,7 +190,6 @@
    unit: "",
    quantity: "",
    checkCompany: "",
    checkResult: "",
  },
  rules: {
    checkTime: [{required: true, message: "请输入", trigger: "blur"},],
@@ -208,7 +203,6 @@
    qualifiedQuantity: [{required: true, message: "请输入", trigger: "blur"}],
    unqualifiedQuantity: [{required: true, message: "请输入", trigger: "blur"}],
    checkCompany: [{required: false, message: "请输入", trigger: "blur"}],
    checkResult: [{required: true, message: "请选择检测结果", trigger: "change"}],
  },
});
const tableColumn = ref([
@@ -249,6 +243,37 @@
// 是否为查看模式
const isViewMode = computed(() => operationType.value === 'view');
const passRateValue = computed(() => {
  const fromApi = form.value.passRate;
  if (fromApi != null && fromApi !== '') {
    const n = Number(fromApi);
    if (!Number.isNaN(n)) return n;
  }
  const quantity = Number(form.value.quantity);
  const qualified = Number(form.value.qualifiedQuantity);
  if (!quantity || Number.isNaN(quantity)) return null;
  const qualifiedNum = Number.isNaN(qualified) ? 0 : qualified;
  return (qualifiedNum / quantity) * 100;
});
const passRateDisplayText = computed(() => {
  const params = passRateValue.value;
  if (params == null || params === '') return '—';
  const n = Number(params);
  if (Number.isNaN(n)) return '—';
  return `${n.toFixed(2)}%`;
});
const passRateTagType = computed(() => {
  const params = passRateValue.value;
  if (params == null || params === '') return 'info';
  const n = Number(params);
  if (Number.isNaN(n)) return 'info';
  if (n >= 100) return 'success';
  if (n >= 90) return 'warning';
  return 'danger';
});
// 编辑时:productMainId 或 purchaseLedgerId 任一有值则供应商、数量置灰
const supplierQuantityDisabled = computed(() => {
  const v = form.value || {};
@@ -282,7 +307,6 @@
    unit: "",
    quantity: "",
    checkCompany: "",
    checkResult: "",
  }
  testStandardOptions.value = [];
  tableData.value = [];
src/views/qualityManagement/rawMaterialInspection/index.vue
@@ -144,27 +144,31 @@
    width: 120
  },
  {
    label: "检测单位",
    prop: "checkCompany",
    width: 120
  },
  {
    label: "检测结果",
    prop: "checkResult",
    label: "合格率",
    prop: "passRate",
    width: 100,
    dataType: "tag",
    formatData: (params) => {
      if (params == null || params === '') return '—';
      const n = Number(params);
      if (Number.isNaN(n)) return '—';
      return `${n.toFixed(2)}%`;
    },
    formatType: (params) => {
      if (params === '不合格') {
        return "danger";
      } else if (params === '合格') {
        return "success";
      } else {
        return 'danger';
      }
      if (params == null || params === '') return 'info';
      const n = Number(params);
      if (Number.isNaN(n)) return 'info';
      if (n === 100) return 'success';
      if (n >= 75) return 'primary';
      if (n >= 50) return 'warning';
      if (n >= 25) return 'danger';
      return 'danger';
    },
  },
  {
    label: "提交状态",
    prop: "inspectState",
    dataType: "tag",
    formatData: (params) => {
      if (params) {
        return "已提交";
@@ -172,6 +176,13 @@
        return "未提交";
      }
    },
    formatType: (params) => {
      if (params) {
        return "success";
      } else {
        return "info";
      }
    },
  },
  {
    dataType: "action",