src/pages/equipmentManagement/repair/add.vue
@@ -62,11 +62,19 @@
          </template>
        </u-form-item>
        <u-form-item label="报修人"
                     prop="repairName"
                     prop="maintenanceName"
                     required
                     border-bottom>
          <u-input v-model="form.repairName"
          <u-input v-model="form.maintenanceName"
                   placeholder="请输入报修人"
                   clearable />
        </u-form-item>
        <u-form-item label="验收人"
                     prop="acceptanceName"
                     required
                     border-bottom>
          <u-input v-model="form.acceptanceName"
                   placeholder="请输入验收人"
                   clearable />
        </u-form-item>
        <u-form-item label="故障现象"
@@ -80,6 +88,34 @@
                      count
                      maxlength="200" />
        </u-form-item>
      <u-form-item label="故障图片"
                   border-bottom>
        <view class="repair-image-upload">
          <u-button type="primary"
                    @click="chooseRepairImage"
                    :loading="uploading"
                    :disabled="uploading">
            {{ uploading ? "上传中..." : "上传故障图片" }}
          </u-button>
          <view v-if="repairImageList.length"
                class="repair-image-list">
            <view v-for="(file, index) in repairImageList"
                  :key="file.id || index"
                  class="repair-image-item">
              <image :src="getFileAccessUrl(file)"
                     mode="aspectFill"
                     class="repair-image-preview"
                     @click="previewRepairImage(index)" />
              <view class="repair-image-delete"
                    @click="removeRepairImage(file, index)">
                <u-icon name="close"
                        size="12"
                        color="#fff" />
              </view>
            </view>
          </view>
        </view>
      </u-form-item>
      </u-cell-group>
      <!-- 提交按钮 -->
      <view class="footer-btns">
@@ -108,16 +144,23 @@
<script setup>
  import { ref, computed, onMounted, onUnmounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { onLoad, onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import config from "@/config";
  import { getToken } from "@/utils/auth";
  import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
  import {
    addRepair,
    delRepairFile,
    editRepair,
    getRepairFileList,
    getRepairById,
  } from "@/api/equipmentManagement/repair";
  import dayjs from "dayjs";
  import { formatDateToYMD } from "@/utils/ruoyi";
  import useUserStore from "@/store/modules/user";
  const userStore = useUserStore();
  const showToast = message => {
    uni.showToast({
      title: message,
@@ -136,6 +179,10 @@
  const showDevice = ref(false);
  const showDate = ref(false);
  const pickerDateValue = ref(Date.now());
  const uploading = ref(false);
  const repairImageList = ref([]);
  const currentRepairId = ref(undefined);
  const pageInited = ref(false);
  // 设备选项
  const deviceOptions = ref([]);
@@ -159,7 +206,12 @@
    repairTime: [
      { required: true, trigger: "change", message: "请选择报修日期" },
    ],
    repairName: [{ required: true, trigger: "blur", message: "请输入报修人" }],
    maintenanceName: [
      { required: true, trigger: "blur", message: "请输入报修人" },
    ],
    acceptanceName: [
      { required: true, trigger: "blur", message: "请输入验收人" },
    ],
    remark: [{ required: true, trigger: "blur", message: "请输入故障现象" }],
  };
@@ -168,17 +220,222 @@
    deviceLedgerId: undefined, // 设备ID
    deviceModel: undefined, // 规格型号
    repairTime: dayjs().format("YYYY-MM-DD"), // 报修日期
    repairName: undefined, // 报修人
    status: "0",
    maintenanceName: userStore.nickName || userStore.name || "", // 报修人
    acceptanceName: undefined, // 验收人
    remark: undefined, // 故障现象
  });
  const normalizeFileUrl = (rawUrl = "") => {
    let fileUrl = rawUrl || "";
    const javaApi = config.baseUrl;
    const localPrefixes = ["wxfile://", "file://", "content://", "blob:", "data:"];
    if (localPrefixes.some(prefix => fileUrl.startsWith(prefix))) {
      return fileUrl;
    }
    if (fileUrl && fileUrl.indexOf("\\") > -1) {
      const lowerPath = fileUrl.toLowerCase();
      const uploadPathIndex = lowerPath.indexOf("uploadpath");
      if (uploadPathIndex > -1) {
        fileUrl = fileUrl.substring(uploadPathIndex).replace(/\\/g, "/");
      } else {
        fileUrl = fileUrl.replace(/\\/g, "/");
      }
    }
    fileUrl = fileUrl.replace(/^\/?uploadPath/, "/profile");
    if (fileUrl && !fileUrl.startsWith("http")) {
      if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl;
      fileUrl = javaApi + fileUrl;
    }
    return fileUrl;
  };
  const getFileAccessUrl = (file = {}) => {
    if (file?.link) {
      if (String(file.link).startsWith("http")) return file.link;
      return normalizeFileUrl(file.link);
    }
    return normalizeFileUrl(file?.url || file?.tempFilePath || "");
  };
  const normalizeId = raw => {
    if (raw === null || raw === undefined) return undefined;
    const val = String(raw).trim();
    if (!val || val === "undefined" || val === "null") return undefined;
    return val;
  };
  const resolveRepairId = () => {
    const pages = getCurrentPages();
    const currentPage = pages[pages.length - 1] || {};
    const routeId = normalizeId(currentPage?.options?.id);
    const memoryId = normalizeId(currentRepairId.value);
    const storageId = normalizeId(uni.getStorageSync("repairId"));
    return memoryId || routeId || storageId;
  };
  const getPageId = () => {
    const id = resolveRepairId();
    currentRepairId.value = id;
    return id;
  };
  const fetchRepairFileList = async id => {
    if (!id) {
      repairImageList.value = [];
      return;
    }
    try {
      const { code, data } = await getRepairFileList(id);
      if (code === 200) {
        repairImageList.value = Array.isArray(data) ? data : [];
      } else {
        repairImageList.value = [];
      }
    } catch (e) {
      repairImageList.value = [];
    }
  };
  const chooseRepairImage = () => {
    uni.chooseImage({
      count: 9,
      sizeType: ["original", "compressed"],
      sourceType: ["album", "camera"],
      success: res => {
        const id = getPageId();
        const files = res.tempFiles || [];
        if (!files.length) return;
        if (id) {
          uploadRepairImages(files, id);
          return;
        }
        if (operationType.value === "edit") {
          showToast("未获取到报修ID,请返回列表后重试");
          return;
        }
        // 新增模式临时上传:先本地暂存,保存报修后再自动上传绑定
        const tempItems = files.map((file, idx) => {
          const filePath = file.path || file.tempFilePath;
          return {
            id: `temp_${Date.now()}_${idx}`,
            url: filePath,
            tempFilePath: filePath,
            name: file.name || `temp_${Date.now()}_${idx}.jpg`,
            isTemp: true,
          };
        });
        repairImageList.value = [...repairImageList.value, ...tempItems];
        showToast("已临时添加,保存报修后自动上传");
      },
      fail: () => {
        showToast("选择图片失败");
      },
    });
  };
  const uploadRepairImages = async (files, repairId) => {
    const commonId = normalizeId(repairId);
    if (!commonId) {
      showToast("未获取到报修ID,上传失败");
      return;
    }
    const token = getToken();
    if (!token) {
      showToast("登录已失效,请重新登录");
      return;
    }
    uploading.value = true;
    try {
      for (const file of files) {
        const filePath = file.path || file.tempFilePath;
        if (!filePath) continue;
        await new Promise((resolve, reject) => {
          uni.uploadFile({
            url: `${config.baseUrl}/device/repair/uploadFile`,
            filePath,
            name: "file",
            formData: {
              deviceRepairId: commonId,
              type: 13,
            },
            header: {
              Authorization: `Bearer ${token}`,
            },
            success: uploadRes => {
              try {
                const parsed = JSON.parse(uploadRes.data || "{}");
                if (uploadRes.statusCode === 200 && parsed.code === 200) {
                  resolve(parsed);
                } else {
                  reject(new Error(parsed.msg || "上传失败"));
                }
              } catch (err) {
                reject(new Error("上传响应解析失败"));
              }
            },
            fail: () => reject(new Error("上传失败")),
          });
        });
      }
      showToast("上传成功");
      await fetchRepairFileList(commonId);
    } catch (e) {
      showToast(e?.message || "上传失败");
    } finally {
      uploading.value = false;
    }
  };
  const previewRepairImage = index => {
    const urls = repairImageList.value
      .map(item => getFileAccessUrl(item))
      .filter(Boolean);
    if (!urls.length) return;
    uni.previewImage({
      urls,
      current: urls[index] || urls[0],
    });
  };
  const removeRepairImage = (file, index) => {
    if (!file?.id || file?.isTemp) {
      repairImageList.value.splice(index, 1);
      return;
    }
    uni.showModal({
      title: "提示",
      content: "确认删除该图片吗?",
      success: async res => {
        if (!res.confirm) return;
        try {
          const { code } = await delRepairFile(file.id);
          if (code === 200) {
            repairImageList.value.splice(index, 1);
            showToast("删除成功");
          } else {
            showToast("删除失败");
          }
        } catch (e) {
          showToast("删除失败");
        }
      },
    });
  };
  // 报修状态选项
  const repairStatusOptions = ref([
    { name: "待维修", value: "0" },
    { name: "完结", value: "1" },
    { name: "失败", value: "2" },
    { name: "待验收", value: "3" },
    { name: "完成", value: "1" },
    { name: "维修失败", value: "2" },
  ]);
  const repairStatusText = ref("");
  const repairStatusText = ref("待维修");
  // 打开报修状态选择器
  const openRepairStatusPicker = () => {
@@ -220,11 +477,19 @@
          form.value.deviceLedgerId = data.deviceLedgerId;
          form.value.deviceModel = data.deviceModel;
          form.value.repairTime = dayjs(data.repairTime).format("YYYY-MM-DD");
          form.value.repairName = data.repairName;
          form.value.maintenanceName =
            data.maintenanceName || data.repairName || "";
          form.value.acceptanceName = data.acceptanceName || "";
          form.value.remark = data.remark;
          form.value.status =
            data.status !== undefined && data.status !== null
              ? String(data.status)
              : "0";
          await fetchRepairFileList(id);
          repairStatusText.value =
            repairStatusOptions.value.find(item => item.value == data.status)
              ?.name || "";
            repairStatusOptions.value.find(
              item => item.value == String(data.status)
            )?.name || "待维修";
          // 设置设备名称显示
          const device = deviceOptions.value.find(
            item => item.id === data.deviceLedgerId
@@ -237,8 +502,13 @@
        showToast("获取详情失败");
      }
    } else {
      // 新增模式
      operationType.value = "add";
      form.value.status = "0";
      repairStatusText.value = "待维修";
      if (!form.value.maintenanceName) {
        form.value.maintenanceName =
          userStore.nickName || userStore.name || "";
      }
    }
  };
@@ -327,15 +597,23 @@
    showDate.value = false;
  };
  onLoad(options => {
    const routeId = normalizeId(options?.id);
    const storageId = normalizeId(uni.getStorageSync("repairId"));
    const id = routeId || storageId;
    currentRepairId.value = id || undefined;
    pageInited.value = true;
  });
  onShow(() => {
    // 页面显示时获取参数
    // onLoad 已拿到 id 后,再按当前 id 初始化页面
    if (!pageInited.value) return;
    getPageParams();
  });
  onMounted(() => {
    // 页面加载时获取设备列表和参数
    // 页面加载时先获取设备列表
    loadDeviceName();
    getPageParams();
  });
  // 组件卸载时清理定时器
@@ -357,9 +635,18 @@
      } else if (!form.value.repairTime || form.value.repairTime.trim() === "") {
        isValid = false;
        errorMessage = "请选择报修日期";
      } else if (!form.value.repairName || form.value.repairName.trim() === "") {
      } else if (
        !form.value.maintenanceName ||
        form.value.maintenanceName.trim() === ""
      ) {
        isValid = false;
        errorMessage = "请输入报修人";
      } else if (
        !form.value.acceptanceName ||
        form.value.acceptanceName.trim() === ""
      ) {
        isValid = false;
        errorMessage = "请输入验收人";
      } else if (!form.value.remark || form.value.remark.trim() === "") {
        isValid = false;
        errorMessage = "请输入故障现象";
@@ -374,13 +661,34 @@
      const id = getPageId();
      // 准备提交数据
      const submitData = { ...form.value };
      const submitData = {
        ...form.value,
        status: Number(form.value.status ?? 0),
        repairName: form.value.maintenanceName,
      };
      const { code } = id
      const result = id
        ? await editRepair({ id: id, ...submitData })
        : await addRepair(submitData);
      const { code, data } = result || {};
      if (code == 200) {
        // 新增场景:临时图片在保存成功后自动上传并绑定新报修单
        if (!id) {
          const newId = data?.id || data?.repairId || data;
          if (newId) {
            currentRepairId.value = newId;
            const tempFiles = repairImageList.value
              .filter(item => item?.isTemp && (item.tempFilePath || item.url))
              .map(item => ({
                path: item.tempFilePath || item.url,
                tempFilePath: item.tempFilePath || item.url,
              }));
            if (tempFiles.length) {
              await uploadRepairImages(tempFiles, newId);
            }
          }
        }
        showToast(`${id ? "编辑" : "新增"}报修成功`);
        setTimeout(() => {
          uni.navigateBack();
@@ -402,26 +710,19 @@
  // 获取页面参数
  const getPageParams = () => {
    // 使用uni.getStorageSync获取id
    const id = uni.getStorageSync("repairId");
    const id = resolveRepairId();
    currentRepairId.value = id || undefined;
    // 根据是否有id参数来判断是新增还是编辑
    if (id) {
      // 编辑模式,获取详情
      loadForm(id);
      // 可选:获取后清除存储的id,避免影响后续操作
      uni.removeStorageSync("repairId");
    } else {
      // 新增模式
      currentRepairId.value = undefined;
      repairImageList.value = [];
      loadForm();
    }
  };
  // 获取页面ID
  const getPageId = () => {
    // 使用uni.getStorageSync获取id
    const id = uni.getStorageSync("repairId");
    return id;
  };
</script>
@@ -486,4 +787,41 @@
    margin-left: 8px;
    cursor: pointer;
  }
  .repair-image-upload {
    width: 100%;
  }
  .repair-image-list {
    margin-top: 12px;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
  }
  .repair-image-item {
    position: relative;
    width: 80px;
    height: 80px;
  }
  .repair-image-preview {
    width: 80px;
    height: 80px;
    border-radius: 6px;
    background: #f5f5f5;
  }
  .repair-image-delete {
    position: absolute;
    top: -6px;
    right: -6px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.65);
    display: flex;
    align-items: center;
    justify-content: center;
  }
</style>