yyb
2026-05-18 2d4ee49af27e50a4a394f902033704a01f2462d4
Merge remote-tracking branch 'origin/dev_NEW_pro' into dev_NEW_pro_鹤壁
已添加1个文件
已修改6个文件
1820 ■■■■ 文件已修改
src/api/basicData/storageAttachment.js 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/add.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/fileList.vue 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/maintain.vue 97 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/index.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/upload.vue 1537 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/storageAttachment.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
// é™„件页面接口
import request from '@/utils/request'
// é™„件查询
export function attachmentList(query) {
    return request({
        url: '/storageAttachment/list',
        method: 'get',
        params: query
    })
}
// é™„件新增
export function createAttachment(data) {
    return request({
        url: '/storageAttachment/add',
        method: 'post',
        data
    })
}
// é™„件删除
export function deleteAttachment(data) {
    return request({
        url: '/storageAttachment/delete',
        method: 'delete',
        data
    })
}
src/pages/equipmentManagement/upkeep/add.vue
@@ -60,6 +60,11 @@
                 placeholder="请输入保养项目"
                 clearable />
      </u-form-item>
      <u-form-item label="附件图片"
                   prop="storageBlobDTOs"
                   border-bottom>
        <CommonUpload v-model="form.storageBlobDTOs" />
      </u-form-item>
      <!-- æäº¤æŒ‰é’® -->
      <view class="footer-btns">
        <u-button class="cancel-btn"
@@ -87,6 +92,7 @@
  import { ref, computed, onMounted, onUnmounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import CommonUpload from "@/components/CommonUpload.vue";
  import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
  import {
    addUpkeep,
@@ -152,6 +158,7 @@
    maintenancePlanTime: dayjs().format("YYYY-MM-DD"), // è®¡åˆ’保养日期
    maintenancePerson: undefined, // ä¿å…»äºº
    machineryCategory: undefined, // ä¿å…»é¡¹ç›®
    storageBlobDTOs: [], // é™„件图片
  });
  // åŠ è½½è®¾å¤‡åˆ—è¡¨
@@ -178,6 +185,7 @@
          );
          form.value.maintenancePerson = data.maintenancePerson;
          form.value.machineryCategory = data.machineryCategory;
          form.value.storageBlobDTOs = data.storageBlobVOs || [];
          // è®¾ç½®è®¾å¤‡åç§°æ˜¾ç¤º
          const device = deviceOptions.value.find(
            item => item.id === data.deviceLedgerId
@@ -314,7 +322,8 @@
      const id = getPageId();
      // å‡†å¤‡æäº¤æ•°æ®
      const submitData = { ...form.value };
      const submitData = { ...form.value, status: 0 };
      // ç¡®ä¿æ—¥æœŸæ ¼å¼æ­£ç¡®
      if (
        submitData.maintenancePlanTime &&
src/pages/equipmentManagement/upkeep/fileList.vue
@@ -8,7 +8,7 @@
      <view v-if="fileList.length > 0"
            class="file-list">
        <view v-for="(file, index) in fileList"
              :key="file.id || index"
              :key="file.storageAttachmentId || file.id || index"
              class="file-item">
          <!-- æ–‡ä»¶å›¾æ ‡ -->
          <!-- <view class="file-icon"
@@ -19,7 +19,7 @@
          </view> -->
          <!-- æ–‡ä»¶ä¿¡æ¯ -->
          <view class="file-info">
            <text class="file-name">{{ file.name }}</text>
            <text class="file-name">{{ file.originalFilename || file.name }}</text>
            <!-- <text class="file-meta">{{ formatFileSize(file.fileSize) }} Â· {{ file.uploadTime || file.createTime }}</text> -->
          </view>
          <!-- æ“ä½œæŒ‰é’® -->
@@ -65,15 +65,16 @@
<script setup>
  import { ref, onMounted } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import config from "@/config";
  import { getToken } from "@/utils/auth";
  // import { saveAs } from "file-saver";
  import {
    listMaintenanceTaskFiles,
    addMaintenanceTaskFile,
    delMaintenanceTaskFile,
  } from "@/api/equipmentManagement/upkeep";
    attachmentList,
    createAttachment,
    deleteAttachment,
  } from "@/api/basicData/storageAttachment";
  import { blobValidate } from "@/utils/ruoyi";
  // é™„件列表
@@ -214,21 +215,27 @@
              // const fileType = fileName.split(".").pop();
              // 3. æž„造保存文件信息的参数
              const saveData = {
                name: fileName,
                deviceMaintenanceId: upkeepId.value,
                url: res.data.tempPath || "",
                application: "file",
                recordType: recordType.value,
                recordId: upkeepId.value,
                storageBlobDTOs: [
                  {
                    name: fileName,
                    url:
                      res.data.url ||
                      res.data.previewURL ||
                      res.data.tempPath ||
                      "",
                    ...res.data,
                  },
                ],
              };
              console.log(saveData, "保存文件信息参数");
              // 4. è°ƒç”¨ addRuleFile æŽ¥å£ä¿å­˜æ–‡ä»¶ä¿¡æ¯
              addMaintenanceTaskFile(saveData)
              // 4. è°ƒç”¨ createAttachment æŽ¥å£ä¿å­˜æ–‡ä»¶ä¿¡æ¯
              createAttachment(saveData)
                .then(addRes => {
                  if (addRes.code === 200) {
                    // 5. æ·»åŠ åˆ°æ–‡ä»¶åˆ—è¡¨
                    const newFile = {
                      ...addRes.data,
                      uploadTime: new Date().toLocaleString(),
                    };
                    // fileList.value.push(newFile);
                    // 5. åˆ·æ–°åˆ—表
                    getFileList();
                    showToast("上传成功");
                  } else {
@@ -257,20 +264,32 @@
  };
  // ä¸‹è½½æ–‡ä»¶
  const downloadFile = file => {
    var url =
      config.baseUrl +
      "/common/download?fileName=" +
      encodeURIComponent(file.url) +
      "&delete=true";
    console.log(url, "url");
    let url = file.downloadURL || file.previewURL || file.url;
    if (!url) {
      showToast("文件地址无效");
      return;
    }
    // å¦‚果不是完整的URL,则拼接
    if (!url.startsWith("http")) {
      url =
        config.baseUrl +
        "/common/download?fileName=" +
        encodeURIComponent(url) +
        "&delete=true";
    }
    console.log(url, "下载地址");
    uni.showLoading({ title: "正在下载...", mask: true });
    uni
      .downloadFile({
        url: url,
        responseType: "blob",
        header: { Authorization: "Bearer " + getToken() },
      })
      .then(res => {
        uni.hideLoading();
        let osType = uni.getStorageSync("deviceInfo").osName;
        let filePath = res.tempFilePath;
        if (osType === "ios") {
@@ -280,7 +299,6 @@
            success: res => {},
            fail: err => {
              console.log("uni.openDocument--fail");
              reject(err);
            },
          });
        } else {
@@ -290,10 +308,8 @@
              uni.showToast({
                icon: "none",
                mask: true,
                title:
                  "文件已保存:Android/data/uni.UNI720216F/apps/__UNI__720216F/" +
                  fileRes.savedFilePath, //保存路径
                duration: 3000,
                title: "文件已下载并尝试打开",
                duration: 2000,
              });
              setTimeout(() => {
                //打开文档查看
@@ -305,24 +321,12 @@
            },
            fail: err => {
              console.log("uni.save--fail");
              reject(err);
            },
          });
        }
        // const isBlob = blobValidate(res.data);
        // if (isBlob) {
        //   const blob = new Blob([res.data], { type: "text/plain" });
        //   const url = URL.createObjectURL(blob);
        //   const downloadLink = document.getElementById("downloadLink");
        //   downloadLink.href = url;
        //   downloadLink.download = file.name;
        //   downloadLink.click();
        //   showToast("下载成功");
        // } else {
        //   showToast("下载失败");
        // }
      })
      .catch(err => {
        uni.hideLoading();
        console.error("下载失败:", err);
        showToast("下载失败");
      });
@@ -335,7 +339,7 @@
      content: `确定要删除附件 "${file.name}" å—?`,
      success: res => {
        if (res.confirm) {
          deleteFile(file.id, index);
          deleteFile(file.storageAttachmentId || file.id, index);
        }
      },
    });
@@ -348,7 +352,7 @@
      mask: true,
    });
    delMaintenanceTaskFile([fileId])
    deleteAttachment([fileId])
      .then(res => {
        uni.hideLoading();
        if (res.code === 200) {
@@ -372,37 +376,48 @@
      icon: "none",
    });
  };
  const rulesRegulationsManagementId = ref("");
  const upkeepId = ref("");
  const recordType = ref("");
  // é¡µé¢åŠ è½½æ—¶èŽ·å–å‚æ•°
  onLoad(options => {
    if (options.recordId) {
      upkeepId.value = options.recordId;
    } else {
      upkeepId.value = uni.getStorageSync("upkeepId");
    }
    if (options.recordType) {
      recordType.value = options.recordType;
    } else {
      recordType.value = "device_maintenance"; // é»˜è®¤å…¼å®¹
    }
    getFileList();
  });
  // é¡µé¢åŠ è½½æ—¶
  onMounted(() => {
    // ä»Ž API èŽ·å–é™„ä»¶åˆ—è¡¨
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å– rulesRegulationsManagementId
    rulesRegulationsManagementId.value = uni.getStorageSync(
      "rulesRegulationsManagement"
    );
    upkeepId.value = uni.getStorageSync("upkeepId");
    getFileList();
    // getFileList(); // onLoad ä¸­å·²ç»è°ƒç”¨äº†
  });
  // èŽ·å–é™„ä»¶åˆ—è¡¨
  const getFileList = () => {
    if (!upkeepId.value) return;
    uni.showLoading({
      title: "加载中...",
      mask: true,
    });
    listMaintenanceTaskFiles({
      current: 1,
      size: 100,
      deviceMaintenanceId: upkeepId.value,
      rulesRegulationsManagementId: upkeepId.value,
    attachmentList({
      recordType: recordType.value,
      recordId: upkeepId.value,
    })
      .then(res => {
        uni.hideLoading();
        if (res.code === 200) {
          fileList.value = res.data.records || [];
          fileList.value = res.data || [];
        } else {
          showToast("获取附件列表失败");
        }
src/pages/equipmentManagement/upkeep/index.vue
@@ -80,7 +80,8 @@
            </view>
            <view class="detail-row">
              <text class="detail-label">保养结果</text>
              <view class="detail-value">
              <text class="detail-value">{{ item.maintenanceResult || '-' }}</text>
              <!-- <view class="detail-value">
                <u-tag v-if="item.maintenanceResult === 1"
                       type="success"
                       size="mini">
@@ -92,7 +93,7 @@
                  ç»´ä¿®
                </u-tag>
                <text v-if="item.maintenanceResult === undefined || item.maintenanceResult === null">-</text>
              </view>
              </view> -->
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
@@ -206,10 +207,8 @@
  };
  // æ–°å¢žé™„ä»¶ - è·³è½¬åˆ°é™„件页面
  const addFile = id => {
    // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
    uni.setStorageSync("upkeepId", id);
    uni.navigateTo({
      url: "/pages/equipmentManagement/upkeep/fileList",
      url: `/pages/equipmentManagement/upkeep/fileList?recordId=${id}&recordType=device_maintenance`,
    });
  };
src/pages/equipmentManagement/upkeep/maintain.vue
@@ -100,81 +100,9 @@
      <!-- ä¸Šä¼ é™„ä»¶ -->
      <u-form-item v-if="form.status == '1'"
                   label="保养附件"
                   prop="storageBlobDTOs"
                   border-bottom>
        <view class="simple-upload-area">
          <view class="upload-buttons">
            <u-button type="primary"
                      @click="chooseMedia('image')"
                      :loading="uploading"
                      :disabled="uploadFiles.length >= uploadConfig.limit"
                      :customStyle="{ marginRight: '10px', flex: 1 }">
              <u-icon name="camera"
                      size="18"
                      color="#fff"
                      style="margin-right: 5px;"></u-icon>
              {{ uploading ? '上传中...' : '拍照' }}
            </u-button>
            <!-- <u-button type="success"
                      @click="chooseMedia('video')"
                      :loading="uploading"
                      :disabled="uploadFiles.length >= uploadConfig.limit"
                      :customStyle="{ flex: 1 }">
              <uni-icons type="videocam"
                         name="videocam"
                         size="18"
                         color="#fff"
                         style="margin-right: 5px;"></uni-icons>
              {{ uploading ? '上传中...' : '拍视频' }}
            </u-button> -->
          </view>
          <!-- ä¸Šä¼ è¿›åº¦ -->
          <view v-if="uploading"
                class="upload-progress">
            <u-line-progress :percentage="uploadProgress"
                             :showText="true"
                             activeColor="#409eff"></u-line-progress>
          </view>
          <!-- ä¸Šä¼ çš„æ–‡ä»¶åˆ—表 -->
          <view v-if="uploadFiles.length > 0"
                class="file-list">
            <view v-for="(file, index) in uploadFiles"
                  :key="index"
                  class="file-item">
              <view class="file-preview-container">
                <!-- {{formatFileUrl(file.url)}} -->
                <image v-if="file.type === 'image' || isImageFile(file)"
                       :src="formatFileUrl(file.url || file.tempFilePath || file.path || file.downloadUrl)"
                       class="file-preview"
                       mode="aspectFill" />
                <view v-else-if="file.type === 'video'"
                      class="video-preview">
                  <uni-icons type="videocam"
                             name="videocam"
                             size="18"
                             color="#fff"
                             style="margin-right: 5px;"></uni-icons>
                  <text class="video-text">视频</text>
                </view>
                <!-- åˆ é™¤æŒ‰é’® -->
                <view class="delete-btn"
                      @click="removeFile(index)">
                  <u-icon name="close"
                          size="12"
                          color="#fff"></u-icon>
                </view>
              </view>
              <view class="file-info">
                <text class="file-name">{{ file.bucketFilename || file.name || (file.type === 'image' ? '图片' : '视频')
                  }}</text>
                <text class="file-size">{{ formatFileSize(file.size) }}</text>
              </view>
            </view>
          </view>
          <view v-if="uploadFiles.length === 0"
                class="empty-state">
            <text>请选择要上传的保养图片</text>
          </view>
        </view>
        <CommonUpload v-model="form.storageBlobDTOs" />
      </u-form-item>
      <!-- æäº¤æŒ‰é’® -->
      <view class="footer-btns">
@@ -235,6 +163,7 @@
  import { ref, onMounted, reactive } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import CommonUpload from "@/components/CommonUpload.vue";
  import { addMaintenance } from "@/api/equipmentManagement/upkeep";
  import { getSparePartsList } from "@/api/equipmentManagement/repair";
  import useUserStore from "@/store/modules/user";
@@ -275,7 +204,6 @@
  const sparePartsQtyRaw = ref("");
  // æ–‡ä»¶ä¸Šä¼ ç›¸å…³
  const uploadFiles = ref([]);
  const uploading = ref(false);
  const uploadProgress = ref(0);
  const number = ref(0);
@@ -316,6 +244,7 @@
    maintenanceResult: undefined, // ä¿å…»ç»“æžœ
    maintenanceActuallyTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // å®žé™…保养日期(只显示日期)
    sparePartsIds: undefined, // è®¾å¤‡å¤‡ä»¶ID
    storageBlobDTOs: [], // ä¿å…»é™„ä»¶
  });
  // æ¸…除表单校验状态
@@ -330,6 +259,7 @@
      maintenanceResult: undefined,
      maintenanceActuallyTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
      sparePartsIds: [],
      storageBlobDTOs: [],
    };
    maintenancestatusText.value = "";
    selectedSpareParts.value = [];
@@ -374,7 +304,11 @@
      } else if (form.value.maintenanceResult === undefined) {
        isValid = false;
        errorMessage = "请选择保养结果";
      } else if (uploadFiles.value.length === 0 && form.value.status == "1") {
      } else if (
        (!form.value.storageBlobDTOs ||
          form.value.storageBlobDTOs.length === 0) &&
        form.value.status == "1"
      ) {
        isValid = false;
        errorMessage = "请上传保养照片";
      }
@@ -436,7 +370,6 @@
      const submitData = {
        ...form.value,
        imagesFile: form.value.status == "1" ? uploadFiles.value : [],
        sparePartsIds: spareIds.length ? spareIds.join(",") : "",
        sparePartsQty: spareIds.length
          ? spareIds.map(pid => sparePartQtyMap?.[pid] ?? 1).join(",")
@@ -605,7 +538,7 @@
    // é‡ç½®é€‰æ‹©çš„备件
    selectedSpareParts.value = [];
    // é‡ç½®ä¸Šä¼ çš„æ–‡ä»¶
    uploadFiles.value = [];
    form.value.storageBlobDTOs = [];
    uploading.value = false;
    uploadProgress.value = 0;
    maintenancestatusText.value = "";
@@ -655,8 +588,10 @@
      sparePartsIds.value = itemData.sparePartsIds;
      // å¡«å……附件数据
      if (itemData.files && itemData.files.length > 0) {
        uploadFiles.value = itemData.files.map(file => ({
      if (itemData.storageBlobVOs && itemData.storageBlobVOs.length > 0) {
        form.value.storageBlobDTOs = itemData.storageBlobVOs;
      } else if (itemData.files && itemData.files.length > 0) {
        form.value.storageBlobDTOs = itemData.files.map(file => ({
          id: file.id,
          name: file.name || file.bucketFilename || file.originalFilename,
          url: file.url || file.downloadUrl,
@@ -668,7 +603,7 @@
          size: file.size || file.byteSize,
        }));
      } else if (itemData.uploadFiles && itemData.uploadFiles.length > 0) {
        uploadFiles.value = itemData.uploadFiles.map(file => ({
        form.value.storageBlobDTOs = itemData.uploadFiles.map(file => ({
          id: file.id,
          name: file.name || file.bucketFilename || file.originalFilename,
          url: file.url || file.downloadUrl || file.tempFilePath || file.path,
src/pages/inspectionUpload/index.vue
@@ -84,7 +84,7 @@
                         size="small"
                         type="primary"
                         inverted></uni-tag>
                <uni-tag v-else=""
                <uni-tag v-else
                         text="未巡检"
                         size="small"
                         type="warning"
@@ -494,8 +494,6 @@
      icon: "error",
    });
  };
</script>
<style scoped>
src/pages/inspectionUpload/upload.vue
@@ -1,12 +1,13 @@
<template>
  <view class="inspection-upload-page">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <PageHeader title="上传巡检记录" @back="goBack" />
    <PageHeader title="上传巡检记录"
                @back="goBack" />
    <!-- é¡µé¢å†…容 -->
    <view class="upload-content">
      <!-- ä»»åŠ¡ä¿¡æ¯å¡ç‰‡ -->
      <view class="task-info-card" v-if="taskInfo">
      <view class="task-info-card"
            v-if="taskInfo">
        <view class="task-info-header">
          <text class="task-name">{{ taskInfo.taskName }}</text>
        </view>
@@ -25,114 +26,115 @@
          </view>
        </view>
      </view>
      <!-- å¼‚常状态选择 -->
      <view class="section-card">
        <view class="section-title">巡检状态</view>
        <view class="exception-options">
          <view
            class="exception-option"
            :class="{ active: hasException === false }"
            @click="setExceptionStatus(false)"
          >
            <u-icon name="checkmark-circle" size="20" color="#52c41a"></u-icon>
          <view class="exception-option"
                :class="{ active: hasException === false }"
                @click="setExceptionStatus(false)">
            <u-icon name="checkmark-circle"
                    size="20"
                    color="#52c41a"></u-icon>
            <text class="option-text">正常</text>
          </view>
          <view
            class="exception-option"
            :class="{ active: hasException === true }"
            @click="setExceptionStatus(true)"
          >
            <u-icon name="close-circle" size="20" color="#ff4d4f"></u-icon>
          <view class="exception-option"
                :class="{ active: hasException === true }"
                @click="setExceptionStatus(true)">
            <u-icon name="close-circle"
                    size="20"
                    color="#ff4d4f"></u-icon>
            <text class="option-text">存在异常</text>
          </view>
        </view>
      </view>
      <!-- å¼‚常描述(仅在异常时显示) -->
      <view class="section-card" v-if="hasException === true">
      <view class="section-card"
            v-if="hasException === true">
        <view class="section-title">异常描述</view>
        <textarea
          v-model="abnormalDescription"
          class="exception-textarea"
          maxlength="500"
          placeholder="请描述异常情况..."
        />
        <textarea v-model="abnormalDescription"
                  class="exception-textarea"
                  maxlength="500"
                  placeholder="请描述异常情况..." />
      </view>
      <!-- åˆ†ç±»æ ‡ç­¾é¡µï¼ˆä»…在异常时显示) -->
      <view class="section-card" v-if="hasException === true">
      <view class="section-card"
            v-if="hasException === true">
        <view class="upload-tabs">
          <view
            class="tab-item"
            :class="{ active: currentUploadType === 'before' }"
            @click="switchUploadType('before')"
          >
          <view class="tab-item"
                :class="{ active: currentUploadType === 'before' }"
                @click="switchUploadType('before')">
            ç”Ÿäº§å‰
          </view>
          <view
            class="tab-item"
            :class="{ active: currentUploadType === 'after' }"
            @click="switchUploadType('after')"
          >
          <view class="tab-item"
                :class="{ active: currentUploadType === 'after' }"
                @click="switchUploadType('after')">
            ç”Ÿäº§ä¸­
          </view>
          <view
            class="tab-item"
            :class="{ active: currentUploadType === 'issue' }"
            @click="switchUploadType('issue')"
          >
          <view class="tab-item"
                :class="{ active: currentUploadType === 'issue' }"
                @click="switchUploadType('issue')">
            ç”Ÿäº§åŽ
          </view>
        </view>
        <!-- å½“前分类的上传区域 -->
        <view class="upload-area">
          <view class="upload-buttons">
            <u-button
              type="primary"
              @click="chooseMedia('image')"
              :loading="uploading"
              :disabled="getCurrentFiles().length >= uploadConfig.limit"
              :customStyle="{ marginRight: '10px', flex: 1 }"
            >
              <u-icon name="camera" size="18" color="#fff" style="margin-right: 5px"></u-icon>
            <u-button type="primary"
                      @click="chooseMedia('image')"
                      :loading="uploading"
                      :disabled="getCurrentFiles().length >= uploadConfig.limit"
                      :customStyle="{ marginRight: '10px', flex: 1 }">
              <u-icon name="camera"
                      size="18"
                      color="#fff"
                      style="margin-right: 5px"></u-icon>
              {{ uploading ? '上传中...' : '拍照' }}
            </u-button>
            <u-button
              type="success"
              @click="chooseMedia('video')"
              :loading="uploading"
              :disabled="getCurrentFiles().length >= uploadConfig.limit"
              :customStyle="{ flex: 1 }"
            >
              <uni-icons type="videocam" size="18" color="#fff" style="margin-right: 5px"></uni-icons>
            <u-button type="success"
                      @click="chooseMedia('video')"
                      :loading="uploading"
                      :disabled="getCurrentFiles().length >= uploadConfig.limit"
                      :customStyle="{ flex: 1 }">
              <uni-icons type="videocam"
                         size="18"
                         color="#fff"
                         style="margin-right: 5px"></uni-icons>
              {{ uploading ? '上传中...' : '拍视频' }}
            </u-button>
          </view>
          <!-- ä¸Šä¼ è¿›åº¦ -->
          <view v-if="uploading" class="upload-progress">
            <u-line-progress :percentage="uploadProgress" :showText="true" activeColor="#409eff"></u-line-progress>
          <view v-if="uploading"
                class="upload-progress">
            <u-line-progress :percentage="uploadProgress"
                             :showText="true"
                             activeColor="#409eff"></u-line-progress>
          </view>
          <!-- å½“前分类的文件列表 -->
          <view v-if="getCurrentFiles().length > 0" class="file-list">
            <view v-for="(file, index) in getCurrentFiles()" :key="index" class="file-item">
          <view v-if="getCurrentFiles().length > 0"
                class="file-list">
            <view v-for="(file, index) in getCurrentFiles()"
                  :key="index"
                  class="file-item">
              <view class="file-preview-container">
                <image
                  v-if="file.type === 'image' || (file.type !== 'video' && !file.type)"
                  :src="file.url || file.tempFilePath || file.path || file.downloadUrl"
                  class="file-preview"
                  mode="aspectFill"
                />
                <view v-else-if="file.type === 'video'" class="video-preview">
                  <uni-icons type="videocam" size="18" color="#fff" style="margin-right: 5px"></uni-icons>
                <image v-if="file.type === 'image' || (file.type !== 'video' && !file.type)"
                       :src="file.url || file.tempFilePath || file.path || file.downloadUrl"
                       class="file-preview"
                       mode="aspectFill" />
                <view v-else-if="file.type === 'video'"
                      class="video-preview">
                  <uni-icons type="videocam"
                             size="18"
                             color="#fff"
                             style="margin-right: 5px"></uni-icons>
                  <text class="video-text">视频</text>
                </view>
                <!-- åˆ é™¤æŒ‰é’® -->
                <view class="delete-btn" @click="removeFile(index)">
                  <u-icon name="close" size="12" color="#fff"></u-icon>
                <view class="delete-btn"
                      @click="removeFile(index)">
                  <u-icon name="close"
                          size="12"
                          color="#fff"></u-icon>
                </view>
              </view>
              <view class="file-info">
@@ -141,12 +143,11 @@
              </view>
            </view>
          </view>
          <view v-if="getCurrentFiles().length === 0" class="empty-state">
          <view v-if="getCurrentFiles().length === 0"
                class="empty-state">
            <text>请选择要上传的{{ getUploadTypeText() }}图片或视频</text>
          </view>
        </view>
        <!-- ç»Ÿè®¡ä¿¡æ¯ -->
        <view class="upload-summary">
          <text class="summary-text">
@@ -156,730 +157,826 @@
          </text>
        </view>
      </view>
      <!-- æ­£å¸¸çŠ¶æ€æç¤º -->
      <view class="normal-tip-card" v-if="hasException === false">
        <u-icon name="info-circle" size="60" color="#52c41a"></u-icon>
      <view class="normal-tip-card"
            v-if="hasException === false">
        <u-icon name="info-circle"
                size="60"
                color="#52c41a"></u-icon>
        <text class="tip-text">设备运行正常,无需上传照片</text>
      </view>
    </view>
    <!-- åº•部按钮 -->
    <view class="footer-buttons">
      <u-button @click="goBack" :customStyle="{ marginRight: '10px' }">取消</u-button>
      <u-button v-if="hasException === true" type="warning" @click="goToRepair" :customStyle="{ marginRight: '10px' }">
      <u-button @click="goBack"
                :customStyle="{ marginRight: '10px' }">取消</u-button>
      <u-button v-if="hasException === true"
                type="warning"
                @click="goToRepair"
                :customStyle="{ marginRight: '10px' }">
        æ–°å¢žæŠ¥ä¿®
      </u-button>
      <u-button type="primary" @click="submitUpload">提交</u-button>
      <u-button type="primary"
                @click="submitUpload">提交</u-button>
    </view>
  </view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import PageHeader from '@/components/PageHeader.vue';
import { uploadInspectionTask } from '@/api/inspectionManagement';
import { getToken } from '@/utils/auth';
import config from '@/config';
  import { ref, computed, onMounted } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { uploadInspectionTask } from "@/api/inspectionManagement";
  import { getToken } from "@/utils/auth";
  import config from "@/config";
// ä»»åŠ¡ä¿¡æ¯
const taskInfo = ref(null);
  // ä»»åŠ¡ä¿¡æ¯
  const taskInfo = ref(null);
// ä¸Šä¼ ç›¸å…³çŠ¶æ€
const uploading = ref(false);
const uploadProgress = ref(0);
  // ä¸Šä¼ ç›¸å…³çŠ¶æ€
  const uploading = ref(false);
  const uploadProgress = ref(0);
// ä¸‰ä¸ªåˆ†ç±»çš„上传状态
const beforeModelValue = ref([]); // ç”Ÿäº§å‰
const afterModelValue = ref([]); // ç”Ÿäº§ä¸­
const issueModelValue = ref([]); // ç”Ÿäº§åŽ
  // ä¸‰ä¸ªåˆ†ç±»çš„上传状态
  const beforeModelValue = ref([]); // ç”Ÿäº§å‰
  const afterModelValue = ref([]); // ç”Ÿäº§ä¸­
  const issueModelValue = ref([]); // ç”Ÿäº§åŽ
// å½“前激活的上传类型
const currentUploadType = ref('before'); // 'before', 'after', 'issue'
  // å½“前激活的上传类型
  const currentUploadType = ref("before"); // 'before', 'after', 'issue'
// å¼‚常状态
const hasException = ref(null); // null: æœªé€‰æ‹©, true: å­˜åœ¨å¼‚常, false: æ­£å¸¸
// å¼‚常描述
const abnormalDescription = ref('');
  // å¼‚常状态
  const hasException = ref(null); // null: æœªé€‰æ‹©, true: å­˜åœ¨å¼‚常, false: æ­£å¸¸
  // å¼‚常描述
  const abnormalDescription = ref("");
// ä¸Šä¼ é…ç½®
const uploadConfig = {
  action: '/file/upload',
  limit: 10,
  fileSize: 50, // MB
  fileType: ['jpg', 'jpeg', 'png', 'mp4', 'mov'],
  maxVideoDuration: 60, // ç§’
};
  // ä¸Šä¼ é…ç½®
  const uploadConfig = {
    action: "/common/upload",
    limit: 10,
    fileSize: 50, // MB
    fileType: ["jpg", "jpeg", "png", "mp4", "mov"],
    maxVideoDuration: 60, // ç§’
  };
// è®¡ç®—上传URL
const uploadFileUrl = computed(() => {
  const baseUrl = config.baseUrl;
  return baseUrl + uploadConfig.action;
});
  // è®¡ç®—上传URL
  const uploadFileUrl = computed(() => {
    const baseUrl = config.baseUrl;
    return baseUrl + uploadConfig.action;
  });
// é¡µé¢åŠ è½½
onLoad((options) => {
  if (options.taskInfo) {
    try {
      taskInfo.value = JSON.parse(decodeURIComponent(options.taskInfo));
    } catch (e) {
      console.error('解析任务信息失败:', e);
  // é¡µé¢åŠ è½½
  onLoad(options => {
    if (options.taskInfo) {
      try {
        const info = JSON.parse(decodeURIComponent(options.taskInfo));
        taskInfo.value = info;
        // å›žæ˜¾é€»è¾‘:从 taskInfo ä¸­æ¢å¤å·²ä¸Šä¼ çš„æ–‡ä»¶
        const mapFiles = list => {
          if (!list || !Array.isArray(list)) return [];
          return list.map(item => {
            // å¤„理 URL,去除可能的空格
            const finalUrl = (item.url || item.previewURL || "").trim();
            // è‡ªåŠ¨æŽ¨æ–­æ–‡ä»¶ç±»åž‹
            let fileType = item.type;
            if (!fileType && item.contentType) {
              fileType = item.contentType.startsWith("video") ? "video" : "image";
            } else if (!fileType) {
              fileType = "image"; // é»˜è®¤å›¾ç‰‡
            }
            return {
              ...item,
              url: finalUrl,
              name: item.name || item.originalFilename,
              tempId: item.tempId || item.id || item.tempFileId,
              size: item.size || item.byteSize || 0, // æ˜ å°„大小字段
              type: fileType,
              status: "success",
            };
          });
        };
        // æ ¹æ®ç”¨æˆ·è¦æ±‚映射:AfterDTO(生产前), DTO(生产中), BeforeDTO(生产后)
        if (
          info.commonFileListAfterVO &&
          Array.isArray(info.commonFileListAfterVO)
        ) {
          beforeModelValue.value = mapFiles(info.commonFileListAfterVO);
        }
        console.log(beforeModelValue.value, "beforeModelValue");
        if (info.commonFileListVO && Array.isArray(info.commonFileListVO)) {
          afterModelValue.value = mapFiles(info.commonFileListVO);
        }
        if (
          info.commonFileListBeforeVO &&
          Array.isArray(info.commonFileListBeforeVO)
        ) {
          issueModelValue.value = mapFiles(info.commonFileListBeforeVO);
        }
        // å¦‚果有异常描述,也恢复
        if (info.abnormalDescription) {
          abnormalDescription.value = info.abnormalDescription;
        }
        // å¦‚果有异常状态,也恢复
        if (info.hasException !== undefined && info.hasException !== null) {
          hasException.value = info.hasException;
        } else if (
          info.inspectionResult !== undefined &&
          info.inspectionResult !== null
        ) {
          // 0-异常,1-正常
          hasException.value = String(info.inspectionResult) === "0";
        }
        // è‡ªåŠ¨å…œåº•ï¼šå¦‚æžœå­˜åœ¨å·²ä¸Šä¼ æ–‡ä»¶ï¼Œåˆ™å¿…ç„¶æ˜¯å¼‚å¸¸çŠ¶æ€ï¼Œç¡®ä¿ UI æ­£å¸¸æ˜¾ç¤º
        if (
          !hasException.value &&
          (beforeModelValue.value.length > 0 ||
            afterModelValue.value.length > 0 ||
            issueModelValue.value.length > 0)
        ) {
          hasException.value = true;
        }
      } catch (e) {
        console.error("解析任务信息失败:", e);
      }
    }
  }
});
  });
// è¿”回上一页
const goBack = () => {
  uni.navigateBack();
};
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
// åˆ‡æ¢ä¸Šä¼ ç±»åž‹
const switchUploadType = (type) => {
  currentUploadType.value = type;
};
  // åˆ‡æ¢ä¸Šä¼ ç±»åž‹
  const switchUploadType = type => {
    currentUploadType.value = type;
  };
// èŽ·å–å½“å‰åˆ†ç±»çš„æ–‡ä»¶åˆ—è¡¨
const getCurrentFiles = () => {
  switch (currentUploadType.value) {
    case 'before':
      return beforeModelValue.value || [];
    case 'after':
      return afterModelValue.value || [];
    case 'issue':
      return issueModelValue.value || [];
    default:
      return [];
  }
};
  // èŽ·å–å½“å‰åˆ†ç±»çš„æ–‡ä»¶åˆ—è¡¨
  const getCurrentFiles = () => {
    switch (currentUploadType.value) {
      case "before":
        return beforeModelValue.value || [];
      case "after":
        return afterModelValue.value || [];
      case "issue":
        return issueModelValue.value || [];
      default:
        return [];
    }
  };
// èŽ·å–ä¸Šä¼ ç±»åž‹æ–‡æœ¬
const getUploadTypeText = () => {
  switch (currentUploadType.value) {
    case 'before':
      return '生产前';
    case 'after':
      return '生产中';
    case 'issue':
      return '生产后';
    default:
      return '';
  }
};
  // èŽ·å–ä¸Šä¼ ç±»åž‹æ–‡æœ¬
  const getUploadTypeText = () => {
    switch (currentUploadType.value) {
      case "before":
        return "生产前";
      case "after":
        return "生产中";
      case "issue":
        return "生产后";
      default:
        return "";
    }
  };
// è®¾ç½®å¼‚常状态
const setExceptionStatus = (status) => {
  hasException.value = status;
};
  // è®¾ç½®å¼‚常状态
  const setExceptionStatus = status => {
    hasException.value = status;
  };
// è·³è½¬åˆ°æ–°å¢žæŠ¥ä¿®é¡µé¢
const goToRepair = () => {
  try {
    const taskData = {
      taskId: taskInfo.value?.taskId || taskInfo.value?.id,
      taskName: taskInfo.value?.taskName,
      inspectionLocation: taskInfo.value?.inspectionLocation,
      inspector: taskInfo.value?.inspector,
      uploadedFiles: {
        before: beforeModelValue.value,
        after: afterModelValue.value,
        issue: issueModelValue.value,
      },
    };
  // è·³è½¬åˆ°æ–°å¢žæŠ¥ä¿®é¡µé¢
  const goToRepair = () => {
    try {
      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, // 0-异常,1-正常
        commonFileListAfterDTO: beforeModelValue.value,
        commonFileListDTO: afterModelValue.value,
        commonFileListBeforeDTO: issueModelValue.value,
        uploadedFiles: {
          before: beforeModelValue.value,
          after: afterModelValue.value,
          issue: issueModelValue.value,
        },
      };
    uni.setStorageSync('repairTaskInfo', JSON.stringify(taskData));
      uni.setStorageSync("repairTaskInfo", JSON.stringify(taskData));
    uni.navigateTo({
      url: '/pages/equipmentManagement/repair/add',
    });
  } catch (error) {
    console.error('跳转报修页面失败:', error);
    uni.showToast({
      title: '跳转失败,请重试',
      icon: 'error',
    });
  }
};
// æäº¤ä¸Šä¼ 
const submitUpload = async () => {
  try {
    // æ£€æŸ¥æ˜¯å¦é€‰æ‹©äº†å¼‚常状态
    if (hasException.value === null) {
      uni.navigateTo({
        url: "/pages/equipmentManagement/repair/add",
      });
    } catch (error) {
      console.error("跳转报修页面失败:", error);
      uni.showToast({
        title: '请选择巡检状态',
        icon: 'none',
        title: "跳转失败,请重试",
        icon: "error",
      });
    }
  };
  // æäº¤ä¸Šä¼ 
  const submitUpload = async () => {
    try {
      // æ£€æŸ¥æ˜¯å¦é€‰æ‹©äº†å¼‚常状态
      if (hasException.value === null) {
        uni.showToast({
          title: "请选择巡检状态",
          icon: "none",
        });
        return;
      }
      // å¦‚果是异常状态,检查是否有上传文件和描述
      if (hasException.value === true) {
        const totalFiles =
          beforeModelValue.value.length +
          afterModelValue.value.length +
          issueModelValue.value.length;
        if (totalFiles === 0) {
          uni.showToast({
            title: "请上传异常照片",
            icon: "none",
          });
          return;
        }
        // æ£€æŸ¥æ˜¯å¦å¡«å†™äº†å¼‚常描述
        if (!abnormalDescription.value.trim()) {
          uni.showToast({
            title: "请填写异常描述",
            icon: "none",
          });
          return;
        }
      }
      // æ˜¾ç¤ºæäº¤ä¸­çš„加载提示
      uni.showLoading({
        title: "提交中...",
        mask: true,
      });
      // æŒ‰ç…§é€»è¾‘合并所有分类的文件用于提取ID
      const allFiles = [
        ...beforeModelValue.value,
        ...afterModelValue.value,
        ...issueModelValue.value,
      ];
      // ä¼ ç»™åŽç«¯çš„临时文件ID列表
      let tempFileIds = [];
      if (allFiles.length > 0) {
        tempFileIds = allFiles
          .map(item => item?.tempId ?? item?.tempFileId ?? item?.id)
          .filter(v => v !== undefined && v !== null && v !== "");
      }
      // æäº¤æ•°æ®
      const submitData = {
        ...taskInfo.value,
        commonFileListAfterDTO: beforeModelValue.value, // ç”Ÿäº§å‰
        commonFileListDTO: afterModelValue.value, // ç”Ÿäº§ä¸­
        commonFileListBeforeDTO: issueModelValue.value, // ç”Ÿäº§åŽ
        hasException: hasException.value,
        inspectionResult: hasException.value ? 0 : 1, // 0-异常,1-正常
        abnormalDescription: abnormalDescription.value,
        tempFileIds: tempFileIds,
      };
      const result = await uploadInspectionTask(submitData);
      // æ£€æŸ¥æäº¤ç»“æžœ
      if (result && (result.code === 200 || result.success)) {
        uni.hideLoading();
        uni.showToast({
          title: "提交成功",
          icon: "success",
        });
        // è¿”回列表页并刷新
        setTimeout(() => {
          uni.navigateBack();
        }, 500);
      } else {
        uni.hideLoading();
        uni.showToast({
          title: result?.msg || result?.message || "提交失败",
          icon: "error",
        });
      }
    } catch (error) {
      console.error("提交上传失败:", error);
      uni.hideLoading();
      uni.showToast({
        title: error?.message || "提交失败",
        icon: "error",
      });
    }
  };
  // æ ¼å¼åŒ–文件大小
  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++;
    }
    return `${fileSize.toFixed(2)} ${units[index]}`;
  };
  // æ‹ç…§/拍视频
  const chooseMedia = type => {
    if (getCurrentFiles().length >= uploadConfig.limit) {
      uni.showToast({
        title: `最多只能选择${uploadConfig.limit}个文件`,
        icon: "none",
      });
      return;
    }
    // å¦‚果是异常状态,检查是否有上传文件和描述
    if (hasException.value === true) {
      const totalFiles = beforeModelValue.value.length + afterModelValue.value.length + issueModelValue.value.length;
      if (totalFiles === 0) {
        uni.showToast({
          title: '请上传异常照片',
          icon: 'none',
        });
        return;
      }
      // æ£€æŸ¥æ˜¯å¦å¡«å†™äº†å¼‚常描述
      if (!abnormalDescription.value.trim()) {
        uni.showToast({
          title: '请填写异常描述',
          icon: 'none',
        });
        return;
      }
    }
    const remaining = uploadConfig.limit - getCurrentFiles().length;
    // æ˜¾ç¤ºæäº¤ä¸­çš„加载提示
    uni.showLoading({
      title: '提交中...',
      mask: true,
    });
    // ä¼˜å…ˆä½¿ç”¨ chooseMedia
    if (typeof uni.chooseMedia === "function") {
      uni.chooseMedia({
        count: Math.min(remaining, 1),
        mediaType: [type || "image"],
        sizeType: ["compressed", "original"],
        sourceType: ["camera"],
        success: res => {
          try {
            const files = res?.tempFiles || [];
            if (!files.length) throw new Error("未获取到文件");
    // æŒ‰ç…§é€»è¾‘合并所有分类的文件
    let arr = [];
    if (beforeModelValue.value.length > 0) {
      arr.push(...beforeModelValue.value);
    }
    if (afterModelValue.value.length > 0) {
      arr.push(...afterModelValue.value);
    }
    if (issueModelValue.value.length > 0) {
      arr.push(...issueModelValue.value);
    }
    // ä¼ ç»™åŽç«¯çš„临时文件ID列表
    let tempFileIds = [];
    if (arr !== null && arr.length > 0) {
      tempFileIds = arr
        .map((item) => item?.tempId ?? item?.tempFileId ?? item?.id)
        .filter((v) => v !== undefined && v !== null && v !== '');
    }
    // æäº¤æ•°æ®
    const submitData = {
      ...taskInfo.value,
      storageBlobDTO: arr,
      hasException: hasException.value,
      abnormalDescription: abnormalDescription.value,
      tempFileIds: tempFileIds,
    };
    const result = await uploadInspectionTask(submitData);
    // æ£€æŸ¥æäº¤ç»“æžœ
    if (result && (result.code === 200 || result.success)) {
      uni.hideLoading();
      uni.showToast({
        title: '提交成功',
        icon: 'success',
      });
      // è¿”回列表页并刷新
      setTimeout(() => {
        uni.navigateBack();
      }, 500);
    } else {
      uni.hideLoading();
      uni.showToast({
        title: result?.msg || result?.message || '提交失败',
        icon: 'error',
      });
    }
  } catch (error) {
    console.error('提交上传失败:', error);
    uni.hideLoading();
    uni.showToast({
      title: error?.message || '提交失败',
      icon: 'error',
    });
  }
};
// æ ¼å¼åŒ–文件大小
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++;
  }
  return `${fileSize.toFixed(2)} ${units[index]}`;
};
// æ‹ç…§/拍视频
const chooseMedia = (type) => {
  if (getCurrentFiles().length >= uploadConfig.limit) {
    uni.showToast({
      title: `最多只能选择${uploadConfig.limit}个文件`,
      icon: 'none',
    });
    return;
  }
  const remaining = uploadConfig.limit - getCurrentFiles().length;
  // ä¼˜å…ˆä½¿ç”¨ chooseMedia
  if (typeof uni.chooseMedia === 'function') {
    uni.chooseMedia({
      count: Math.min(remaining, 1),
      mediaType: [type || 'image'],
      sizeType: ['compressed', 'original'],
      sourceType: ['camera'],
      success: (res) => {
        try {
          const files = res?.tempFiles || [];
          if (!files.length) throw new Error('未获取到文件');
          files.forEach((tf, idx) => {
            const filePath = tf.tempFilePath || tf.path || '';
            const fileType = tf.fileType || type || 'image';
            const ext = fileType === 'video' ? 'mp4' : 'jpg';
            const file = {
              tempFilePath: filePath,
              path: filePath,
              type: fileType,
              name: `${fileType}_${Date.now()}_${idx}.${ext}`,
              size: tf.size || 0,
              duration: tf.duration || 0,
              createTime: Date.now(),
            };
            uploadFile(file);
          });
        } catch (err) {
          uni.showToast({ title: err.message || '处理文件失败', icon: 'none' });
        }
      },
      fail: (err) => {
        console.error('选择媒体失败:', err);
        uni.showToast({ title: '选择失败', icon: 'none' });
      },
    });
  } else {
    // é™çº§æ–¹æ¡ˆ
    if (type === 'video') {
      uni.chooseVideo({
        sourceType: ['camera'],
        success: (res) => {
          const file = {
            tempFilePath: res.tempFilePath,
            path: res.tempFilePath,
            type: 'video',
            name: `video_${Date.now()}.mp4`,
            size: res.size || 0,
            duration: res.duration || 0,
            createTime: Date.now(),
          };
          uploadFile(file);
        },
        fail: () => {
          uni.showToast({ title: '选择视频失败', icon: 'none' });
        },
      });
    } else {
      uni.chooseImage({
        count: Math.min(remaining, 9),
        sizeType: ['compressed'],
        sourceType: ['camera'],
        success: (res) => {
          const list = res.tempFilePaths || res.tempFiles || [];
          list.forEach((src, idx) => {
            const path = typeof src === 'string' ? src : src.path;
            const file = {
              tempFilePath: path,
              path: path,
              type: 'image',
              name: `image_${Date.now()}_${idx}.jpg`,
              size: 0,
              createTime: Date.now(),
            };
            uploadFile(file);
          });
        },
        fail: () => {
          uni.showToast({ title: '选择图片失败', icon: 'none' });
        },
      });
    }
  }
};
// ä¸Šä¼ å•个文件
const uploadFile = (file) => {
  const token = getToken();
  if (!token) {
    uni.showToast({ title: '用户未登录', icon: 'none' });
    return;
  }
  uploading.value = true;
  uploadProgress.value = 0;
  const uploadTask = uni.uploadFile({
    url: uploadFileUrl.value,
    filePath: file.tempFilePath,
    name: 'file',
    header: {
      Authorization: `Bearer ${token}`,
    },
    formData: {
      type: getTabType(),
    },
    success: (res) => {
      try {
        const data = JSON.parse(res.data);
        if (data.code === 200) {
          const uploadedFile = {
            ...file,
            url: data.data.url,
            tempId: data.data.tempId || data.data.id,
            status: 'success',
          };
          // æ ¹æ®å½“前类型添加到对应数组
          if (currentUploadType.value === 'before') {
            beforeModelValue.value.push(uploadedFile);
          } else if (currentUploadType.value === 'after') {
            afterModelValue.value.push(uploadedFile);
          } else if (currentUploadType.value === 'issue') {
            issueModelValue.value.push(uploadedFile);
            files.forEach((tf, idx) => {
              const filePath = tf.tempFilePath || tf.path || "";
              const fileType = tf.fileType || type || "image";
              const ext = fileType === "video" ? "mp4" : "jpg";
              const file = {
                tempFilePath: filePath,
                path: filePath,
                type: fileType,
                name: `${fileType}_${Date.now()}_${idx}.${ext}`,
                size: tf.size || 0,
                duration: tf.duration || 0,
                createTime: Date.now(),
              };
              uploadFile(file);
            });
          } catch (err) {
            uni.showToast({ title: err.message || "处理文件失败", icon: "none" });
          }
          uni.showToast({ title: '上传成功', icon: 'success' });
        } else {
          uni.showToast({ title: data.msg || '上传失败', icon: 'none' });
        }
      } catch (e) {
        uni.showToast({ title: '解析响应失败', icon: 'none' });
        },
        fail: err => {
          console.error("选择媒体失败:", err);
          uni.showToast({ title: "选择失败", icon: "none" });
        },
      });
    } else {
      // é™çº§æ–¹æ¡ˆ
      if (type === "video") {
        uni.chooseVideo({
          sourceType: ["camera"],
          success: res => {
            const file = {
              tempFilePath: res.tempFilePath,
              path: res.tempFilePath,
              type: "video",
              name: `video_${Date.now()}.mp4`,
              size: res.size || 0,
              duration: res.duration || 0,
              createTime: Date.now(),
            };
            uploadFile(file);
          },
          fail: () => {
            uni.showToast({ title: "选择视频失败", icon: "none" });
          },
        });
      } else {
        uni.chooseImage({
          count: Math.min(remaining, 9),
          sizeType: ["compressed"],
          sourceType: ["camera"],
          success: res => {
            const list = res.tempFilePaths || res.tempFiles || [];
            list.forEach((src, idx) => {
              const path = typeof src === "string" ? src : src.path;
              const file = {
                tempFilePath: path,
                path: path,
                type: "image",
                name: `image_${Date.now()}_${idx}.jpg`,
                size: 0,
                createTime: Date.now(),
              };
              uploadFile(file);
            });
          },
          fail: () => {
            uni.showToast({ title: "选择图片失败", icon: "none" });
          },
        });
      }
    },
    fail: (err) => {
      console.error('上传失败:', err);
      uni.showToast({ title: '上传失败', icon: 'none' });
    },
    complete: () => {
      uploading.value = false;
    },
  });
    }
  };
  // ç›‘听上传进度
  uploadTask.onProgressUpdate((res) => {
    uploadProgress.value = res.progress;
  });
};
  // ä¸Šä¼ å•个文件
  const uploadFile = file => {
    const token = getToken();
    if (!token) {
      uni.showToast({ title: "用户未登录", icon: "none" });
      return;
    }
// èŽ·å–type值
const getTabType = () => {
  switch (currentUploadType.value) {
    case 'before':
      return 10;
    case 'after':
      return 11;
    case 'issue':
      return 12;
    default:
      return 10;
  }
};
    uploading.value = true;
    uploadProgress.value = 0;
// åˆ é™¤æ–‡ä»¶
const removeFile = (index) => {
  const files = getCurrentFiles();
  files.splice(index, 1);
};
    const uploadTask = uni.uploadFile({
      url: uploadFileUrl.value,
      filePath: file.tempFilePath,
      name: "files",
      header: {
        Authorization: `Bearer ${token}`,
      },
      formData: {
        type: getTabType(),
      },
      success: res => {
        try {
          const data = JSON.parse(res.data);
          if (data.code === 200) {
            // å…¼å®¹ CommonUpload.vue çš„处理逻辑
            const resultData = Array.isArray(data.data)
              ? data.data[0]
              : data.data;
            // å¤„理 url å’Œ name èµ‹å€¼
            const finalUrl = resultData.url || resultData.previewURL;
            const finalName = resultData.name || resultData.originalFilename;
            const finalId =
              resultData.tempId || resultData.id || resultData.tempFileId;
            const uploadedFile = {
              ...file,
              ...resultData, // åŒ…含后端返回的所有字段
              url: finalUrl,
              name: finalName,
              tempId: finalId,
              status: "success",
            };
            // æ ¹æ®å½“前类型添加到对应数组
            if (currentUploadType.value === "before") {
              beforeModelValue.value.push(uploadedFile);
            } else if (currentUploadType.value === "after") {
              afterModelValue.value.push(uploadedFile);
            } else if (currentUploadType.value === "issue") {
              issueModelValue.value.push(uploadedFile);
            }
            uni.showToast({ title: "上传成功", icon: "success" });
          } else {
            uni.showToast({ title: data.msg || "上传失败", icon: "none" });
          }
        } catch (e) {
          uni.showToast({ title: "解析响应失败", icon: "none" });
        }
      },
      fail: err => {
        console.error("上传失败:", err);
        uni.showToast({ title: "上传失败", icon: "none" });
      },
      complete: () => {
        uploading.value = false;
      },
    });
    // ç›‘听上传进度
    uploadTask.onProgressUpdate(res => {
      uploadProgress.value = res.progress;
    });
  };
  // èŽ·å–type值
  const getTabType = () => {
    switch (currentUploadType.value) {
      case "before":
        return 10;
      case "after":
        return 11;
      case "issue":
        return 12;
      default:
        return 10;
    }
  };
  // åˆ é™¤æ–‡ä»¶
  const removeFile = index => {
    const files = getCurrentFiles();
    files.splice(index, 1);
  };
</script>
<style scoped>
.inspection-upload-page {
  min-height: 100vh;
  background-color: #f5f5f5;
  padding-bottom: 80px;
}
  .inspection-upload-page {
    min-height: 100vh;
    background-color: #f5f5f5;
    padding-bottom: 80px;
  }
.upload-content {
  padding: 15px;
}
  .upload-content {
    padding: 15px;
  }
/* ä»»åŠ¡ä¿¡æ¯å¡ç‰‡ */
.task-info-card {
  background: #fff;
  border-radius: 12px;
  padding: 15px;
  margin-bottom: 15px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
  /* ä»»åŠ¡ä¿¡æ¯å¡ç‰‡ */
  .task-info-card {
    background: #fff;
    border-radius: 12px;
    padding: 15px;
    margin-bottom: 15px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
.task-info-header {
  margin-bottom: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid #f0f0f0;
}
  .task-info-header {
    margin-bottom: 12px;
    padding-bottom: 12px;
    border-bottom: 1px solid #f0f0f0;
  }
.task-name {
  font-size: 16px;
  font-weight: 600;
  color: #333;
}
  .task-name {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
.task-info-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
  .task-info-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
.info-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
  .info-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
.info-label {
  font-size: 13px;
  color: #999;
}
  .info-label {
    font-size: 13px;
    color: #999;
  }
.info-value {
  font-size: 13px;
  color: #666;
}
  .info-value {
    font-size: 13px;
    color: #666;
  }
/* é€šç”¨å¡ç‰‡æ ·å¼ */
.section-card {
  background: #fff;
  border-radius: 12px;
  padding: 15px;
  margin-bottom: 15px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
  /* é€šç”¨å¡ç‰‡æ ·å¼ */
  .section-card {
    background: #fff;
    border-radius: 12px;
    padding: 15px;
    margin-bottom: 15px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
.section-title {
  font-size: 14px;
  font-weight: 600;
  color: #333;
  margin-bottom: 12px;
}
  .section-title {
    font-size: 14px;
    font-weight: 600;
    color: #333;
    margin-bottom: 12px;
  }
/* å¼‚常状态选择 */
.exception-options {
  display: flex;
  gap: 12px;
}
  /* å¼‚常状态选择 */
  .exception-options {
    display: flex;
    gap: 12px;
  }
.exception-option {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 14px 16px;
  background: #f8f9fa;
  border: 2px solid #e9ecef;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.3s ease;
}
  .exception-option {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 14px 16px;
    background: #f8f9fa;
    border: 2px solid #e9ecef;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.3s ease;
  }
.exception-option.active {
  border-color: #409eff;
  background: #f0f8ff;
}
  .exception-option.active {
    border-color: #409eff;
    background: #f0f8ff;
  }
.option-text {
  font-size: 14px;
  color: #333;
  font-weight: 500;
}
  .option-text {
    font-size: 14px;
    color: #333;
    font-weight: 500;
  }
/* å¼‚常描述 */
.exception-textarea {
  width: 100%;
  min-height: 100px;
  padding: 12px;
  background: #f8f9fa;
  border: 1px solid #e9ecef;
  border-radius: 8px;
  font-size: 14px;
  color: #333;
  resize: none;
  box-sizing: border-box;
}
  /* å¼‚常描述 */
  .exception-textarea {
    width: 100%;
    min-height: 100px;
    padding: 12px;
    background: #f8f9fa;
    border: 1px solid #e9ecef;
    border-radius: 8px;
    font-size: 14px;
    color: #333;
    resize: none;
    box-sizing: border-box;
  }
.exception-textarea:focus {
  outline: none;
  border-color: #409eff;
  background: #fff;
}
  .exception-textarea:focus {
    outline: none;
    border-color: #409eff;
    background: #fff;
  }
/* åˆ†ç±»æ ‡ç­¾é¡µ */
.upload-tabs {
  display: flex;
  gap: 10px;
  margin-bottom: 15px;
}
  /* åˆ†ç±»æ ‡ç­¾é¡µ */
  .upload-tabs {
    display: flex;
    gap: 10px;
    margin-bottom: 15px;
  }
.tab-item {
  flex: 1;
  padding: 10px;
  text-align: center;
  background: #f5f5f5;
  border-radius: 6px;
  font-size: 13px;
  color: #666;
  cursor: pointer;
  transition: all 0.3s;
}
  .tab-item {
    flex: 1;
    padding: 10px;
    text-align: center;
    background: #f5f5f5;
    border-radius: 6px;
    font-size: 13px;
    color: #666;
    cursor: pointer;
    transition: all 0.3s;
  }
.tab-item.active {
  background: #409eff;
  color: #fff;
}
  .tab-item.active {
    background: #409eff;
    color: #fff;
  }
/* ä¸Šä¼ åŒºåŸŸ */
.upload-area {
  padding: 10px 0;
}
  /* ä¸Šä¼ åŒºåŸŸ */
  .upload-area {
    padding: 10px 0;
  }
.upload-buttons {
  display: flex;
  gap: 10px;
  margin-bottom: 15px;
}
  .upload-buttons {
    display: flex;
    gap: 10px;
    margin-bottom: 15px;
  }
.upload-progress {
  margin-bottom: 15px;
}
  .upload-progress {
    margin-bottom: 15px;
  }
/* æ–‡ä»¶åˆ—表 */
.file-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
  /* æ–‡ä»¶åˆ—表 */
  .file-list {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
  }
.file-item {
  width: calc(33.33% - 7px);
}
  .file-item {
    width: calc(33.33% - 7px);
  }
.file-preview-container {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 8px;
  overflow: hidden;
  background: #f5f5f5;
}
  .file-preview-container {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
    border-radius: 8px;
    overflow: hidden;
    background: #f5f5f5;
  }
.file-preview {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
  .file-preview {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
.video-preview {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: #333;
}
  .video-preview {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: #333;
  }
.video-text {
  font-size: 12px;
  color: #fff;
  margin-top: 5px;
}
  .video-text {
    font-size: 12px;
    color: #fff;
    margin-top: 5px;
  }
.delete-btn {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 22px;
  height: 22px;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}
  .delete-btn {
    position: absolute;
    top: 5px;
    right: 5px;
    width: 22px;
    height: 22px;
    background: rgba(0, 0, 0, 0.5);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
  }
.file-info {
  margin-top: 5px;
}
  .file-info {
    margin-top: 5px;
  }
.file-name {
  display: block;
  font-size: 11px;
  color: #666;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
  .file-name {
    display: block;
    font-size: 11px;
    color: #666;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
.file-size {
  display: block;
  font-size: 10px;
  color: #999;
  margin-top: 2px;
}
  .file-size {
    display: block;
    font-size: 10px;
    color: #999;
    margin-top: 2px;
  }
.empty-state {
  text-align: center;
  padding: 30px;
  color: #999;
  font-size: 13px;
}
  .empty-state {
    text-align: center;
    padding: 30px;
    color: #999;
    font-size: 13px;
  }
/* ç»Ÿè®¡ä¿¡æ¯ */
.upload-summary {
  margin-top: 15px;
  padding: 10px;
  background: #f8f9fa;
  border-radius: 6px;
  border-left: 3px solid #409eff;
}
  /* ç»Ÿè®¡ä¿¡æ¯ */
  .upload-summary {
    margin-top: 15px;
    padding: 10px;
    background: #f8f9fa;
    border-radius: 6px;
    border-left: 3px solid #409eff;
  }
.summary-text {
  font-size: 12px;
  color: #666;
}
  .summary-text {
    font-size: 12px;
    color: #666;
  }
/* æ­£å¸¸çŠ¶æ€æç¤º */
.normal-tip-card {
  background: #f6ffed;
  border: 2px dashed #b7eb8f;
  border-radius: 12px;
  padding: 50px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-bottom: 15px;
}
  /* æ­£å¸¸çŠ¶æ€æç¤º */
  .normal-tip-card {
    background: #f6ffed;
    border: 2px dashed #b7eb8f;
    border-radius: 12px;
    padding: 50px 20px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    margin-bottom: 15px;
  }
.normal-tip-card .tip-text {
  margin-top: 15px;
  font-size: 16px;
  color: #52c41a;
  font-weight: 500;
}
  .normal-tip-card .tip-text {
    margin-top: 15px;
    font-size: 16px;
    color: #52c41a;
    font-weight: 500;
  }
/* åº•部按钮 */
.footer-buttons {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  padding: 15px;
  background: #fff;
  border-top: 1px solid #f0f0f0;
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
}
  /* åº•部按钮 */
  .footer-buttons {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    display: flex;
    padding: 15px;
    background: #fff;
    border-top: 1px solid #f0f0f0;
    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
  }
</style>