1e71cfb6ec97cff6531dec65a3fb5cb24b2c18ac..991f6f78fccb86b2718ab96969a69304daafe2b4
昨天 spring
fix: 完成巡查
991f6f 对比 | 目录
昨天 spring
fix: 完成巡查
6e7631 对比 | 目录
已添加1个文件
已修改9个文件
1289 ■■■■ 文件已修改
src/App.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/routingInspection/routingInspection.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/composables/useScanCode.ts 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/routingInspection/detail/indexJX.vue 239 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/routingInspection/detail/indexLS.vue 199 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/routingInspection/index.vue 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/routingInspection/list/index.vue 212 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/routingInspection/product_card/index.vue 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/routingInspection/upload.vue 158 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue
@@ -1,22 +1,81 @@
<script setup lang="ts">
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
import { useThemeStore } from "@/store";
import { ref } from "vue";
// ä¸»é¢˜åˆå§‹åŒ–
const themeStore = useThemeStore();
// å…¨å±€æ‰«ç å¹¿æ’­æŽ¥æ”¶å™¨
let main: any = null;
let receiver: any = null;
let filter: any = null;
// åˆå§‹åŒ–扫码广播接收
const initGlobalScan = () => {
  // #ifdef APP-PLUS
  try {
    main = plus.android.runtimeMainActivity();
    const IntentFilter = plus.android.importClass("android.content.IntentFilter");
    filter = new IntentFilter();
    filter.addAction("com.dwexample.ACTION");
    receiver = plus.android.implements("io.dcloud.feature.internal.reflect.BroadcastReceiver", {
      onReceive: (context: any, intent: any) => {
        console.log("🔍 [全局Scan] onReceive è§¦å‘:", context, intent);
        plus.android.importClass(intent);
        const scanResult = intent.getStringExtra("com.motorolasolutions.emdk.datawedge.data_string");
        console.log("🔍 [全局Scan] æ‰«æç»“æžœ:", scanResult);
        // å‘送到所有可能的事件
        const eventNames = ["scan", "scanIndex", "scanJX", "scanLS"];
        eventNames.forEach((eventName) => {
          uni.$emit(eventName, { code: scanResult });
          console.log(`🔍 [全局Scan] å·²å‘送 ${eventName} äº‹ä»¶`);
        });
      },
    });
    // æ³¨å†Œå¹¿æ’­æŽ¥æ”¶å™¨
    main.registerReceiver(receiver, filter);
    console.log("🔍 [全局Scan] å…¨å±€æ‰«ç å¹¿æ’­æŽ¥æ”¶å™¨å·²å¯åЍ");
  } catch (error) {
    console.error("🔍 [全局Scan] åˆå§‹åŒ–失败:", error);
  }
  // #endif
};
// åœæ­¢æ‰«ç å¹¿æ’­æŽ¥æ”¶
const stopGlobalScan = () => {
  // #ifdef APP-PLUS
  try {
    if (main && receiver) {
      main.unregisterReceiver(receiver);
      console.log("🔍 [全局Scan] å…¨å±€æ‰«ç å¹¿æ’­æŽ¥æ”¶å™¨å·²åœæ­¢");
    }
  } catch (error) {
    console.error("🔍 [全局Scan] åœæ­¢å¤±è´¥:", error);
  }
  // #endif
};
onLaunch(() => {
  console.log("App Launch");
  // åˆå§‹åŒ–主题
  themeStore.initTheme();
  // åˆå§‹åŒ–全局扫码广播接收器
  initGlobalScan();
});
onShow(() => {
  console.log("App Show");
  // åº”用显示时重新启动广播接收器
  initGlobalScan();
});
onHide(() => {
  console.log("App Hide");
  // åº”用隐藏时不停止广播(保持后台接收)
});
</script>
src/api/routingInspection/routingInspection.ts
@@ -50,6 +50,14 @@
      data: data,
    });
  },
  // éªŒè¯äºŒç»´ç 
  assertScanQR(params: { deviceUid: string }) {
    return request<BaseResult<any>>({
      url: "/wireInspection/assertScanQR?deviceUid=" + params.deviceUid,
      method: "GET",
    });
  },
};
export default RoutingInspectionApi;
src/composables/useScanCode.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,249 @@
import { ref, computed, onUnmounted } from "vue";
import { onShow, onHide } from "@dcloudio/uni-app";
import RoutingInspectionApi from "@/api/routingInspection/routingInspection";
/**
 * æ‰«ç æ•°æ®æŽ¥å£
 */
interface ScanCodeData {
  uid?: string;
  deviceModel?: string;
  [key: string]: any;
}
// å…¨å±€ç›‘听器状态映射(确保每个事件名只有一个监听器)
const globalListeners = new Map<string, boolean>();
// æ·»åŠ ä¸€ä¸ªå…¨å±€çš„æ•èŽ·æ‰€æœ‰æ‰«ç äº‹ä»¶çš„ç›‘å¬å™¨ï¼ˆç”¨äºŽè°ƒè¯•ï¼‰
let debugListenerInitialized = false;
const initDebugListener = () => {
  if (debugListenerInitialized) return;
  // ç›‘听所有可能的扫码事件
  const eventNames = ["scan", "scanIndex", "scanJX", "scanLS"];
  eventNames.forEach((eventName) => {
    uni.$on(eventName, (data: any) => {
      console.log(`🔍 [全局调试] æ•获到 ${eventName} äº‹ä»¶:`, data);
    });
  });
  debugListenerInitialized = true;
  console.log("🔍 [全局调试] è°ƒè¯•监听器已初始化");
};
/**
 * æ‰«ç ç®¡ç† Composable
 * ç»Ÿä¸€ç®¡ç†æ‰«ç äº‹ä»¶ç›‘听、缓存存储和数据读取
 * å…¨å±€ç›‘听器,不随页面切换而关闭
 * @param eventName ç›‘听的事件名称,默认为 "scan"
 */
export function useScanCode(eventName: string = "scan") {
  // å½“前扫码的设备 UID
  const deviceUid = ref<string>("");
  // å½“前扫码的机台型号
  const deviceModel = ref<string>("");
  // å®Œæ•´çš„æ‰«ç æ•°æ®
  const scanData = ref<ScanCodeData>({});
  // ä¿å­˜äº‹ä»¶åç§°
  const currentEventName = eventName;
  /**
   * ä»Žæœ¬åœ°ç¼“存加载扫码数据
   */
  const loadFromCache = () => {
    try {
      const cachedData = uni.getStorageSync("scanCodeData");
      if (cachedData) {
        scanData.value = cachedData;
        deviceUid.value = cachedData.uid || "";
        deviceModel.value = cachedData.deviceModel || "";
        console.log("[useScanCode] ä»Žç¼“存加载扫码数据:", cachedData);
        return cachedData;
      }
    } catch (error) {
      console.error("[useScanCode] è¯»å–缓存失败:", error);
    }
    return null;
  };
  /**
   * ä¿å­˜æ‰«ç æ•°æ®åˆ°ç¼“å­˜
   */
  const saveToCache = (data: ScanCodeData) => {
    try {
      uni.setStorageSync("scanCodeData", data);
      console.log("[useScanCode] å·²ä¿å­˜åˆ°ç¼“å­˜:", data);
      return true;
    } catch (error) {
      console.error("[useScanCode] ä¿å­˜ç¼“存失败:", error);
      return false;
    }
  };
  /**
   * æ¸…空扫码数据和缓存
   */
  const clearScanData = () => {
    try {
      uni.removeStorageSync("scanCodeData");
      deviceUid.value = "";
      deviceModel.value = "";
      scanData.value = {};
      console.log("[useScanCode] å·²æ¸…空扫码数据");
    } catch (error) {
      console.error("[useScanCode] æ¸…空缓存失败:", error);
    }
  };
  /**
   * éªŒè¯äºŒç»´ç ï¼ˆä»…调用接口,不处理返回结果)
   */
  const validateQRCode = async (uid: string): Promise<void> => {
    try {
      console.log("[useScanCode] è°ƒç”¨éªŒè¯äºŒç»´ç æŽ¥å£, deviceUid:", uid);
      await RoutingInspectionApi.assertScanQR({ deviceUid: uid });
      console.log("[useScanCode] éªŒè¯æŽ¥å£è°ƒç”¨å®Œæˆ");
    } catch (error: any) {
      console.error("[useScanCode] éªŒè¯æŽ¥å£è°ƒç”¨å¼‚常:", error);
      // å³ä½¿å¼‚常也不影响后续流程
    }
  };
  /**
   * å¤„理扫码事件
   */
  const handleScanEvent = async (params: any) => {
    console.log(`========== [useScanCode][${currentEventName}] æ”¶åˆ°æ‰«ç äº‹ä»¶ ==========`);
    console.log(`[useScanCode][${currentEventName}] æŽ¥æ”¶å‚æ•°:`, params);
    console.log(`[useScanCode][${currentEventName}] è§¦å‘æ—¶é—´:`, new Date().toLocaleTimeString());
    try {
      if (!params?.code) {
        console.warn("[useScanCode] æ‰«ç å†…容为空");
        return;
      }
      let codeObj: ScanCodeData = {};
      try {
        codeObj = JSON.parse(params.code);
        console.log("[useScanCode] è§£æžåŽçš„对象:", codeObj);
      } catch (err) {
        console.error("[useScanCode] JSON è§£æžå¤±è´¥:", err);
        console.log("[useScanCode] åŽŸå§‹å­—ç¬¦ä¸²:", params.code);
        // å¦‚果不是 JSON,尝试作为普通字符串处理
        codeObj = { code: params.code };
      }
      // æ£€æŸ¥æ˜¯å¦æœ‰å¿…要的字段(uid æˆ– deviceModel)
      if (!codeObj.uid && !codeObj.deviceModel) {
        console.warn("[useScanCode] æ‰«ç æ•°æ®ç¼ºå°‘ uid å’Œ deviceModel,不保存缓存");
        uni.showToast({
          title: "扫码数据无效",
          icon: "none",
        });
        return;
      }
      // æ›´æ–°æœ¬åœ°çŠ¶æ€
      scanData.value = codeObj;
      deviceUid.value = codeObj.uid || "";
      deviceModel.value = codeObj.deviceModel || "";
      // ä¿å­˜åˆ°ç¼“å­˜
      saveToCache(codeObj);
      // å¦‚果有 uid,调用验证接口(不等待结果)
      if (codeObj.uid) {
        validateQRCode(codeObj.uid);
      }
      // æ˜¾ç¤ºæˆåŠŸæç¤º
      uni.showToast({
        title: "扫码成功",
        icon: "success",
      });
      console.log("[useScanCode] æ‰«ç æ•°æ®å·²æ›´æ–°:", {
        uid: deviceUid.value,
        deviceModel: deviceModel.value,
      });
    } catch (error) {
      console.error("[useScanCode] å¤„理扫码数据异常:", error);
    }
  };
  /**
   * å¯ç”¨æ‰«ç ç›‘听(全局,每次都重新注册以确保有效)
   */
  const enableListener = () => {
    // å…ˆç§»é™¤å¯èƒ½å­˜åœ¨çš„æ—§ç›‘听器
    uni.$off(currentEventName, handleScanEvent);
    // æ·»åŠ æ–°çš„ç›‘å¬å™¨
    uni.$on(currentEventName, handleScanEvent);
    // æ ‡è®°ä¸ºå…¨å±€å·²å¯ç”¨
    globalListeners.set(currentEventName, true);
    console.log(
      `[useScanCode][${currentEventName}] âœ… å…¨å±€ç›‘听器已启用/刷新(不会随页面切换关闭)`
    );
  };
  /**
   * ç¦ç”¨æ‰«ç ç›‘听(仅用于应用退出时清理,正常页面切换不调用)
   */
  const disableListener = () => {
    if (!globalListeners.get(currentEventName)) {
      console.log(`[useScanCode][${currentEventName}] ç›‘听器未启用,跳过`);
      return;
    }
    uni.$off(currentEventName, handleScanEvent);
    globalListeners.delete(currentEventName);
    console.log(`[useScanCode][${currentEventName}] âŒ å…¨å±€ç›‘听器已禁用`);
  };
  /**
   * è®¾ç½®é¡µé¢ç”Ÿå‘½å‘¨æœŸé’©å­ï¼ˆå…¨å±€ç›‘听器模式)
   * ç›‘听器全局启用一次,不随页面切换关闭
   * @param options é…ç½®é€‰é¡¹
   */
  const setupLifecycle = (
    options: {
      loadCacheOnShow?: boolean; // æ˜¯å¦åœ¨ onShow æ—¶åŠ è½½ç¼“å­˜
    } = {}
  ) => {
    const { loadCacheOnShow = true } = options;
    // åªåœ¨ onShow æ—¶åŠ è½½ç¼“å­˜ï¼Œä¸ç¦ç”¨ç›‘å¬å™¨
    if (loadCacheOnShow) {
      onShow(() => {
        console.log(`[useScanCode][${currentEventName}] onShow è§¦å‘`);
        loadFromCache();
      });
    }
    // æ³¨æ„ï¼šä¸å†åœ¨ onHide å’Œ onUnmounted ä¸­ç¦ç”¨ç›‘听器
    // ç›‘听器保持全局激活状态
  };
  // é¡µé¢åŠ è½½æ—¶è‡ªåŠ¨ä»Žç¼“å­˜è¯»å–
  loadFromCache();
  return {
    // çŠ¶æ€
    deviceUid,
    deviceModel,
    scanData,
    // è®¡ç®—属性
    hasScanned: computed(() => !!deviceUid.value),
    displayText: computed(() => deviceModel.value || "未扫码"),
    // æ–¹æ³•
    loadFromCache,
    saveToCache,
    clearScanData,
    validateQRCode,
    enableListener,
    disableListener,
    setupLifecycle,
  };
}
src/pages/routingInspection/detail/indexJX.vue
@@ -34,6 +34,9 @@
        ä¿å­˜
      </wd-button>
      <view class="placeholder"></view>
      <view class="scan-info">
        <text class="scan-device-text">当前扫码机台: {{ scannedDeviceModel || "未扫码" }}</text>
      </view>
      <view class="scan-wrapper" @click="openScan">
        <wd-icon name="scan" size="24px" color="#0D867F"></wd-icon>
      </view>
@@ -70,20 +73,20 @@
        <wd-form-item label="生产长度" prop="actuallyLength">
          {{ formatValue(recordData.fixedInfo?.actuallyLength, "m") }}
        </wd-form-item>
        <wd-form-item label="张力设置" prop="twistTension">
          {{ formatValue(recordData.fixedInfo?.twistTension, "N/m") }}
        <wd-form-item label="张力设置" prop="tensionSetting">
          {{ formatValue(recordData.fixedInfo?.tensionSetting, "N/m") }}
        </wd-form-item>
        <!-- ç»žåˆ¶å¤–径(可编辑) -->
        <wd-form-item label="绞制外径" prop="twistDiameter" required>
        <wd-form-item label="绞合外径" prop="twistedOuterDiameter" required>
          <template v-if="isEdit">
            <wd-input
              v-model="formData.twistDiameter"
              placeholder="请输入绞制外径(mm)"
              v-model="formData.twistedOuterDiameter"
              placeholder="请输入绞合外径(mm)"
              type="number"
            />
          </template>
          <template v-else>
            {{ formatValue(formData.twistDiameter, "mm") }}
            {{ formatValue(formData.twistedOuterDiameter, "mm") }}
          </template>
        </wd-form-item>
      </wd-col>
@@ -205,7 +208,12 @@
        </wd-form-item>
        <wd-form-item label="节距" prop="pitch" required>
          <template v-if="isEdit">
            <wd-input v-model="item.pitch" placeholder="请输入节距(mm)" type="number" />
            <wd-input
              v-model="item.pitch"
              placeholder="请输入节距(mm)"
              type="number"
              @input="updatePitchRatio(item)"
            />
          </template>
          <template v-else>
            {{ formatValue(item.pitch, "mm") }}
@@ -225,17 +233,18 @@
      <wd-col :span="24">
        <wd-form-item label="产品外观" prop="productAppearance" required>
          <template v-if="isEdit">
            <wd-checkbox-group
              v-model="formData.productAppearance"
              inline
              v-for="(opt, idx) in appearanceOptions"
              :key="idx"
              style="text-align: justify"
            >
              <wd-checkbox :modelValue="opt.value" style="width: 100px">
            <view style="display: flex; flex-wrap: wrap; gap: 10px">
              <wd-checkbox
                v-for="(opt, idx) in appearanceOptions"
                :key="idx"
                :value="opt.value"
                :modelValue="formData.productAppearance.includes(opt.value)"
                @click="handleAppearanceClick(opt.value)"
                style="width: 100px"
              >
                {{ opt.label }}
              </wd-checkbox>
            </wd-checkbox-group>
            </view>
          </template>
          <template v-else>
            {{ formatProductAppearance(formData.productAppearance) }}
@@ -308,18 +317,18 @@
        <img :src="previewImageUrl" alt="预览图片" style="width: 100%; height: auto" />
      </div>
    </wd-popup>
    <Scan ref="scanRef" emitName="scan" />
    <wd-toast />
  </view>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { ref, reactive, computed, onUnmounted } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import RoutingInspectionApi from "@/api/routingInspection/routingInspection";
import Scan from "@/components/scan/index.vue";
import { useToast } from "wot-design-uni";
import AttachmentUpload from "../upload.vue";
import { useUserStore } from "@/store/modules/user";
import { useScanCode } from "@/composables/useScanCode";
const paramsType = ref("");
const paramsId = ref("");
@@ -328,15 +337,25 @@
const previewImageUrl = ref("");
const isEdit = ref(false);
const tempFiles = ref<any[]>([]); // ä¸´æ—¶å­˜å‚¨æ–°ä¸Šä¼ çš„附件
const deviceUid = ref("");
const scanRef = ref();
const toast = useToast();
const attachmentRef = ref<any>(null);
const detailData = reactive<any>({});
const detailDataLoaded = ref(false);
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ä¿¡æ¯
const userStore = useUserStore();
const userInfo: any = computed(() => userStore.userInfo);
// ä½¿ç”¨æ‰«ç ç®¡ç† composable(全局监听器,不随页面切换关闭)
const {
  deviceUid,
  deviceModel: scannedDeviceModel,
  loadFromCache,
  enableListener,
} = useScanCode("scanJX");
const formData = reactive({
  twistDiameter: "", // ç»žåˆ¶å¤–径
  twistedOuterDiameter: "", // ç»žåˆ¶å¤–径
  structureFormula: "", // æˆå“ç»“æž„
  structureItems: [], // ç»“构标准值和实测
  inspectTwist: [], // ç»žçº¿å·¥è‰ºè´¨é‡æŽ§åˆ¶
@@ -370,15 +389,19 @@
  const structureResult = recordData.value.structureInfo?.structureRecordResult || {};
  const inspectionResult = recordData.value.inspectionResult || {};
  formData.twistDiameter = inspectionResult.twistDiameter || "";
  formData.twistedOuterDiameter =
    recordData.value.structureInfo.structureRecordResult.twistedOuterDiameter || "";
  formData.structureFormula = structureResult.inspectStructure?.structureFormula || "";
  formData.sampleComplete = inspectionResult.sampleComplete || "";
  formData.conclusion = structureResult.conclusion || "";
  formData.productAppearance = Array.isArray(structureResult.productAppearance)
  // åˆå§‹åŒ–产品外观
  const appearance = Array.isArray(structureResult.productAppearance)
    ? structureResult.productAppearance
    : structureResult.productAppearance
    ? [structureResult.productAppearance]
    : [];
      ? [structureResult.productAppearance]
      : [];
  formData.productAppearance = appearance;
  formData.structureItems = JSON.parse(
    JSON.stringify(structureResult.inspectStructure?.structureItems || [])
@@ -395,6 +418,13 @@
    const response = await RoutingInspectionApi.getStrandedInspectionStructureInfoById({ id });
    recordData.value = response.data;
    detailData.value = response.data.structureInfo;
    // å¦‚果记录人为空,默认设置为当前登录用户
    if (recordData.value.structureInfo && !recordData.value.structureInfo.createUserName) {
      recordData.value.structureInfo.createUserName =
        userInfo.value?.nickName || userInfo.value?.userName || "";
    }
    console.log("detailData.value", detailData.value);
    tempFiles.value = []; // æ¸…空临时文件
    initFormData(); // æ•°æ®è¿”回后初始化表单
@@ -434,7 +464,8 @@
const saveList = async () => {
  // 1. åŸºç¡€å­—段校验
  if (!formData.structureFormula) return uni.showToast({ title: "成品结构为必填项", icon: "none" });
  if (!formData.twistDiameter) return uni.showToast({ title: "绞制外径为必填项", icon: "none" });
  if (!formData.twistedOuterDiameter)
    return uni.showToast({ title: "绞制外径为必填项", icon: "none" });
  if (!formData.productAppearance.length)
    return uni.showToast({ title: "产品外观为必填项", icon: "none" });
  if (!formData.conclusion) return uni.showToast({ title: "结论为必填项", icon: "none" });
@@ -458,8 +489,15 @@
    if (!item.pitchRatio)
      return uni.showToast({ title: `${item.twistName}节径比为必填项`, icon: "none" });
  }
  console.log("1111", deviceUid.value);
  if (!deviceUid.value) return uni.showToast({ title: "请扫描二维码", icon: "none" });
  // éªŒè¯æ‰«ç æ•°æ®ï¼ˆä»Žç¼“存或新扫码获取)
  console.log("保存前检查 deviceUid:", deviceUid.value);
  if (!deviceUid.value) {
    return uni.showToast({
      title: "请先扫描设备二维码",
      icon: "none",
      duration: 2000,
    });
  }
  const { newFiles } = attachmentRef.value.getSubmitFiles();
  console.log("newFiles", newFiles);
  const allFileIds = [...newFiles];
@@ -468,7 +506,7 @@
      deviceUid: deviceUid.value,
      id: paramsId.value,
      result: {
        twistDiameter: formData.twistDiameter,
        twistedOuterDiameter: formData.twistedOuterDiameter,
        structureFormula: formData.structureFormula,
        structureItems: formData.structureItems,
        inspectTwist: formData.inspectTwist,
@@ -483,9 +521,20 @@
    });
    if (res.code === 200) {
      uni.showToast({ title: "保存成功", icon: "success" });
      isEdit.value = false;
      getDetailData(paramsId.value, paramsType.value);
      // è®¾ç½®åˆ·æ–°æ ‡è®°ï¼Œå‘Šè¯‰åˆ—表页需要刷新
      uni.setStorageSync("needRefreshInspectionList", true);
      uni.showToast({
        title: "保存成功",
        icon: "success",
        duration: 1500,
      });
      // å»¶è¿Ÿè¿”回列表页,让用户看到成功提示
      setTimeout(() => {
        uni.navigateBack({
          delta: 1,
        });
      }, 1500);
    } else {
      uni.showModal({ title: res.msg || "保存失败", icon: "error" });
    }
@@ -507,11 +556,11 @@
    case 1:
      return "danger"; // å·²é©³å›ž
    case 2:
      return "info"; // å¾…审核
      return "primary"; // å¾…审核
    case 3:
      return "success"; // é€šè¿‡
    default:
      return "info";
      return "default";
  }
};
@@ -553,36 +602,90 @@
  });
};
const openScan = () => {
  scanRef.value.triggerScan();
// è®¡ç®—节径比
const calculatePitchRatio = (pitch: string, dia: string) => {
  // å¦‚æžœpitch或dia为空,则返回"-"
  if (!pitch || !dia) return "-";
  // å°†pitch和dia转换为浮点数
  const pitchNum = parseFloat(pitch);
  const diaNum = parseFloat(dia);
  // å¦‚æžœpitchNum或diaNum是NaN,或者diaNum为0,则返回"-"
  if (isNaN(pitchNum) || isNaN(diaNum) || diaNum === 0) return "-";
  // è®¡ç®—pitchNum和diaNum的比值,并保留两位小数
  return (pitchNum / diaNum).toFixed(2);
};
const getScanCode = (params: any) => {
  console.log("完整参数:", params);
  let codeObj = {};
  try {
    codeObj = JSON.parse(params.code);
  } catch (err) {
    console.error("JSON解析失败:", err);
    toast.error("扫码数据异常");
    return;
// æ›´æ–°èŠ‚å¾„æ¯”ï¼ˆå½“èŠ‚è·å˜åŒ–æ—¶è‡ªåŠ¨è®¡ç®—ï¼‰
const updatePitchRatio = (item: any) => {
  // ä½¿ç”¨ç»žåˆå¤–径作为直径来计算节径比
  const dia = item.dia;
  item.pitchRatio = calculatePitchRatio(item.pitch, dia);
};
// å¤„理产品外观选择的互斥逻辑
const handleAppearanceClick = (value: string) => {
  const currentValues = [...formData.productAppearance];
  const isCurrentlyChecked = currentValues.includes(value);
  let newSelection: string[] = [];
  if (value === "无外观问题") {
    if (isCurrentlyChecked) {
      // å–消选中"无外观问题"
      newSelection = [];
    } else {
      // é€‰ä¸­"无外观问题",清空其他选项
      newSelection = ["无外观问题"];
    }
  } else {
    // ç‚¹å‡»å…¶ä»–选项
    if (isCurrentlyChecked) {
      // å–消选中该选项
      newSelection = currentValues.filter((v) => v !== value);
    } else {
      // é€‰ä¸­è¯¥é€‰é¡¹ï¼Œç§»é™¤"无外观问题"
      const filteredValues = currentValues.filter((v) => v !== "无外观问题");
      newSelection = [...filteredValues, value];
    }
  }
  deviceUid.value = codeObj?.uid;
  toast.success("扫码成功");
  formData.productAppearance = newSelection;
};
// ç¡®ä¿å…ˆç§»é™¤å†æ·»åŠ ç›‘å¬
const setupScanListener = () => {
  uni.$off("scan", getScanCode); // å…ˆç§»é™¤æ—§çš„
  uni.$on("scan", getScanCode); // å†æ·»åŠ æ–°çš„
const openScan = () => {
  console.log("indexJX - ç‚¹å‡»æ‰«ç æŒ‰é’®ï¼ˆå…¨å±€æ‰«ç æ¨¡å¼ï¼Œæ— éœ€æ‰‹åŠ¨è§¦å‘ï¼‰");
  // å…¨å±€æ‰«ç æ¨¡å¼ä¸‹ï¼Œç¡¬ä»¶æ‰«ç ä¼šè‡ªåŠ¨è§¦å‘ï¼Œæ— éœ€æ‰‹åŠ¨è°ƒç”¨
  uni.showToast({
    title: "请使用扫码枪扫描",
    icon: "none",
  });
};
onUnmounted(() => {
  // å¼€å¯å¹¿æ’­ç›‘听事件
  uni.$off("scan", getScanCode);
  console.log("离开1");
});
onMounted(() => {
  // å¼€å¯å¹¿æ’­ç›‘听事件
  setupScanListener();
  console.log("显示1");
// é¡µé¢æ˜¾ç¤ºæ—¶çš„处理
onShow(() => {
  console.log("========== indexJX - onShow è§¦å‘ ==========");
  // é‡æ–°å¯ç”¨ç›‘听器(确保监听器有效)
  enableListener();
  // åŠ è½½ç¼“å­˜ï¼ˆæ›´æ–°UI显示)
  const cachedData = loadFromCache();
  // å¦‚果没有缓存数据,提示用户需要扫码
  if (!cachedData || !cachedData.uid) {
    console.log("⚠️ æœªæ£€æµ‹åˆ°æ‰«ç ç¼“存,用户需要扫描设备二维码");
    // åœ¨ç¼–辑模式下才提示
    if (isEdit.value) {
      setTimeout(() => {
        uni.showToast({
          title: "请扫描设备二维码后再保存",
          icon: "none",
          duration: 2000,
        });
      }, 500);
    }
  }
});
</script>
@@ -612,6 +715,18 @@
  flex: 1;
}
.scan-info {
  display: flex;
  align-items: center;
  margin-right: 10px;
  .scan-device-text {
    font-size: 14px;
    color: #0d867f;
    font-weight: 500;
  }
}
.scan-wrapper {
  width: 38px;
  height: 38px;
src/pages/routingInspection/detail/indexLS.vue
@@ -34,6 +34,9 @@
        ä¿å­˜
      </wd-button>
      <view class="placeholder"></view>
      <view class="scan-info">
        <text class="scan-device-text">当前扫码机台: {{ scannedDeviceModel || "未扫码" }}</text>
      </view>
      <view class="scan-wrapper" @click="openScan">
        <wd-icon name="scan" size="24px" color="#0D867F"></wd-icon>
      </view>
@@ -126,17 +129,18 @@
        <wd-form-item label="外观" prop="appearance" required>
          <template v-if="isEdit">
            <wd-checkbox-group
              v-model="formData.appearance"
              inline
              v-for="(opt, idx) in appearanceOptions"
              :key="idx"
              style="text-align: justify"
            >
              <wd-checkbox :modelValue="opt.value" style="width: 100px">
            <view style="display: flex; flex-wrap: wrap; gap: 10px">
              <wd-checkbox
                v-for="(opt, idx) in appearanceOptions"
                :key="idx"
                :value="opt.value"
                :modelValue="formData.appearance?.includes(opt.value) || false"
                @click="handleAppearanceClick(opt.value)"
                style="width: 100px"
              >
                {{ opt.label }}
              </wd-checkbox>
            </wd-checkbox-group>
            </view>
          </template>
          <template v-else>
            {{ formatProductAppearance(formData.appearance) }}
@@ -293,18 +297,18 @@
        <img :src="previewImageUrl" alt="预览图片" style="width: 100%; height: auto" />
      </div>
    </wd-popup>
    <Scan ref="scanRef" emitName="scan" />
    <wd-toast />
  </view>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { ref, reactive, computed, onUnmounted } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import RoutingInspectionApi from "@/api/routingInspection/routingInspection";
import Scan from "@/components/scan/index.vue";
import { useToast } from "wot-design-uni";
import AttachmentUpload from "../upload.vue";
import { useUserStore } from "@/store/modules/user";
import { useScanCode } from "@/composables/useScanCode";
// æ ¸å¿ƒçŠ¶æ€
const paramsId = ref("");
@@ -314,17 +318,27 @@
const previewImageUrl = ref("");
const isEdit = ref(false);
const tempFiles = ref<any[]>([]);
const deviceUid = ref("");
const scanRef = ref();
const toast = useToast();
const attachmentRef = ref<any>(null);
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ä¿¡æ¯
const userStore = useUserStore();
const userInfo: any = computed(() => userStore.userInfo);
// ä½¿ç”¨æ‰«ç ç®¡ç† composable(全局监听器,不随页面切换关闭)
const {
  deviceUid,
  deviceModel: scannedDeviceModel,
  loadFromCache,
  enableListener,
} = useScanCode("scanLS");
// è¡¨å•数据
const formData = reactive({
  dia: "",
  maxDia: "",
  minDia: "",
  appearance: [],
  appearance: [] as string[],
  windingTightness: "",
  arrangementNeatness: "",
  aluminumWireDistance: "",
@@ -360,11 +374,11 @@
    case 1:
      return "danger";
    case 2:
      return "info";
      return "primary";
    case 3:
      return "success";
    default:
      return "info";
      return "default";
  }
};
@@ -385,7 +399,45 @@
// æ ¼å¼åŒ–工具
const formatProductAppearance = (productAppearance: string[]) => {
  return !productAppearance.length ? "-" : productAppearance.join("、");
  if (!productAppearance || !Array.isArray(productAppearance) || !productAppearance.length) {
    return "-";
  }
  return productAppearance.join("、");
};
// å¤„理外观选择的互斥逻辑
const handleAppearanceClick = (value: string) => {
  // ç¡®ä¿ appearance æ˜¯æ•°ç»„
  if (!Array.isArray(formData.appearance)) {
    formData.appearance = [];
  }
  const currentValues = [...formData.appearance];
  const isCurrentlyChecked = currentValues.includes(value);
  let newSelection: string[] = [];
  if (value === "无外观问题") {
    if (isCurrentlyChecked) {
      // å–消选中"无外观问题"
      newSelection = [];
    } else {
      // é€‰ä¸­"无外观问题",清空其他选项
      newSelection = ["无外观问题"];
    }
  } else {
    // ç‚¹å‡»å…¶ä»–选项
    if (isCurrentlyChecked) {
      // å–消选中该选项
      newSelection = currentValues.filter((v) => v !== value);
    } else {
      // é€‰ä¸­è¯¥é€‰é¡¹ï¼Œç§»é™¤"无外观问题"
      const filteredValues = currentValues.filter((v) => v !== "无外观问题");
      newSelection = [...filteredValues, value];
    }
  }
  formData.appearance = newSelection;
};
const formatValue = (value: any, unit?: string) => {
@@ -409,7 +461,12 @@
  formData.dia = inspectionResult.dia || "";
  formData.maxDia = inspectionResult.maxDia || "";
  formData.minDia = inspectionResult.minDia || "";
  formData.appearance = inspectionResult.appearance || [];
  // ç¡®ä¿ appearance æ˜¯æ•°ç»„
  formData.appearance = Array.isArray(inspectionResult.appearance)
    ? inspectionResult.appearance
    : inspectionResult.appearance
      ? [inspectionResult.appearance]
      : [];
  formData.windingTightness = inspectionResult.windingTightness || "";
  formData.arrangementNeatness = inspectionResult.arrangementNeatness || "";
  formData.aluminumWireDistance = inspectionResult.aluminumWireDistance || "";
@@ -423,6 +480,13 @@
  try {
    const response = await RoutingInspectionApi.getDrawInspectInfoById({ id });
    detailData.value = response.data;
    // å¦‚果巡检员为空,默认设置为当前登录用户
    if (!detailData.value.processInspectionUserName) {
      detailData.value.processInspectionUserName =
        userInfo.value?.nickName || userInfo.value?.userName || "";
    }
    tempFiles.value = [];
    initFormData();
  } catch (error) {
@@ -464,7 +528,16 @@
    return uni.showToast({ title: "成品模后接头情况为必填项", icon: "none" });
  if (!formData.conclusion) return uni.showToast({ title: "结论为必填项", icon: "none" });
  if (!formData.isFully) return uni.showToast({ title: "铝杆样品是否齐全为必填项", icon: "none" });
  if (!deviceUid.value) return uni.showToast({ title: "请扫描二维码", icon: "none" });
  // éªŒè¯æ‰«ç æ•°æ®ï¼ˆä»Žç¼“存或新扫码获取)
  console.log("保存前检查 deviceUid:", deviceUid.value);
  if (!deviceUid.value) {
    return uni.showToast({
      title: "请先扫描设备二维码",
      icon: "none",
      duration: 2000,
    });
  }
  const { newFiles } = attachmentRef.value.getSubmitFiles();
  console.log("newFiles", newFiles);
  const allFileIds = [...newFiles];
@@ -488,9 +561,20 @@
      processInspectionAttachmentList: allFileIds,
    });
    if (res.code === 200) {
      uni.showToast({ title: "保存成功", icon: "success" });
      isEdit.value = false;
      getDetailData(paramsId.value, paramsType.value);
      // è®¾ç½®åˆ·æ–°æ ‡è®°ï¼Œå‘Šè¯‰åˆ—表页需要刷新
      uni.setStorageSync("needRefreshInspectionList", true);
      uni.showToast({
        title: "保存成功",
        icon: "success",
        duration: 1500,
      });
      // å»¶è¿Ÿè¿”回列表页,让用户看到成功提示
      setTimeout(() => {
        uni.navigateBack({
          delta: 1,
        });
      }, 1500);
    } else {
      uni.showModal({ title: res.msg || "保存失败", icon: "error" });
    }
@@ -505,33 +589,36 @@
};
const openScan = () => {
  scanRef.value.triggerScan();
  console.log("indexLS - ç‚¹å‡»æ‰«ç æŒ‰é’®ï¼ˆå…¨å±€æ‰«ç æ¨¡å¼ï¼Œæ— éœ€æ‰‹åŠ¨è§¦å‘ï¼‰");
  // å…¨å±€æ‰«ç æ¨¡å¼ä¸‹ï¼Œç¡¬ä»¶æ‰«ç ä¼šè‡ªåŠ¨è§¦å‘ï¼Œæ— éœ€æ‰‹åŠ¨è°ƒç”¨
  uni.showToast({
    title: "请使用扫码枪扫描",
    icon: "none",
  });
};
const getScanCode = (params: any) => {
  let codeObj = {};
  try {
    codeObj = JSON.parse(params.code);
  } catch (err) {
    toast.error("扫码数据异常");
    return; // è§£æžå¤±è´¥ç›´æŽ¥è¿”回,避免后续错误
// é¡µé¢æ˜¾ç¤ºæ—¶çš„处理
onShow(() => {
  console.log("========== indexLS - onShow è§¦å‘ ==========");
  // é‡æ–°å¯ç”¨ç›‘听器(确保监听器有效)
  enableListener();
  // åŠ è½½ç¼“å­˜ï¼ˆæ›´æ–°UI显示)
  const cachedData = loadFromCache();
  // å¦‚果没有缓存数据,提示用户需要扫码
  if (!cachedData || !cachedData.uid) {
    console.log("⚠️ æœªæ£€æµ‹åˆ°æ‰«ç ç¼“存,用户需要扫描设备二维码");
    // åœ¨ç¼–辑模式下才提示
    if (isEdit.value) {
      setTimeout(() => {
        uni.showToast({
          title: "请扫描设备二维码后再保存",
          icon: "none",
          duration: 2000,
        });
      }, 500);
    }
  }
  deviceUid.value = codeObj?.uid;
  toast.success("扫码成功");
};
// ç¡®ä¿å…ˆç§»é™¤å†æ·»åŠ ç›‘å¬
const setupScanListener = () => {
  uni.$off("scan", getScanCode); // å…ˆç§»é™¤æ—§çš„
  uni.$on("scan", getScanCode); // å†æ·»åŠ æ–°çš„
};
onUnmounted(() => {
  // å¼€å¯å¹¿æ’­ç›‘听事件
  uni.$off("scan", getScanCode);
  console.log("离开1");
});
onMounted(() => {
  // å¼€å¯å¹¿æ’­ç›‘听事件
  setupScanListener();
  console.log("显示1");
});
</script>
@@ -559,6 +646,18 @@
.placeholder {
  flex: 1;
}
.scan-info {
  display: flex;
  align-items: center;
  margin-right: 10px;
  .scan-device-text {
    font-size: 14px;
    color: #0d867f;
    font-weight: 500;
  }
}
.scan-wrapper {
@@ -681,4 +780,4 @@
  align-items: flex-start; // åž‚直方向顶部对齐(上移关键)
  gap: 20rpx; // é€‰é¡¹ä¹‹é—´çš„间距
}
</style>
</style>
src/pages/routingInspection/index.vue
@@ -2,7 +2,14 @@
  <view>
    <wd-row>
      <wd-col :span="21">
        <wd-search placeholder-left hide-cancel></wd-search>
        <wd-search
          v-model="searchKeyword"
          placeholder="请输入班组名称"
          placeholder-left
          hide-cancel
          @search="handleSearch"
          @clear="handleClear"
        ></wd-search>
      </wd-col>
      <wd-col :span="3">
        <view class="scan_box" @click="openScan">
@@ -17,64 +24,64 @@
        :title="`${item.deviceModel}(待检查${item.pendingNum}条)`"
        class="tab_bg"
      >
        <ProductList :api="RoutingInspectionApi.getInspectListByPatrol" :ProList="item" />
        <ProductList
          :key="searchKey"
          :api="RoutingInspectionApi.getInspectListByPatrol"
          :ProList="{ ...item, teamName: searchKeyword }"
        />
      </wd-tab>
    </wd-tabs>
    <Scan ref="scanRef" emitName="scan" />
    <wd-toast />
  </view>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, onMounted, onUnmounted } from "vue";
import { onShow, onHide } from "@dcloudio/uni-app";
import ProductList from "./list/index.vue";
import Scan from "@/components/scan/index.vue";
import { useUserStore } from "@/store/modules/user";
import reportApi from "@/api/work/report";
import { useToast } from "wot-design-uni";
import RoutingInspectionApi from "@/api/routingInspection/routingInspection";
import { useScanCode } from "@/composables/useScanCode";
const scanRef = ref();
const userStore = useUserStore();
const userInfo: any = computed(() => userStore.userInfo);
const toast = useToast();
const tab = ref<number>(0);
const patrolList = ref<any[]>([]); // å·¡æ£€è®¾å¤‡åˆ—表数据
const searchKeyword = ref<string>(""); // æœç´¢å…³é”®è¯ï¼ˆç­ç»„名称)
const searchKey = ref<number>(0); // ç”¨äºŽå¼ºåˆ¶åˆ·æ–°åˆ—表
// ä½¿ç”¨æ‰«ç ç®¡ç† composable(全局监听器,不随页面切换关闭)
const { deviceUid, deviceModel, hasScanned, displayText, loadFromCache, enableListener } =
  useScanCode("scanIndex");
const handlePatrolData = (index: number, count: number) => {
  // å¯ä»¥åœ¨è¿™é‡Œæ›´æ–°ç‰¹å®šå·¡æ£€è®¾å¤‡çš„待检查数量
  // ä¾‹å¦‚:patrolList.value[index].pendingNum = count;
};
// å¤„理搜索
const handleSearch = (value: string) => {
  console.log("搜索班组:", value);
  searchKey.value++; // æ›´æ–° key å¼ºåˆ¶åˆ·æ–°åˆ—表
};
// å¤„理清空搜索
const handleClear = () => {
  console.log("清空搜索");
  searchKeyword.value = "";
  searchKey.value++; // æ›´æ–° key å¼ºåˆ¶åˆ·æ–°åˆ—表
};
const openScan = () => {
  scanRef.value.triggerScan();
};
const getScanCode = async () => {
  const { code } = await reportApi.sendWorkTime({
    userName: userInfo.value.userName,
  console.log("index.vue - ç‚¹å‡»æ‰«ç æŒ‰é’®ï¼ˆå…¨å±€æ‰«ç æ¨¡å¼ï¼Œæ— éœ€æ‰‹åŠ¨è§¦å‘ï¼‰");
  // å…¨å±€æ‰«ç æ¨¡å¼ä¸‹ï¼Œç¡¬ä»¶æ‰«ç ä¼šè‡ªåŠ¨è§¦å‘ï¼Œæ— éœ€æ‰‹åŠ¨è°ƒç”¨
  uni.showToast({
    title: "请使用扫码枪扫描",
    icon: "none",
  });
  if (code == 200) {
    toast.success("扫码成功");
  }
};
// èŽ·å–ç‰¹å®šå·¡æ£€è®¾å¤‡çš„æ•°æ®
const getPatrolData = (item: any) => {
  return async (params: any) => {
    // è¿™é‡Œå¯ä»¥æ ¹æ®item中的信息调用相应的接口获取详情
    // è¿”回的数据格式需要与ProductList组件期望的格式一致
    return {
      code: 200,
      data: {
        type: "巡检",
        data: {
          total: 0,
          records: [],
        },
      },
    };
  };
};
// èŽ·å–å·¡æ£€è®¾å¤‡åˆ—è¡¨
@@ -89,24 +96,32 @@
  }
};
// ç¡®ä¿å…ˆç§»é™¤å†æ·»åŠ ç›‘å¬
const setupScanListener = () => {
  uni.$off("scan", getScanCode); // å…ˆç§»é™¤æ—§çš„
  uni.$on("scan", getScanCode); // å†æ·»åŠ æ–°çš„
};
onMounted(() => {
  // å¼€å¯å¹¿æ’­ç›‘听事件
  setupScanListener();
  console.log("显示1");
  // é¡µé¢åŠ è½½æ—¶èŽ·å–å·¡æ£€è®¾å¤‡åˆ—è¡¨
  loadPatrolList();
  // å¯ç”¨å…¨å±€ç›‘听器
  enableListener();
  console.log("index.vue - onMounted");
});
onUnmounted(() => {
  // å¼€å¯å¹¿æ’­ç›‘听事件
  uni.$off("scan", getScanCode);
  console.log("离开1");
onShow(() => {
  console.log("========== index.vue - onShow è§¦å‘ ==========");
  // é¡µé¢æ˜¾ç¤ºæ—¶é‡æ–°å¯ç”¨ç›‘听器(确保监听器有效)
  enableListener();
  // åŠ è½½ç¼“å­˜ï¼ˆæ›´æ–°UI显示)
  loadFromCache();
  // æ£€æŸ¥æ˜¯å¦éœ€è¦åˆ·æ–°åˆ—表(只有提交成功后才刷新)
  const needRefresh = uni.getStorageSync("needRefreshInspectionList");
  if (needRefresh) {
    console.log("检测到需要刷新列表,开始刷新...");
    // é‡æ–°åŠ è½½å·¡æ£€è®¾å¤‡åˆ—è¡¨ï¼ˆåˆ·æ–°å¾…æ£€æŸ¥æ•°é‡ï¼‰
    loadPatrolList();
    // å¼ºåˆ¶åˆ·æ–° ProductList ç»„ä»¶
    searchKey.value++;
    // æ¸…除刷新标记
    uni.removeStorageSync("needRefreshInspectionList");
  }
});
</script>
@@ -146,4 +161,4 @@
.statistics_box {
  margin: 15px;
}
</style>
</style>
src/pages/routingInspection/list/index.vue
@@ -1,112 +1,130 @@
<template>
    <view class="card_box">
        <z-paging ref="pagingRef" v-model="list" :fixed="false" :auto-show-back-to-top="true" @query="getList">
            <ProductCard v-for="(item, index) in list" :key="index" :data="item" :map="map"
                @click="toDetail(item.id, item.deviceType)" />
        </z-paging>
        <wd-toast />
    </view>
  <view class="card_box">
    <z-paging
      ref="pagingRef"
      v-model="list"
      :fixed="false"
      :auto-show-back-to-top="true"
      @query="getList"
    >
      <ProductCard
        v-for="(item, index) in list"
        :key="index"
        :data="item"
        :map="map"
        @click="toDetail(item.id, item.deviceType)"
      />
    </z-paging>
    <wd-toast />
  </view>
</template>
<script setup lang="ts">
    import ProductCard from "../product_card/index.vue";
    import { useUserStore } from "@/store/modules/user";
    import zPaging from "@/components/z-paging/z-paging.vue";
    import { useToast } from "wot-design-uni";
import ProductCard from "../product_card/index.vue";
import { useUserStore } from "@/store/modules/user";
import zPaging from "@/components/z-paging/z-paging.vue";
import { useToast } from "wot-design-uni";
    const toast = useToast();
    const userStore = useUserStore();
    const userInfo : any = computed(() => userStore.userInfo);
    const pagingRef = ref();
const toast = useToast();
const userStore = useUserStore();
const userInfo: any = computed(() => userStore.userInfo);
const pagingRef = ref();
const map = reactive({
        deviceModel: "deviceModel",
        model: "model",
        firstNo: "firstNo",
        recordDate: "recordDate",
        workShift: "workShift",
        teamName: "teamName",
        poleModel: "poleModel",
        poleNumber: "poleNumber",
        outputNumber: "outputNumber",
        inspectPerson: "inspectPerson",
        status: "status",
    rejectList: [
  deviceModel: "deviceModel",
  model: "model",
  firstNo: "firstNo",
  recordDate: "recordDate",
  workShift: "workShift",
  teamName: "teamName",
  poleModel: "poleModel",
  poleNumber: "poleNumber",
  outputNumber: "outputNumber",
  inspectPerson: "inspectPerson",
  status: "status",
  productType: "productType",
  recordPosition: "recordPosition",
  rejectList: [
    {
      rejectPerson: "rejectPerson",
      rejectTime: "rejectTime",
      rejectReason: {
        reason: "reason",
      },
    },
  ], // æ”¹ä¸ºå¯¹è±¡ï¼ŒåŒ…含所需的嵌套属性
});
const props = defineProps({
  api: {
    type: Function,
    default: () => {},
  },
  ProList: {
    type: Object,
    default: () => {},
  },
});
const list = ref<any[]>([]);
const toDetail = (id: number, deviceType: number) => {
  console.log("点击卡片", id, deviceType);
  if (deviceType == 1) {
    // ç»žçº¿
    uni.navigateTo({
      url: `/pages/routingInspection/detail/indexJX?id=${id}&deviceType=${deviceType}`,
    });
  } else if (deviceType == 0) {
    // æ‹‰ä¸
    uni.navigateTo({
      url: `/pages/routingInspection/detail/indexLS?id=${id}&deviceType=${deviceType}`,
    });
  }
};
const getList = async (pageNo = 1, pageSize = 10) => {
  const { code, data } = await props.api({
    deviceModel: props.ProList.deviceModel,
    status: "0",
    deviceType: props.ProList.deviceType,
    teamName: props.ProList.teamName || "", // ç­ç»„名称搜索
    current: pageNo,
    size: pageSize,
  });
  if (code == 200) {
    map.deviceModel = "deviceModel";
    map.model = "model";
    map.firstNo = "firstNo";
    map.recordDate = "recordDate";
    map.workShift = "workShift";
    map.teamName = "teamName";
    map.poleModel = "poleModel";
    map.poleNumber = "poleNumber";
    map.outputNumber = "outputNumber";
    map.inspectPerson = "inspectPerson";
    map.productType = "productType";
    map.recordPosition = "recordPosition";
    map.rejectList = [
      {
        rejectPerson: "rejectPerson",
        rejectTime: "rejectTime",
        rejectReason: {
          reason: "reason"
          reason: "reason",
        },
      },
        ], // æ”¹ä¸ºå¯¹è±¡ï¼ŒåŒ…含所需的嵌套属性
    });
    const props = defineProps({
        api: {
            type: Function,
            default: () => { },
        },
        ProList: {
            type: Object,
            default: () => { },
        },
    });
    const list = ref<any[]>([]);
const toDetail = (id: number, deviceType: number) => {
    console.log('点击卡片', id, deviceType);
    if (deviceType == 1) {
            // ç»žçº¿
            uni.navigateTo({
                url: `/pages/routingInspection/detail/indexJX?id=${id}&deviceType=${deviceType}`,
            });
    } else if (deviceType == 0) {
            // æ‹‰ä¸
            uni.navigateTo({
                url: `/pages/routingInspection/detail/indexLS?id=${id}&deviceType=${deviceType}`,
            });
        }
    };
    const getList = async () => {
        const { code, data } = await props.api({
            deviceModel: props.ProList.deviceModel,
            status: "0",
            deviceType: props.ProList.deviceType,
        });
        if (code == 200) {
      map.deviceModel = "deviceModel";
            map.model = "model";
            map.firstNo = "firstNo";
            map.recordDate = "recordDate";
            map.workShift = "workShift";
            map.teamName = "teamName";
            map.poleModel = "poleModel";
            map.poleNumber = "poleNumber";
            map.outputNumber = "outputNumber";
      map.inspectPerson = "inspectPerson";
      map.rejectList = [
        {
          rejectPerson: "rejectPerson",
          rejectTime: "rejectTime",
          rejectReason: {
            reason: "reason"
          },
        },
      ];
            map.status = "status";
            if (data.total == 0) {
                pagingRef.value.complete(true);
      } else {
        console.log('data.records', data.records);
                pagingRef.value.complete(data.records);
            }
        }
    };
    ];
    map.status = "status";
    if (data.total == 0) {
      pagingRef.value.complete(true);
    } else {
      console.log("data.records", data.records);
      pagingRef.value.complete(data.records);
    }
  }
};
</script>
<style lang="scss" scoped>
    .card_box {
        height: calc(100vh - 120px);
    }
</style>
.card_box {
  height: calc(100vh - 120px);
}
</style>
src/pages/routingInspection/product_card/index.vue
@@ -2,25 +2,12 @@
  <wd-card class="card_bg" @click="handleCardClick">
    <template #title>
      <view class="flex justify-between w-full">
        <text class="font-medium text-[#252525]">机台: {{ data[map.deviceModel] }}</text>
        <text class="font-medium text-[#252525]">记录位置: {{ data[map.recordPosition] }}</text>
        <wd-tag color="#0D867F" bg-color="#E7F4EC">
          <text class="text-xs">{{ data[map.model] }}</text>
        </wd-tag>
      </view>
    </template>
    <wd-row class="my-2">
      <wd-col :span="24">
        <view class="flex">
          <view class="icon_box">
            <wd-icon name="folder" color="#0D867F"></wd-icon>
          </view>
          <text class="text-[#646874] mx-2">
            é¦–检单号:
            <text class="text-[#252525]">{{ data[map.firstNo] }}</text>
          </text>
        </view>
      </wd-col>
    </wd-row>
    <wd-row class="my-2">
      <wd-col :span="24">
        <view class="flex">
@@ -42,44 +29,22 @@
          </view>
          <text class="text-[#646874] mx-2">
            ç­ç»„:
            <text class="text-[#252525]">{{ data[map.teamName] }}</text>
            <text class="text-[#252525]">
              {{ data[map.teamName]?.slice(-2) || data[map.teamName] }}
            </text>
          </text>
        </view>
      </wd-col>
    </wd-row>
    <wd-row class="my-2">
    <wd-row class="my-2" v-if="data[map.productType]">
      <wd-col :span="24">
        <view class="flex">
          <view class="icon_box">
            <wd-icon name="folder" color="#0D867F"></wd-icon>
          </view>
          <text class="text-[#646874] mx-2">
            é¢†ç”¨æ†å·:
            <text class="text-[#252525]">{{ data[map.poleNumber] }}</text>
          </text>
        </view>
      </wd-col>
    </wd-row>
    <wd-col :span="12">
      <view class="flex">
        <view class="icon_box">
          <wd-icon name="folder" color="#0D867F"></wd-icon>
        </view>
        <text class="text-[#646874] mx-2">
          æ†åž‹å·:
          <text class="text-[#252525]">{{ data[map.poleModel] }}</text>
        </text>
      </view>
    </wd-col>
    <wd-row class="my-2">
      <wd-col :span="12">
        <view class="flex">
          <view class="icon_box">
            <wd-icon name="folder" color="#0D867F"></wd-icon>
          </view>
          <text class="text-[#646874] mx-2">
            ç”Ÿäº§è½´æ•°:
            <text class="text-[#252525]">{{ data[map.outputNumber] }}</text>
            äº§å“ç±»åˆ«:
            <text class="text-[#252525]">{{ data[map.productType] }}</text>
          </text>
        </view>
      </wd-col>
@@ -216,7 +181,9 @@
.page-class {
  :deep() {
    .custom-shadow {
      box-shadow: 0 3px 1px -2px rgb(0 0 0 / 20%), 0 2px 2px 0 rgb(0 0 0 / 14%),
      box-shadow:
        0 3px 1px -2px rgb(0 0 0 / 20%),
        0 2px 2px 0 rgb(0 0 0 / 14%),
        0 1px 5px 0 rgb(0 0 0 / 12%);
    }
  }
@@ -232,4 +199,4 @@
.content {
  padding: 5px;
}
</style>
</style>
src/pages/routingInspection/upload.vue
@@ -18,20 +18,25 @@
    <view class="attachment-list">
      <wd-status-tip v-if="attachmentList.length === 0" image="content" tip="暂无附件" />
      <view v-for="item in attachmentList" :key="item.id" class="attachment-card">
      <view v-for="(item, index) in attachmentList" :key="item.id || index" class="attachment-card">
        <view class="media-wrapper" @click="previewAttachment(item)">
          <!-- å›¾ç‰‡é¢„览 -->
          <template v-if="isImageType(item.url)">
            <image
              v-if="!item.loadError"
              :src="getFullUrl(item.url)"
              mode="aspectFill"
              class="media-preview"
              @error="onImageError(item)"
              @load="onImageLoad(item)"
              style="width: 100%; height: 100%"
              @error="onImageError(item, index)"
              @load="onImageLoad(item, index)"
              :show-menu-by-longpress="true"
            />
            <!-- åŠ è½½ä¸­é®ç½© -->
            <view v-if="item.loading" class="loading-mask">
              <text class="loading-text">加载中...</text>
            </view>
            <!-- å›¾ç‰‡åŠ è½½å¤±è´¥æ˜¾ç¤ºé»˜è®¤å›¾æ ‡ -->
            <view v-else class="file-icon-wrapper">
            <view v-if="item.loadError" class="file-icon-wrapper error-overlay">
              <wd-icon name="picture" size="48px" color="#ccc" />
              <text class="file-name error-text">加载失败</text>
            </view>
@@ -40,15 +45,15 @@
          <!-- è§†é¢‘预览 -->
          <template v-else-if="isVideoType(item.url)">
            <video
              v-if="!item.loadError"
              :src="getFullUrl(item.url)"
              class="media-preview"
              :controls="false"
              :show-center-play-btn="false"
              @error="onVideoError(item)"
              :show-center-play-btn="true"
              @error="onVideoError(item, index)"
              object-fit="cover"
            />
            <!-- è§†é¢‘加载失败显示默认图标 -->
            <view v-else class="file-icon-wrapper">
            <view v-if="item.loadError" class="file-icon-wrapper error-overlay">
              <wd-icon name="video" size="48px" color="#ccc" />
              <text class="file-name error-text">加载失败</text>
            </view>
@@ -73,15 +78,17 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ref, computed, watch } from "vue";
import { useToast } from "wot-design-uni";
import AttachmentAPI from "@/api/product/attachment";
// H5 ä½¿ç”¨ VITE_APP_BASE_API ä½œä¸ºä»£ç†è·¯å¾„,其他平台使用 VITE_APP_API_URL ä½œä¸ºè¯·æ±‚路径
let baseUrl = import.meta.env.VITE_APP_API_URL;
let baseUrlValue = import.meta.env.VITE_APP_API_URL || "";
// #ifdef H5
baseUrl = import.meta.env.VITE_APP_BASE_API;
baseUrlValue = import.meta.env.VITE_APP_BASE_API || "";
// #endif
const baseUrl = ref(baseUrlValue); // ä½¿ç”¨ref使其在模板中可访问
// å¤–部参数
const props = defineProps({
@@ -91,35 +98,100 @@
});
const toast = useToast();
const attachmentList = ref<any[]>(props.detailData.files || []);
const attachmentIds = ref<string[]>(props.detailData.attachmentId || []);
// èŽ·å–åˆå§‹æ•°æ®
const getInitialData = () => {
  // å¤„理不同的数据结构
  let data = props.detailData;
  // å¦‚果是 ref å¯¹è±¡ï¼ŒèŽ·å–å…¶ value
  if (data && typeof data === "object" && "value" in data) {
    data = data.value;
  }
  // å¦‚果是数组,直接返回
  if (Array.isArray(data)) {
    return data.map((item) => ({
      ...item,
      loading: false,
      loadError: false,
    }));
  }
  // å¦‚果有 files å±žæ€§
  if (data && data.files) {
    const files = Array.isArray(data.files) ? data.files : [];
    return files.map((item) => ({
      ...item,
      loading: false,
      loadError: false,
    }));
  }
  return [];
};
const attachmentList = ref<any[]>(getInitialData());
const attachmentIds = ref<string[]>(attachmentList.value.map((item: any) => item.id) || []);
// ç›‘听 props.detailData å˜åŒ–
watch(
  () => props.detailData,
  (newVal) => {
    const newData = getInitialData();
    if (newData.length > 0) {
      attachmentList.value = newData.map((item) => ({
        ...item,
        loading: false,
        loadError: false,
      }));
      attachmentIds.value = newData.map((item: any) => item.id);
    }
  },
  { deep: true, immediate: false }
);
// èŽ·å–å®Œæ•´çš„å›¾ç‰‡/视频 URL
const getFullUrl = (url: string) => {
  if (!url) return "";
  // å¦‚果已经是完整的 URL(http æˆ– https å¼€å¤´ï¼‰ï¼Œç›´æŽ¥è¿”回
  if (url.startsWith("http://") || url.startsWith("https://")) {
    return url;
  }
  // æ£€æŸ¥ baseUrl æ˜¯å¦æœ‰æ•ˆ
  if (!baseUrl.value) {
    console.error("❌ baseUrl未配置,url:", url);
    return url;
  }
  // å¦‚果是相对路径,拼接基础 URL
  return `${baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
  const separator = url.startsWith("/") || baseUrl.value.endsWith("/") ? "" : "/";
  return `${baseUrl.value}${separator}${url}`;
};
// å›¾ç‰‡åŠ è½½æˆåŠŸ
const onImageLoad = (item: any) => {
const onImageLoad = (item: any, index: number) => {
  item.loading = false;
  item.loadError = false;
  attachmentList.value = [...attachmentList.value];
};
// å›¾ç‰‡åŠ è½½å¤±è´¥
const onImageError = (item: any) => {
  console.error("图片加载失败:", item.url);
const onImageError = (item: any, index: number) => {
  console.error(`图片加载失败 [${index}]:`, item.url);
  item.loading = false;
  item.loadError = true;
  attachmentList.value = [...attachmentList.value];
};
// è§†é¢‘加载失败
const onVideoError = (item: any) => {
  console.error("视频加载失败:", item.url);
const onVideoError = (item: any, index: number) => {
  console.error(`视频加载失败 [${index}]:`, item.url);
  item.loading = false;
  item.loadError = true;
  attachmentList.value = [...attachmentList.value];
};
// æ–°å¢žé™„ä»¶
@@ -405,14 +477,23 @@
  .attachment-card {
    width: 100%;
    aspect-ratio: 1;
    position: relative;
    // ä½¿ç”¨ padding-top å®žçŽ°æ­£æ–¹å½¢ï¼ˆå…¼å®¹æ€§æ›´å¥½ï¼‰
    &::before {
      content: "";
      display: block;
      padding-top: 100%; // é«˜åº¦ç­‰äºŽå®½åº¦
    }
  }
}
.media-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  border-radius: 8px;
  overflow: hidden;
  background: #f5f5f5;
@@ -421,6 +502,25 @@
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }
  .loading-mask {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.3);
    z-index: 5;
    .loading-text {
      font-size: 12px;
      color: #fff;
    }
  }
  .file-icon-wrapper {
@@ -448,6 +548,16 @@
        color: #ff4757;
      }
    }
    &.error-overlay {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(255, 255, 255, 0.9);
      z-index: 5;
    }
  }
  .delete-btn {
src/utils/request.ts
@@ -36,10 +36,12 @@
          uni.showToast({
            title: resData.msg || "业务处理失败",
            icon: "none",
            duration: 2000,
          });
          reject({
            message: resData.msg || "业务处理失败",
            code: resData.code,
            duration: 2000,
          });
        }
      },
@@ -53,6 +55,7 @@
        reject({
          message: "网络请求失败",
          error,
          duration: 2000,
        });
      },
    });