a9561a3189d50f0e33d693b5226ea56adda06a08..883ab99ab021be1adc6dc5689368f899ea436e54
4 天以前 yyb
完成
883ab9 对比 | 目录
4 天以前 yyb
除附件
491585 对比 | 目录
已添加1个文件
已修改4个文件
1643 ■■■■ 文件已修改
src/api/routingInspection/routingInspection.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/routingInspection/detail/indexJX.vue 564 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/routingInspection/detail/indexLS.vue 700 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/routingInspection/product_card/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/routingInspection/upload.vue 359 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/routingInspection/routingInspection.ts
@@ -34,6 +34,22 @@
      // data: params,
    });
  },
  // æ‹‰ä¸å·¡æ£€ä¿å­˜
  drawPatrolCheckInspection(data: any) {
    return request<BaseResult<any>>({
      url: "/wireInspection/drawPatrolCheckInspection?deviceUid=" + data.deviceUid,
      method: "POST",
      data: data,
    });
  },
  // ç»žçº¿å·¡æ£€ä¿å­˜
  strandedPatrolCheckInspection(data: any) {
    return request<BaseResult<any>>({
      url: "/wireInspection/strandedPatrolCheckInspection?deviceUid=" + data.deviceUid,
      method: "POST",
      data: data,
    });
  },
};
export default RoutingInspectionApi;
src/pages/routingInspection/detail/indexJX.vue
@@ -73,11 +73,22 @@
        <wd-form-item label="张力设置" prop="twistTension">
          {{ formatValue(recordData.fixedInfo?.twistTension, "N/m") }}
        </wd-form-item>
        <wd-form-item label="绞制外径" prop="twistDiameter">
          {{ formatValue(recordData.inspectionResult?.twistDiameter, "mm") }}
        <!-- ç»žåˆ¶å¤–径(可编辑) -->
        <wd-form-item label="绞制外径" prop="twistDiameter" required>
          <template v-if="isEdit">
            <wd-input
              v-model="formData.twistDiameter"
              placeholder="请输入绞制外径(mm)"
              type="number"
            />
          </template>
          <template v-else>
            {{ formatValue(formData.twistDiameter, "mm") }}
          </template>
        </wd-form-item>
      </wd-col>
    </wd-row>
    <!-- è‡ªæ£€è®°å½•详情模块 -->
    <wd-row>
      <view style="margin: 10rpx">
@@ -97,54 +108,76 @@
        </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="inspectStructure">
          {{
            recordData.structureInfo.structureRecordResult.inspectStructure.structureFormula || "-"
          }}
        <wd-form-item label="成品结构" prop="structureFormula" required>
          <template v-if="isEdit">
            <wd-input v-model="formData.structureFormula" placeholder="请输入成品结构" />
          </template>
          <template v-else>
            {{ formData.structureFormula || "-" }}
          </template>
        </wd-form-item>
      </wd-col>
    </wd-row>
    <wd-row v-if="recordData.structureInfo.structureRecordResult.inspectStructure.structureItems">
    <!-- ç»“构标准值和实测(可编辑) -->
    <wd-row v-if="formData.structureItems.length">
      <view style="margin: 10rpx">
        <text class="title">{{ "结构标准值和实测" }}</text>
      </view>
      <wd-col
        :span="24"
        v-for="(item, index) in recordData.structureInfo.structureRecordResult.inspectStructure
          .structureItems"
        v-for="(item, index) in formData.structureItems"
        :key="index"
        style="padding-bottom: 10px"
      >
        <wd-form-item
          prop="structureItemsGroup"
          :label="formatValue(item.structureName)"
          label-width="400rpx"
          style="color: red"
          required
        ></wd-form-item>
        <wd-form-item label="标准值" prop="structureValue">
        <wd-form-item label="标准值" prop="structureValue" required>
          {{ formatValue(item.structureValue) }}
        </wd-form-item>
        <wd-form-item label="实测根数" prop="actualValue1">
          {{ formatValue(item.actualValue1, "æ ¹") }}
        <wd-form-item label="实测根数" prop="actualValue1" required>
          <template v-if="isEdit">
            <wd-input v-model="item.actualValue1" placeholder="请输入实测根数" type="number" />
          </template>
          <template v-else>
            {{ formatValue(item.actualValue1, "æ ¹") }}
          </template>
        </wd-form-item>
        <wd-form-item label="实测直径" prop="actualValue2">
          {{ formatValue(item.actualValue2, "mm") }}
        <wd-form-item label="实测直径" prop="actualValue2" required>
          <template v-if="isEdit">
            <wd-input
              v-model="item.actualValue2"
              placeholder="请输入实测直径(mm)"
              type="number"
            />
          </template>
          <template v-else>
            {{ formatValue(item.actualValue2, "mm") }}
          </template>
        </wd-form-item>
      </wd-col>
    </wd-row>
    <!-- ç»žçº¿å·¥è‰ºè´¨é‡æŽ§åˆ¶æ¨¡å— -->
    <wd-row v-if="recordData.structureInfo?.structureRecordResult?.inspectTwist">
    <!-- ç»žçº¿å·¥è‰ºè´¨é‡æŽ§åˆ¶ï¼ˆå¯ç¼–辑) -->
    <wd-row v-if="formData.inspectTwist.length">
      <view style="margin: 10rpx">
        <text class="title">{{ "绞线工艺质量控制" }}</text>
      </view>
      <wd-col
        :span="24"
        v-for="(item, index) in recordData.structureInfo.structureRecordResult.inspectTwist"
        v-for="(item, index) in formData.inspectTwist"
        :key="index"
        style="padding-bottom: 10px"
      >
@@ -152,87 +185,319 @@
          :label="formatValue(item.twistName)"
          label-width="400rpx"
          style="color: red"
          prop="inspectTwistGroup"
          required
        ></wd-form-item>
        <wd-form-item label="绞向" prop="direction">
          {{ formatValue(item.direction) }}
        <wd-form-item label="绞向" prop="direction" required>
          <template v-if="isEdit">
            <wd-select-picker
              label=""
              v-model="item.direction"
              :columns="twistDirectionOptions"
              type="radio"
              placeholder="请选择绞向"
              :clearable="false"
            ></wd-select-picker>
          </template>
          <template v-else>
            {{ formatValue(item.direction) }}
          </template>
        </wd-form-item>
        <wd-form-item label="节距" prop="pitch">{{ formatValue(item.pitch, "mm") }}</wd-form-item>
        <wd-form-item label="节径比" prop="pitchRatio">
        <wd-form-item label="节距" prop="pitch" required>
          <template v-if="isEdit">
            <wd-input v-model="item.pitch" placeholder="请输入节距(mm)" type="number" />
          </template>
          <template v-else>
            {{ formatValue(item.pitch, "mm") }}
          </template>
        </wd-form-item>
        <wd-form-item label="节径比" prop="pitchRatio" required>
          {{ formatValue(item.pitchRatio) }}
        </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="conclusion">
          {{ formatValue(recordData.structureInfo.structureRecordResult.conclusion) }}
        <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">
                {{ opt.label }}
              </wd-checkbox>
            </wd-checkbox-group>
          </template>
          <template v-else>
            {{ formatProductAppearance(formData.productAppearance) }}
          </template>
        </wd-form-item>
        <wd-form-item label="产品外观" prop="productAppearance">
          {{
            formatProductAppearance(
              recordData.structureInfo.structureRecordResult.productAppearance
            )
          }}
        <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(formData.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="sampleComplete">
          {{ formatValue(recordData.inspectionResult?.sampleComplete) }}
        <wd-form-item label="样品是否齐全" prop="sampleComplete" required>
          <template v-if="isEdit">
            <wd-radio-group v-model="formData.sampleComplete" 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(formData.sampleComplete) }}
          </template>
        </wd-form-item>
      </wd-col>
    </wd-row>
    <!-- é™„件模块 -->
    <!-- é™„件模块(含上传功能) -->
    <wd-row class="attachment-section">
      <view style="margin: 10rpx">
        <text class="title">{{ "附件" }}</text>
      </view>
      <!-- ç”¨ flex å®¹å™¨åŒ…裹图片列,实现自动换行 -->
      <view class="attachment-grid">
        <wd-col
          v-for="(file, index) in recordData.structureInfo.files"
          :key="index"
          class="attachment-item"
        >
          <wd-img :width="80" :height="80" :src="file.url" @click="previewImage(file.url)">
            <template #error>
              <view class="error-wrap">加载失败</view>
            </template>
            <template #loading>
              <view class="loading-wrap">
                <wd-loading />
              </view>
            </template>
          </wd-img>
        </wd-col>
      </view>
      <wd-col :span="24">
        <AttachmentUpload
          :detailData="detailData"
          :isEdit="isEdit"
          :deviceType="paramsType"
          ref="attachmentRef"
          v-if="detailDataLoaded"
        />
      </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>
    <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 RoutingInspectionApi from "@/api/routingInspection/routingInspection";
import Scan from "@/components/scan/index.vue";
import { useToast } from "wot-design-uni";
import AttachmentUpload from "../upload.vue";
const paramsType = ref("");
const paramsId = ref("");
const recordData = ref<any>({});
const recordData = ref<any>({ structureInfo: { files: [], structureRecordResult: {} } });
const show = ref(false);
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 formData = reactive({
  twistDiameter: "", // ç»žåˆ¶å¤–径
  structureFormula: "", // æˆå“ç»“æž„
  structureItems: [], // ç»“构标准值和实测
  inspectTwist: [], // ç»žçº¿å·¥è‰ºè´¨é‡æŽ§åˆ¶
  productAppearance: [] as string[], // äº§å“å¤–观(改为数组存储选中值)
  conclusion: "", // ç»“论(改为数组存储选中值)
  sampleComplete: "", // æ ·å“æ˜¯å¦é½å…¨
});
const twistDirectionOptions = [
  { label: "左向", value: "左向" },
  { label: "右向", value: "右向" },
];
const appearanceOptions = [
  { label: "无外观问题", value: "无外观问题" },
  { label: "表面划伤", value: "表面划伤" },
  { label: "直径不均", value: "直径不均" },
  { label: "其他缺陷", value: "其他缺陷" },
];
const conclusionOptions = [
  { label: "合格", value: "合格" },
  { label: "不合格", value: "不合格" },
];
const sampleCompleteOptions = [
  { label: "是", value: "是" },
  { label: "否", value: "否" },
];
const initFormData = () => {
  const structureResult = recordData.value.structureInfo?.structureRecordResult || {};
  const inspectionResult = recordData.value.inspectionResult || {};
  formData.twistDiameter = inspectionResult.twistDiameter || "";
  formData.structureFormula = structureResult.inspectStructure?.structureFormula || "";
  formData.sampleComplete = inspectionResult.sampleComplete || "";
  formData.conclusion = structureResult.conclusion || "";
  formData.productAppearance = Array.isArray(structureResult.productAppearance)
    ? structureResult.productAppearance
    : structureResult.productAppearance
    ? [structureResult.productAppearance]
    : [];
  formData.structureItems = JSON.parse(
    JSON.stringify(structureResult.inspectStructure?.structureItems || [])
  );
  formData.inspectTwist = JSON.parse(JSON.stringify(structureResult.inspectTwist || []));
  formData.inspectTwist.forEach((item: any) => {
    if (!item.direction) item.direction = "";
  });
};
const getDetailData = async (id: string, deviceType: string) => {
  try {
    const response = await RoutingInspectionApi.getStrandedInspectionStructureInfoById({ id });
    recordData.value = response.data;
    detailData.value = response.data.structureInfo;
    console.log("detailData.value", detailData.value);
    tempFiles.value = []; // æ¸…空临时文件
    initFormData(); // æ•°æ®è¿”回后初始化表单
    detailDataLoaded.value = true; // æ•°æ®åŠ è½½å®ŒæˆåŽï¼Œæ¸²æŸ“å­ç»„ä»¶
    console.log("父组件-数据就绪后打印");
  } catch (error) {
    console.error("获取详情失败:", error);
    uni.showToast({ title: "加载失败", icon: "error" });
  }
};
// é¡µé¢åŠ è½½
onLoad((options: any) => {
  try {
    paramsId.value = options.id;
    paramsType.value = options.deviceType;
    getDetailData(options.id, options.deviceType);
  } catch (error) {
    console.error("获取详情失败:", error);
    uni.showToast({ title: "加载失败", icon: "error" });
  }
});
// ç¼–辑模式切换
const editList = () => {
  isEdit.value = true;
};
// å–消编辑(重置表单)
const close = () => {
  isEdit.value = false;
  tempFiles.value = [];
  initFormData();
};
// ä¿å­˜ç¼–辑(含必填项校验)
const saveList = async () => {
  // 1. åŸºç¡€å­—段校验
  if (!formData.structureFormula) return uni.showToast({ title: "成品结构为必填项", icon: "none" });
  if (!formData.twistDiameter) 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" });
  if (!formData.sampleComplete)
    return uni.showToast({ title: "样品是否齐全为必填项", icon: "none" });
  // 2. ç»“构项循环校验
  for (const item of formData.structureItems) {
    if (!item.structureValue)
      return uni.showToast({ title: `${item.structureName}标准值为必填项`, icon: "none" });
    if (!item.actualValue1)
      return uni.showToast({ title: `${item.structureName}实测根数为必填项`, icon: "none" });
    if (!item.actualValue2)
      return uni.showToast({ title: `${item.structureName}实测直径为必填项`, icon: "none" });
  }
  // 3. ç»žçº¿å·¥è‰ºé¡¹å¾ªçŽ¯æ ¡éªŒ
  for (const item of formData.inspectTwist) {
    if (!item.direction)
      return uni.showToast({ title: `${item.twistName}绞向为必填项`, icon: "none" });
    if (!item.pitch) return uni.showToast({ title: `${item.twistName}节距为必填项`, icon: "none" });
    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" });
  const { newFiles } = attachmentRef.value.getSubmitFiles();
  console.log("newFiles", newFiles);
  const allFileIds = [...newFiles];
  try {
    const res = await RoutingInspectionApi.strandedPatrolCheckInspection({
      deviceUid: deviceUid.value,
      id: paramsId.value,
      result: {
        twistDiameter: formData.twistDiameter,
        structureFormula: formData.structureFormula,
        structureItems: formData.structureItems,
        inspectTwist: formData.inspectTwist,
        productAppearance: formData.productAppearance,
        conclusion: formData.conclusion,
        sampleComplete: formData.sampleComplete,
      },
      inspectionResult: {
        sampleComplete: formData.sampleComplete,
      },
      processInspectionAttachmentList: allFileIds,
    });
    if (res.code === 200) {
      uni.showToast({ title: "保存成功", icon: "success" });
      isEdit.value = false;
      getDetailData(paramsId.value, paramsType.value);
    } 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 getStatusType = (status: number) => {
@@ -266,12 +531,6 @@
  }
};
// èŽ·å–å¤–è§‚æ–‡æœ¬
const getAppearanceText = (appearance: string[]) => {
  if (!appearance || appearance.length === 0) return "-";
  return appearance.join("、");
};
// æ ¼å¼åŒ–产品外观显示
const formatProductAppearance = (productAppearance: string[]) => {
  if (!productAppearance || productAppearance.length === 0) return "-";
@@ -294,82 +553,40 @@
  });
};
// æ ¼å¼åŒ–结构值显示
const formatStructureValue = (value1: any, value2: any) => {
  const val1 = value1 || "-";
  const val2 = value2 ? `${value2}mm` : "-";
  return { count: val1, diameter: val2 };
const openScan = () => {
  scanRef.value.triggerScan();
};
const getDetailData = async (id: string, deviceType: string) => {
const getScanCode = (params: any) => {
  console.log("完整参数:", params);
  let codeObj = {};
  try {
    let response;
    // èŽ·å–ç»žçº¿å•ä¸ªç»“æž„
    response = await RoutingInspectionApi.getStrandedInspectionStructureInfoById({
      id: id,
    });
    recordData.value = response.data;
    console.log(recordData.value);
  } catch (error) {
    console.error("获取详情失败:", error);
    codeObj = JSON.parse(params.code);
  } catch (err) {
    console.error("JSON解析失败:", err);
    toast.error("扫码数据异常");
    return;
  }
  deviceUid.value = codeObj?.uid;
  toast.success("扫码成功");
};
onLoad((options: any) => {
  paramsId.value = options.id;
  getDetailData(options.id, options.deviceType);
// ç¡®ä¿å…ˆç§»é™¤å†æ·»åŠ ç›‘å¬
const setupScanListener = () => {
  uni.$off("scan", getScanCode); // å…ˆç§»é™¤æ—§çš„
  uni.$on("scan", getScanCode); // å†æ·»åŠ æ–°çš„
};
onUnmounted(() => {
  // å¼€å¯å¹¿æ’­ç›‘听事件
  uni.$off("scan", getScanCode);
  console.log("离开1");
});
const previewImage = (url: string) => {
  previewImageUrl.value = url;
  show.value = true;
};
const handleClose = () => {
  show.value = false;
};
// ç¼–辑列表
const editList = () => {};
// å…³é—­
const close = () => {};
// ä¿å­˜åˆ—表
const saveList = async () => {
  // try {
  //   let response;
  //   // ä¿å­˜åˆ—表
  //   response = await RoutingInspectionApi.saveStrandedInspectionStructureInfo({
  //     id: paramsId.value,
  //     structureInfo: recordData.value.structureInfo,
  //   });
  //   if (response.code === 200) {
  //     uni.showToast({
  //       title: "保存成功",
  //       icon: "success",
  //     });
  //     // åˆ·æ–°è¯¦æƒ…数据
  //     getDetailData(paramsId.value, recordData.value.deviceType);
  //   } else {
  //     uni.showToast({
  //       title: response.msg || "保存失败",
  //       icon: "error",
  //     });
  //   }
  // } catch (error) {
  //   console.error("保存列表失败:", error);
  //   uni.showToast({
  //     title: "保存失败",
  //     icon: "error",
  //   });
  // }
};
onMounted(() => {
  // å¼€å¯å¹¿æ’­ç›‘听事件
  setupScanListener();
  console.log("显示1");
});
</script>
<style lang="scss" scoped>
.placeholder {
  flex: 1;
}
.fixed-header {
  position: fixed;
  top: 44;
@@ -391,14 +608,8 @@
  gap: 10px;
}
.search-wrapper {
.placeholder {
  flex: 1;
  min-width: 0;
}
:deep(.search-wrapper .wd-search) {
  width: 100% !important;
  min-width: 0 !important;
}
.scan-wrapper {
@@ -413,11 +624,11 @@
.list {
  padding: 12px;
  padding-top: 84px;
  background: #f3f9f8;
  min-height: 100vh;
  box-sizing: border-box;
  overflow-y: auto;
}
.title {
@@ -439,6 +650,20 @@
  border-radius: 2px;
}
// äº§å“å¤–观和结论选择器样式(一行两个)
.checkbox-group {
  display: flex;
  flex-wrap: wrap;
  gap: 16rpx;
  padding: 8rpx 0;
}
.checkbox-item {
  width: calc(50% - 8rpx);
  margin-bottom: 8rpx;
}
// é™„件相关样式
.attachment-section {
  width: 100%;
}
@@ -453,6 +678,39 @@
.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) {
@@ -460,4 +718,36 @@
    width: calc(25% - 10px);
  }
}
</style>
// ç¼–辑模式下表单组件样式优化
: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>
src/pages/routingInspection/detail/indexLS.vue
@@ -40,173 +40,334 @@
    </view>
  </view>
  <view class="list">
    <div class="inspection-report">
      <!-- åŸºæœ¬ä¿¡æ¯æ¨¡å— -->
      <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-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-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">
    <!-- è‡ªæ£€è®°å½•详情模块 -->
    <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") || "-" }}
          </wd-form-item>
          <wd-form-item label="最小直径" prop="minDia">
          </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") || "-" }}
          </wd-form-item>
          <wd-form-item label="外观" prop="appearance">
            {{ formatProductAppearance(detailData.inspectionResult?.appearance) || "-" }}
          </wd-form-item>
          <wd-form-item label="卷绕紧密" prop="windingTightness">
          </template>
        </wd-form-item>
        <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">
                {{ opt.label }}
              </wd-checkbox>
            </wd-checkbox-group>
          </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) }}
          </wd-form-item>
          <wd-form-item label="排列整齐" prop="arrangementNeatness">
          </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) }}
          </wd-form-item>
          <wd-form-item
            label="外层铝线离侧板边缘距离"
            prop="aluminumWireDistance"
            label-width="500rpx"
          >
          </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") || "-" }}
          </wd-form-item>
          <wd-form-item label="成品模后接头情况" prop="jointCondition" label-width="250rpx">
          </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) || "-" }}
          </wd-form-item>
          <wd-form-item label="结论" prop="conclusion">
          </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) || "-" }}
          </wd-form-item>
        </wd-col>
      </wd-row>
      <!-- å·¡æ£€ç»“æžœ -->
      <wd-row v-if="detailData.processInspectionResult?.isFully">
        <view style="margin: 10rpx">
          <text class="title">{{ "巡检结果" }}</text>
        </view>
        <wd-col :span="24">
          <wd-form-item label="铝杆前、中、尾样品是否齐全" prop="processInspectionUserName">
          </template>
        </wd-form-item>
      </wd-col>
    </wd-row>
    <!-- å·¡æ£€ç»“æžœ -->
    <wd-row v-if="detailData.processInspectionResult?.isFully">
      <view style="margin: 10rpx">
        <text class="title">{{ "巡检结果" }}</text>
      </view>
      <wd-col :span="24">
        <wd-form-item label="铝杆前、中、尾样品是否齐全" prop="isFully" required>
          <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>
          </wd-form-item>
        </wd-col>
      </wd-row>
      <!-- é™„件模块 -->
      <wd-row class="attachment-section" v-if="detailData.files && detailData.files.length > 0">
        <view style="margin: 10rpx">
          <text class="title">{{ "附件" }}</text>
        </view>
        <view class="attachment-grid">
          <wd-col v-for="(file, index) in detailData.files" :key="index" class="attachment-item">
            <wd-img :width="80" :height="80" :src="file.url" @click="previewImage(file.url)">
              <template #error>
                <view class="error-wrap">加载失败</view>
              </template>
              <template #loading>
                <view class="loading-wrap">
                  <wd-loading />
                </view>
              </template>
            </wd-img>
          </wd-col>
        </view>
      </wd-row>
    </div>
          </template>
        </wd-form-item>
      </wd-col>
    </wd-row>
    <!-- é™„件模块 -->
    <wd-row class="attachment-section" v-if="detailData.files && detailData.files.length > 0">
      <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>
    <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 RoutingInspectionApi from "@/api/routingInspection/routingInspection";
import Scan from "@/components/scan/index.vue";
import { useToast } from "wot-design-uni";
import AttachmentUpload from "../upload.vue";
// æ ¸å¿ƒçŠ¶æ€
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 deviceUid = ref("");
const scanRef = ref();
const toast = useToast();
const attachmentRef = ref<any>(null);
// çŠ¶æ€ç±»åž‹æ˜ å°„
// è¡¨å•数据
const formData = reactive({
  dia: "",
  maxDia: "",
  minDia: "",
  appearance: [],
  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"; // å¾…巡检
      return "warning";
    case 1:
      return "danger"; // å·²é©³å›ž
      return "danger";
    case 2:
      return "info"; // å¾…审核
      return "info";
    case 3:
      return "success"; // é€šè¿‡
      return "success";
    default:
      return "info";
  }
};
// çŠ¶æ€æ–‡æœ¬æ˜ å°„
const getStatusText = (status: number) => {
  switch (status) {
    case 0:
@@ -222,25 +383,16 @@
  }
};
// èŽ·å–å¤–è§‚æ–‡æœ¬
const getAppearanceText = (appearance: string[]) => {
  if (!appearance || appearance.length === 0) return "-";
  return appearance.join("、");
};
// æ ¼å¼åŒ–产品外观显示
// æ ¼å¼åŒ–工具
const formatProductAppearance = (productAppearance: string[]) => {
  if (!productAppearance || productAppearance.length === 0) return "-";
  return productAppearance.join("、");
  return !productAppearance.length ? "-" : productAppearance.join("、");
};
// æ ¼å¼åŒ–数值显示
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", {
@@ -250,89 +402,182 @@
  });
};
// æ ¼å¼åŒ–结构值显示
const formatStructureValue = (value1: any, value2: any) => {
  const val1 = value1 || "-";
  const val2 = value2 ? `${value2}mm` : "-";
  return { count: val1, diameter: val2 };
// åˆå§‹åŒ–表单
const initFormData = () => {
  const inspectionResult = detailData.value.inspectionResult || {};
  const processInspectionResult = detailData.value.processInspectionResult || {};
  formData.dia = inspectionResult.dia || "";
  formData.maxDia = inspectionResult.maxDia || "";
  formData.minDia = inspectionResult.minDia || "";
  formData.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 {
    let response;
    // èŽ·å–æ‹‰ä¸å•ä¸ªç»“æž„
    response = await RoutingInspectionApi.getDrawInspectInfoById({
      id: id,
    });
    const response = await RoutingInspectionApi.getDrawInspectInfoById({ id });
    detailData.value = response.data;
    console.log(detailData.value);
    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 previewImage = (url: string) => {
  previewImageUrl.value = url;
  show.value = true;
// ç¼–辑切换
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" });
  if (!deviceUid.value) return uni.showToast({ title: "请扫描二维码", icon: "none" });
  const { newFiles } = attachmentRef.value.getSubmitFiles();
  console.log("newFiles", newFiles);
  const allFileIds = [...newFiles];
  // æäº¤
  try {
    const res = await RoutingInspectionApi.drawPatrolCheckInspection({
      deviceUid: deviceUid.value,
      id: paramsId.value,
      result: {
        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,
      },
      inspectionResult: { isFully: formData.isFully },
      processInspectionAttachmentList: allFileIds,
    });
    if (res.code === 200) {
      uni.showToast({ title: "保存成功", icon: "success" });
      isEdit.value = false;
      getDetailData(paramsId.value, paramsType.value);
    } 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 editList = () => {};
// å…³é—­
const close = () => {};
// ä¿å­˜åˆ—表
const saveList = async () => {
  // try {
  //   let response;
  //   // ä¿å­˜åˆ—表
  //   response = await RoutingInspectionApi.saveStrandedInspectionStructureInfo({
  //     id: paramsId.value,
  //     structureInfo: recordData.value.structureInfo,
  //   });
  //   if (response.code === 200) {
  //     uni.showToast({
  //       title: "保存成功",
  //       icon: "success",
  //     });
  //     // åˆ·æ–°è¯¦æƒ…数据
  //     getDetailData(paramsId.value, recordData.value.deviceType);
  //   } else {
  //     uni.showToast({
  //       title: response.msg || "保存失败",
  //       icon: "error",
  //     });
  //   }
  // } catch (error) {
  //   console.error("保存列表失败:", error);
  //   uni.showToast({
  //     title: "保存失败",
  //     icon: "error",
  //   });
  // }
const openScan = () => {
  scanRef.value.triggerScan();
};
</script>
<style lang="scss" scoped>
.list {
  height: calc(100vh - 80px);
  padding: 12px;
  background: #f3f9f8;
  :deep() {
    .round {
      border-radius: 4px;
    }
const getScanCode = (params: any) => {
  let codeObj = {};
  try {
    codeObj = JSON.parse(params.code);
  } catch (err) {
    toast.error("扫码数据异常");
    return; // è§£æžå¤±è´¥ç›´æŽ¥è¿”回,避免后续错误
  }
  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>
<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-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 {
@@ -361,22 +606,79 @@
.attachment-grid {
  display: flex;
  flex-wrap: wrap;
  /* è¶…出自动换行 */
  gap: 10px;
  /* å›¾ç‰‡ä¹‹é—´çš„间距 */
  padding: 10px 0;
}
.attachment-item {
  width: calc(25% - 10px);
  /* æ¯è¡Œ4张,间距由gap控制,需计算宽度 */
  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;
}
/* é€‚配小屏幕,可调整每行数量(如每行2张) */
.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>
src/pages/routingInspection/product_card/index.vue
@@ -121,12 +121,12 @@
        </view>
      </wd-col>
      <wd-col :span="8">
        <view class="flex">
        <view class="flex" @click.stop>
          <wd-button
            v-if="data[map.status] == 1"
            size="small"
            type="primary"
            @click.stop="showRejectPopup = true"
            @click="showRejectPopup = true"
            style="margin-left: auto"
          >
            æŸ¥çœ‹é©³å›žä¿¡æ¯
src/pages/routingInspection/upload.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,359 @@
<template>
  <view class="attachment-container">
    <!-- å¤´éƒ¨æ“ä½œåŒº -->
    <view class="header-actions">
      <wd-button
        icon="file-add"
        :round="false"
        size="small"
        custom-class="add_btn"
        @click="addAttachment"
        v-if="isEdit"
      >
        æ–°å¢ž
      </wd-button>
    </view>
    <!-- é™„件列表 -->
    <view class="attachment-list">
      <wd-status-tip v-if="attachmentList.length === 0" image="content" tip="暂无附件" />
      <wd-card
        v-for="item in attachmentList"
        :key="item.id"
        type="rectangle"
        custom-class="attachment-card"
        :border="false"
      >
        <view class="attachment-item" @click="previewAttachment(item)">
          <view class="attachment-info">
            <view class="attachment-name">{{ item.bucketFileName || item.name }}</view>
            <view class="attachment-meta">
              <text class="file-type">{{ getFileType(item.bucketFileName) }}</text>
              <text class="upload-time">{{ formatTime(item.createTime) }}</text>
            </view>
          </view>
          <view class="attachment-actions" @click.stop v-if="isEdit">
            <wd-icon name="delete" color="#ff4757" @click="deleteAttachment(item.id)" />
          </view>
        </view>
      </wd-card>
    </view>
    <wd-toast />
  </view>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useToast } from "wot-design-uni";
import AttachmentAPI from "@/api/product/attachment";
// å¤–部参数
const props = defineProps({
  detailData: { type: Object, default: () => ({}) },
  isEdit: { type: Boolean, default: false },
  deviceType: { type: String, default: "" },
});
const toast = useToast();
const attachmentList =
  props.deviceType == "1"
    ? ref<any[]>(props.detailData.value.files || [])
    : ref<any[]>(props.detailData.files || []);
const attachmentIds =
  props.deviceType == "1"
    ? ref<string[]>(props.detailData.value.attachmentId || [])
    : ref<string[]>(props.detailData.attachmentId || []);
// æ–°å¢žé™„ä»¶
const addAttachment = () => {
  // æ˜¾ç¤ºé€‰æ‹©æ–‡ä»¶ç±»åž‹çš„弹窗
  uni.showActionSheet({
    itemList: ["选择图片", "选择视频", "拍照", "录像"],
    success: (res) => {
      switch (res.tapIndex) {
        case 0: // é€‰æ‹©å›¾ç‰‡
          chooseImages();
          break;
        case 1: // é€‰æ‹©è§†é¢‘
          chooseVideos();
          break;
        case 2: // æ‹ç…§
          takePhoto();
          break;
        case 3: // å½•像
          recordVideo();
          break;
      }
    },
    fail: (error) => {
      console.error("选择文件类型失败:", error);
      toast.show("选择文件类型失败");
    },
  });
};
// é€‰æ‹©å›¾ç‰‡
const chooseImages = () => {
  uni.chooseImage({
    count: 9,
    sizeType: ["original", "compressed"],
    sourceType: ["album"],
    success: async (res) => {
      const filePaths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePaths];
      await handleFileUpload(filePaths);
    },
    fail: (error) => {
      console.error("选择图片失败:", error);
      toast.show("选择图片失败");
    },
  });
};
// é€‰æ‹©è§†é¢‘
const chooseVideos = () => {
  uni.chooseVideo({
    sourceType: ["album"],
    maxDuration: 60,
    camera: "back",
    success: async (res) => {
      await handleFileUpload([res.tempFilePath]);
    },
    fail: (error) => {
      console.error("选择视频失败:", error);
      toast.show("选择视频失败");
    },
  });
};
// æ‹ç…§
const takePhoto = () => {
  uni.chooseImage({
    count: 1,
    sizeType: ["original", "compressed"],
    sourceType: ["camera"],
    success: async (res) => {
      const filePaths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePaths];
      await handleFileUpload(filePaths);
    },
    fail: (error) => {
      console.error("拍照失败:", error);
      toast.show("拍照失败");
    },
  });
};
// å½•像
const recordVideo = () => {
  uni.chooseVideo({
    sourceType: ["camera"],
    maxDuration: 60,
    camera: "back",
    success: async (res) => {
      await handleFileUpload([res.tempFilePath]);
    },
    fail: (error) => {
      console.error("录像失败:", error);
      toast.show("录像失败");
    },
  });
};
// å¤„理文件上传
const handleFileUpload = async (filePaths: string[]) => {
  try {
    toast.show("正在上传...");
    // ä¸Šä¼ æ–‡ä»¶
    const uploadResults: any = await AttachmentAPI.uploadAttachmentFiles(filePaths);
    const result = uploadResults.map((it: any) => {
      return it.data;
    });
    console.log("result", result);
    // æ›´æ–°é™„件列表
    const flattenedResult = result.flat();
    attachmentList.value.push(...flattenedResult);
    console.log(attachmentList.value);
    // æå–附件ID
    attachmentIds.value = attachmentList.value.map((item: any) => item.id);
    toast.show("上传成功");
    console.log("111", attachmentIds.value.attachmentId);
  } catch (error) {
    console.error("上传失败:", error);
    toast.show("上传失败");
  }
};
// åˆ é™¤é™„ä»¶
const deleteAttachment = async (aid: number) => {
  try {
    uni.showModal({
      title: "确认删除",
      content: "确定要删除这个附件吗?",
      success: async (res) => {
        if (res.confirm) {
          // å‰ç«¯æ‰‹åŠ¨åˆ é™¤ï¼šç›´æŽ¥ä»Žåˆ—è¡¨ä¸­ç§»é™¤è¿™æ¡æ•°æ®
          attachmentList.value = attachmentList.value.filter((item) => item.id !== aid);
          // èŽ·å–å‰©ä½™çš„é™„ä»¶ID组合
          attachmentIds.value = attachmentList.value.map((item) => item.id).join(",");
          toast.show("删除成功");
        }
      },
    });
    console.log("111", attachmentIds.value.attachmentId);
  } catch (error) {
    console.error("删除失败:", error);
    toast.show("删除失败");
  }
};
// é¢„览附件
const previewAttachment = (item: any) => {
  // æ ¹æ®æ–‡ä»¶ç±»åž‹è¿›è¡Œé¢„览
  const fileName = item.bucketFileName || item.name;
  const fileType = getFileType(fileName);
  if (fileType.startsWith("image")) {
    // å›¾ç‰‡é¢„览
    uni.previewImage({
      urls: [item.url],
      current: item.url,
    });
  } else {
    // å…¶ä»–文件类型,可以下载或打开
    uni.downloadFile({
      url: item.url,
      success: (res) => {
        uni.openDocument({
          filePath: res.tempFilePath,
          success: () => {
            console.log("打开文档成功");
          },
          fail: (error) => {
            console.error("打开文档失败:", error);
            toast.show("无法预览此文件类型");
          },
        });
      },
      fail: (error) => {
        console.error("下载文件失败:", error);
        toast.show("下载文件失败");
      },
    });
  }
};
// èŽ·å–æ–‡ä»¶ç±»åž‹
const getFileType = (fileName: string) => {
  if (!fileName) return "unknown";
  const extension = fileName.split(".").pop()?.toLowerCase();
  switch (extension) {
    case "jpg":
    case "jpeg":
    case "png":
    case "gif":
    case "bmp":
    case "webp":
      return "image";
    case "pdf":
      return "pdf";
    case "doc":
    case "docx":
      return "word";
    case "xls":
    case "xlsx":
      return "excel";
    case "ppt":
    case "pptx":
      return "powerpoint";
    case "txt":
      return "text";
    case "zip":
    case "rar":
      return "archive";
    default:
      return "file";
  }
};
// æ ¼å¼åŒ–文件大小
const formatFileSize = (size: number) => {
  if (size < 1024) return size + " B";
  if (size < 1024 * 1024) return (size / 1024).toFixed(1) + " KB";
  return (size / (1024 * 1024)).toFixed(1) + " MB";
};
// æ ¼å¼åŒ–æ—¶é—´
const formatTime = (time: string) => {
  const date = new Date(time);
  return date.toLocaleString();
};
// å¯¹å¤–暴露方法:获取所有需提交的文件
const getSubmitFiles = () => ({
  newFiles: attachmentIds.value || [],
});
defineExpose({ getSubmitFiles });
</script>
<style lang="scss" scoped>
.attachment-container {
  padding: 12px;
  background: #f3f9f8;
  min-height: 100vh;
}
.header-actions {
  margin-bottom: 12px;
  :deep(.add_btn) {
    background: #0d867f;
    color: white;
    border: none;
  }
}
.attachment-list {
  .attachment-card {
    margin-bottom: 12px;
    border-radius: 4px;
  }
}
.attachment-item {
  display: flex;
  align-items: center;
  padding: 12px;
  .attachment-info {
    flex: 1;
    .attachment-name {
      font-size: 16px;
      font-weight: 500;
      color: #333;
      margin-bottom: 4px;
      word-break: break-all;
    }
    .attachment-meta {
      display: flex;
      gap: 12px;
      font-size: 12px;
      color: #999;
    }
  }
  .attachment-actions {
    margin-left: 12px;
    :deep(.wd-icon) {
      font-size: 20px;
      cursor: pointer;
    }
  }
}
</style>