spring
2025-11-19 af4f45eaa2703ecf991bd10f07f6df179f2677d9
src/pages/routingInspection/detail/indexLS.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,788 @@
<template>
  <view class="fixed-header">
    <view class="header-container">
      <wd-button
        icon="file-add"
        :round="false"
        size="small"
        custom-class="add_btn"
        @click="editList"
        v-if="!isEdit"
      >
        ç¼–辑
      </wd-button>
      <wd-button
        icon="close"
        type="info"
        :round="false"
        size="small"
        custom-class="add_btn"
        @click="close"
        v-if="isEdit"
      >
        å–消
      </wd-button>
      <wd-button
        icon="check"
        type="success"
        :round="false"
        size="small"
        custom-class="add_btn"
        @click="saveList"
        v-if="isEdit"
      >
        ä¿å­˜
      </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>
    </view>
  </view>
  <view class="list">
    <!-- åŸºæœ¬ä¿¡æ¯æ¨¡å— -->
    <wd-row>
      <view style="margin: 10rpx">
        <text class="title">{{ "基本信息" }}</text>
      </view>
      <wd-col :span="24">
        <wd-form-item label="日期" prop="recordDate">
          {{ formatDate(detailData.fixedInfo?.recordDate) }}
        </wd-form-item>
        <wd-form-item label="机台" prop="deviceModel">
          {{ formatValue(detailData.fixedInfo?.deviceModel) }}
        </wd-form-item>
        <wd-form-item label="班次" prop="workShift">
          {{ formatValue(detailData.fixedInfo?.workShift) }}
        </wd-form-item>
        <wd-form-item label="班组" prop="teamName">
          {{ formatValue(detailData.fixedInfo?.teamName) }}
        </wd-form-item>
        <wd-form-item label="单丝规格" prop="model">
          {{ formatValue(detailData.fixedInfo?.model) }}
        </wd-form-item>
        <wd-form-item label="生产轴数" prop="outputNumber">
          {{ formatValue(detailData.fixedInfo?.outputNumber, "è½´") }}
        </wd-form-item>
        <wd-form-item label="型号" prop="poleModel">
          {{ formatValue(detailData.fixedInfo?.poleModel) }}
        </wd-form-item>
        <wd-form-item label="批次" prop="poleNumber">
          {{ formatValue(detailData.fixedInfo?.poleNumber) }}
        </wd-form-item>
        <wd-form-item label="记录人" prop="createUserName">
          {{ formatValue(detailData.fixedInfo?.createUserName) }}
        </wd-form-item>
        <wd-form-item label="首检盘号" prop="firstNo">
          {{ formatValue(detailData.fixedInfo?.firstNo) }}
        </wd-form-item>
      </wd-col>
    </wd-row>
    <!-- å·¥è‰ºè®°å½•详情模块 -->
    <wd-row>
      <view style="margin: 10rpx">
        <text class="title">{{ "工艺记录详情" }}</text>
      </view>
      <wd-col :span="24">
        <wd-form-item label="巡检员" prop="processInspectionUserName">
          {{ detailData.processInspectionUserName || "-" }}
        </wd-form-item>
        <wd-form-item label="状态" prop="status">
          <wd-tag custom-class="space" :type="getStatusType(detailData.status)">
            {{ getStatusText(detailData.status) }}
          </wd-tag>
        </wd-form-item>
      </wd-col>
    </wd-row>
    <!-- æ£€éªŒç»“æžœ -->
    <wd-row>
      <view style="margin: 10rpx">
        <text class="title">{{ "检验结果" }}</text>
      </view>
      <wd-col :span="24">
        <wd-form-item label="单丝直径" prop="dia">
          {{ formatValue(detailData.inspectionResult?.dia, "mm") || "-" }}
        </wd-form-item>
        <wd-form-item label="最大直径" prop="maxDia" required>
          <template v-if="isEdit">
            <wd-input v-model="formData.maxDia" placeholder="请输入最大直径(mm)" type="number" />
          </template>
          <template v-else>
            {{ formatValue(detailData.inspectionResult?.maxDia, "mm") || "-" }}
          </template>
        </wd-form-item>
        <wd-form-item label="最小直径" prop="minDia" required>
          <template v-if="isEdit">
            <wd-input v-model="formData.minDia" placeholder="请输入最小直径(mm)" type="number" />
          </template>
          <template v-else>
            {{ formatValue(detailData.inspectionResult?.minDia, "mm") || "-" }}
          </template>
        </wd-form-item>
        <wd-form-item label="外观" prop="appearance" required>
          <template v-if="isEdit">
            <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>
            </view>
          </template>
          <template v-else>
            {{ formatProductAppearance(formData.appearance) }}
          </template>
        </wd-form-item>
        <wd-form-item label="卷绕紧密" prop="windingTightness" required>
          <template v-if="isEdit">
            <wd-radio-group
              v-model="formData.windingTightness"
              inline
              class="conclusion-radio-group"
            >
              <wd-radio
                v-for="(opt, idx) in sampleCompleteOptions"
                :key="idx"
                :value="opt.value"
                shape="dot"
              >
                {{ opt.label }}
              </wd-radio>
            </wd-radio-group>
          </template>
          <template v-else>
            {{ formatValue(detailData.inspectionResult?.windingTightness) }}
          </template>
        </wd-form-item>
        <wd-form-item label="排列整齐" prop="arrangementNeatness" required>
          <template v-if="isEdit">
            <wd-radio-group
              v-model="formData.arrangementNeatness"
              inline
              class="conclusion-radio-group"
            >
              <wd-radio
                v-for="(opt, idx) in sampleCompleteOptions"
                :key="idx"
                :value="opt.value"
                shape="dot"
              >
                {{ opt.label }}
              </wd-radio>
            </wd-radio-group>
          </template>
          <template v-else>
            {{ formatValue(detailData.inspectionResult?.arrangementNeatness) }}
          </template>
        </wd-form-item>
        <wd-form-item
          label="外层铝线离侧板边缘距离"
          prop="aluminumWireDistance"
          label-width="360rpx"
          required
        >
          <template v-if="isEdit">
            <wd-input
              v-model="formData.aluminumWireDistance"
              placeholder="请输入距离(mm)"
              type="number"
            />
          </template>
          <template v-else>
            {{ formatValue(detailData.inspectionResult?.aluminumWireDistance, "mm") || "-" }}
          </template>
        </wd-form-item>
        <wd-form-item label="成品模后接头情况" prop="jointCondition" label-width="280rpx" required>
          <template v-if="isEdit">
            <wd-radio-group v-model="formData.jointCondition" inline class="conclusion-radio-group">
              <wd-radio
                v-for="(opt, idx) in jointConditionOptions"
                :key="idx"
                :value="opt.value"
                shape="dot"
              >
                {{ opt.label }}
              </wd-radio>
            </wd-radio-group>
          </template>
          <template v-else>
            {{ formatValue(detailData.inspectionResult?.jointCondition) || "-" }}
          </template>
        </wd-form-item>
        <wd-form-item label="结论" prop="conclusion" required>
          <template v-if="isEdit">
            <wd-radio-group v-model="formData.conclusion" inline class="conclusion-radio-group">
              <wd-radio
                v-for="(opt, idx) in conclusionOptions"
                :key="idx"
                :value="opt.value"
                shape="dot"
              >
                {{ opt.label }}
              </wd-radio>
            </wd-radio-group>
          </template>
          <template v-else>
            {{ formatValue(detailData.inspectionResult?.conclusion) || "-" }}
          </template>
        </wd-form-item>
      </wd-col>
    </wd-row>
    <!-- å·¡æ£€ç»“æžœ -->
    <wd-row>
      <view style="margin: 10rpx">
        <text class="title">{{ "巡检结果" }}</text>
      </view>
      <wd-col :span="24">
        <wd-form-item
          label="铝杆前、中、尾样品是否齐全"
          prop="isFully"
          required
          label-width="420rpx"
        >
          <template v-if="isEdit">
            <wd-radio-group v-model="formData.isFully" inline class="conclusion-radio-group">
              <wd-radio
                v-for="(opt, idx) in sampleCompleteOptions"
                :key="idx"
                :value="opt.value"
                shape="dot"
              >
                {{ opt.label }}
              </wd-radio>
            </wd-radio-group>
          </template>
          <template v-else>
            <wd-tag
              custom-class="space"
              :type="detailData.processInspectionResult?.isFully ? 'success' : 'danger'"
            >
              {{ detailData.processInspectionResult?.isFully ? "是" : "否" }}
            </wd-tag>
          </template>
        </wd-form-item>
      </wd-col>
    </wd-row>
    <!-- é™„件模块 -->
    <wd-row class="attachment-section">
      <view style="margin: 10rpx">
        <text class="title">{{ "附件" }}</text>
      </view>
      <wd-col :span="24">
        <AttachmentUpload
          :detailData="detailData"
          :isEdit="isEdit"
          :deviceType="paramsType"
          ref="attachmentRef"
        />
      </wd-col>
    </wd-row>
    <wd-popup v-model="show" custom-style="border-radius:32rpx;" @close="handleClose">
      <div class="image-preview">
        <img :src="previewImageUrl" alt="预览图片" style="width: 100%; height: auto" />
      </div>
    </wd-popup>
    <wd-toast />
  </view>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onUnmounted } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import RoutingInspectionApi from "@/api/routingInspection/routingInspection";
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("");
const paramsType = ref("");
const detailData = ref<any>({});
const show = ref(false);
const previewImageUrl = ref("");
const isEdit = ref(false);
const tempFiles = ref<any[]>([]);
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: [] as string[],
  windingTightness: "",
  arrangementNeatness: "",
  aluminumWireDistance: "",
  jointCondition: "",
  conclusion: "",
  isFully: "",
});
// å¤–观选项
const appearanceOptions = [
  { label: "无外观问题", value: "无外观问题" },
  { label: "表面划伤", value: "表面划伤" },
  { label: "直径不均", value: "直径不均" },
  { label: "其他缺陷", value: "其他缺陷" },
];
const sampleCompleteOptions = [
  { label: "是", value: "是" },
  { label: "否", value: "否" },
];
const jointConditionOptions = [
  { label: "有", value: "有" },
  { label: "无", value: "无" },
];
const conclusionOptions = [
  { label: "合格", value: "合格" },
  { label: "不合格", value: "不合格" },
];
// çŠ¶æ€æ˜ å°„
const getStatusType = (status: number) => {
  switch (status) {
    case 0:
      return "warning";
    case 1:
      return "danger";
    case 2:
      return "primary";
    case 3:
      return "success";
    default:
      return "default";
  }
};
const getStatusText = (status: number) => {
  switch (status) {
    case 0:
      return "待巡检";
    case 1:
      return "已驳回";
    case 2:
      return "待审核";
    case 3:
      return "通过";
    default:
      return "未知";
  }
};
// æ ¼å¼åŒ–工具
const formatProductAppearance = (productAppearance: string[]) => {
  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) => {
  if (value === null || value === undefined || value === "") return "-";
  return unit ? `${value}${unit}` : value;
};
const formatDate = (date: string) => {
  if (!date) return "-";
  return new Date(date).toLocaleDateString("zh-CN", {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  });
};
// åˆå§‹åŒ–表单
const initFormData = () => {
  const inspectionResult = detailData.value.inspectionResult || {};
  const processInspectionResult = detailData.value.processInspectionResult || {};
  formData.dia = inspectionResult.dia || "";
  formData.maxDia = inspectionResult.maxDia || "";
  formData.minDia = inspectionResult.minDia || "";
  // ç¡®ä¿ 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 || "";
  formData.jointCondition = inspectionResult.jointCondition || "";
  formData.conclusion = inspectionResult.conclusion || "";
  formData.isFully = processInspectionResult.isFully ? "是" : "否";
};
// èŽ·å–è¯¦æƒ…
const getDetailData = async (id: string, deviceType: string) => {
  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) {
    console.error("获取详情失败:", error);
  }
};
// é¡µé¢åŠ è½½
onLoad((options: any) => {
  paramsId.value = options.id;
  paramsType.value = options.deviceType;
  getDetailData(options.id, options.deviceType);
});
// ç¼–辑切换
const editList = () => {
  isEdit.value = true;
};
// å–消编辑
const close = () => {
  isEdit.value = false;
  tempFiles.value = [];
  initFormData();
};
// ä¿å­˜ç¼–辑
const saveList = async () => {
  // æ ¡éªŒ
  if (!formData.maxDia) return uni.showToast({ title: "最大直径为必填项", icon: "none" });
  if (!formData.minDia) return uni.showToast({ title: "最小直径为必填项", icon: "none" });
  if (!formData.appearance.length) return uni.showToast({ title: "外观为必填项", icon: "none" });
  if (!formData.windingTightness) return uni.showToast({ title: "卷绕紧密为必填项", icon: "none" });
  if (!formData.arrangementNeatness)
    return uni.showToast({ title: "排列整齐为必填项", icon: "none" });
  if (!formData.aluminumWireDistance)
    return uni.showToast({ title: "外层铝线离侧板边缘距离为必填项", icon: "none" });
  if (!formData.jointCondition)
    return uni.showToast({ title: "成品模后接头情况为必填项", icon: "none" });
  if (!formData.conclusion) return uni.showToast({ title: "结论为必填项", icon: "none" });
  if (!formData.isFully) 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];
  // æäº¤
  try {
    const res = await RoutingInspectionApi.drawPatrolCheckInspection({
      deviceUid: deviceUid.value,
      id: paramsId.value,
      inspectionResult: {
        dia: formData.dia,
        maxDia: formData.maxDia,
        minDia: formData.minDia,
        appearance: formData.appearance,
        windingTightness: formData.windingTightness,
        arrangementNeatness: formData.arrangementNeatness,
        aluminumWireDistance: formData.aluminumWireDistance,
        jointCondition: formData.jointCondition,
        conclusion: formData.conclusion,
      },
      result: { isFully: formData.isFully },
      processInspectionAttachmentList: allFileIds,
    });
    if (res.code === 200) {
      // è®¾ç½®åˆ·æ–°æ ‡è®°ï¼Œå‘Šè¯‰åˆ—表页需要刷新
      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" });
    }
  } catch (e) {
    console.error("保存失败:", e);
    uni.showModal({ title: e.message || "保存失败", icon: "error" });
  }
};
const handleClose = () => {
  show.value = false;
};
const openScan = () => {
  console.log("indexLS - ç‚¹å‡»æ‰«ç æŒ‰é’®ï¼ˆå…¨å±€æ‰«ç æ¨¡å¼ï¼Œæ— éœ€æ‰‹åŠ¨è§¦å‘ï¼‰");
  // å…¨å±€æ‰«ç æ¨¡å¼ä¸‹ï¼Œç¡¬ä»¶æ‰«ç ä¼šè‡ªåŠ¨è§¦å‘ï¼Œæ— éœ€æ‰‹åŠ¨è°ƒç”¨
  uni.showToast({
    title: "请使用扫码枪扫描",
    icon: "none",
  });
};
// é¡µé¢æ˜¾ç¤ºæ—¶çš„处理
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);
    }
  }
});
</script>
<style lang="scss" scoped>
.fixed-header {
  position: fixed;
  top: 44;
  left: 0;
  right: 0;
  background: #f3f9f8;
  z-index: 999;
  padding: 12px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  min-height: 60px;
  box-sizing: border-box;
  overflow: visible;
}
.header-container {
  display: flex;
  align-items: center;
  width: 100%;
  gap: 10px;
}
.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 {
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6px;
  flex-shrink: 0;
}
.list {
  padding: 12px;
  padding-top: 84px;
  background: #f3f9f8;
  min-height: 100vh;
  box-sizing: border-box;
  overflow-y: auto;
}
.title {
  position: relative;
  margin-left: 10px;
  font-size: 16px;
  font-weight: 500;
  color: #0d867f;
}
.title::after {
  position: absolute;
  content: "";
  top: 4px;
  left: -10px;
  width: 4px;
  height: 16px;
  background: #0d867f;
  border-radius: 2px;
}
.attachment-section {
  width: 100%;
}
.attachment-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 10px 0;
}
.attachment-item {
  width: calc(25% - 10px);
  box-sizing: border-box;
  position: relative;
}
.upload-btn {
  width: 80px;
  height: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}
.upload-icon {
  font-size: 32px;
  color: #0d867f;
}
.delete-icon {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 24px;
  height: 24px;
  background-color: rgba(255, 0, 0, 0.8);
  color: white;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
}
@media (max-width: 768px) {
  .attachment-item {
    width: calc(25% - 10px);
    margin: 10;
  }
}
:deep(.wd-form-item) {
  margin-bottom: 8rpx;
}
:deep(.wd-input, .wd-select, .wd-radio-group, .wd-checkbox-group) {
  width: 100%;
  box-sizing: border-box;
}
:deep(.wd-form-item__label)::after {
  content: "*";
  color: red;
  margin-left: 4rpx;
}
:deep(.wd-select) {
  width: 100%;
}
:deep(.wd-checkbox) {
  margin-right: 0;
}
.conclusion-radio-group {
  display: flex;
  align-items: flex-start; // åž‚直方向顶部对齐(上移关键)
  gap: 20rpx; // é€‰é¡¹ä¹‹é—´çš„间距
}
</style>