spring
2026-10-08 e0fc86fda41a8777a0cec5a9de7aa9f1474f5dc8
src/pages/production/twist/receive/plate/index.vue
@@ -1,36 +1,61 @@
<template>
  <view class="list">
  <view class="list_box">
    <z-paging
      ref="pagingRef"
      v-model="cardList"
      v-model="plateData"
      :fixed="false"
      :auto-show-back-to-top="true"
      :loading-more-enabled="false"
      @query="getList"
    >
      <template #top>
        <CardTitle title="盘具领用" :hideAction="true" :full="false" @action="addReport" />
      </template>
      <wd-card v-for="(item, index) in cardList" :key="index" type="rectangle" custom-class="round">
        <template #title>
          <view class="flex justify-between">
            <view>
              <wd-icon name="a-rootlist" color="#0D867F"></wd-icon>
              <text class="text-[#252525] ml-2 font-medium">{{ item.diskMaterial }}</text>
        <CardTitle title="盘具领用" :hideAction="false">
          <template #action>
            <view class="action-wrap">
              <!-- 工单要求的盘具规格型号 / 材质:展示在扫码(新增)按钮旁 -->
              <view class="reel-defaults">
                <text class="reel-default-item">规格型号:{{ defaultDishModel || "-" }}</text>
                <text class="reel-default-item">材质:{{ defaultDishMaterial || "-" }}</text>
              </view>
              <wd-button type="icon" icon="scan" color="#0D867F" @click="openScan"></wd-button>
            </view>
            <view class="text-[#A8A8A8]" @click="toEdit(item.id)">编辑</view>
          </view>
        </template>
        <ProductionCard :data="cardAttr" :value="item" color="#0D867F" />
      </wd-card>
    </z-paging>
    <wd-popup v-model="addDialog.visible" position="bottom" custom-class="yl-popup">
      <view class="action px-3">
        <wd-button type="text" @click="cancelAdd">取消</wd-button>
        <wd-button type="text" @click="submitAdd">确定</wd-button>
          </template>
        </CardTitle>
        <view class="scan-tip">请扫描盘具标签二维码领用盘具(盘具需先在盘具称重台账中称重)</view>
      </template>
      <view class="content">
        <wd-card
          v-for="(item, index) in plateData"
          :key="item.id ?? item.dishCode ?? index"
          type="rectangle"
          custom-class="round"
        >
          <template #title>
            <view class="flex justify-between">
              <view>
                <wd-icon name="a-rootlist" color="#0D867F"></wd-icon>
                <text class="text-[#252525] ml-2 font-medium">{{ item.diskMaterial }}</text>
              </view>
              <view class="flex items-center">
                <text v-if="!item.id" class="text-[#E6A23C] mr-3 text-[12px]">待保存</text>
                <view v-if="!item.id" class="text-[#A8A8A8]" @click="removeDish(item)">移除</view>
                <view v-else class="text-[#A8A8A8]" @click="toEdit(item)">编辑</view>
              </view>
            </view>
          </template>
          <ProductionCard :data="cardAttr" :value="item" color="#0D867F" />
        </wd-card>
      </view>
      <PlateForm ref="addFormRef" mode="add" @refresh="reloadList" />
    </wd-popup>
    <wd-popup v-model="editDialog.visible" position="bottom" custom-class="yl-popup">
    </z-paging>
    <view class="save-footer">
      <wd-button block @click="save">
        <text class="text-[#fff]">保存</text>
      </wd-button>
    </view>
    <wd-popup v-model="editDialog.visible" position="bottom" custom-class="yl-popup" :z-index="30">
      <view class="action px-3">
        <wd-button type="text" @click="cancelEdit">取消</wd-button>
        <wd-button type="text" @click="submitEdit">确定</wd-button>
@@ -42,6 +67,8 @@
        @refresh="reloadList"
      />
    </wd-popup>
    <Scan ref="scanRef" emitName="scanPlate" />
    <wd-toast />
  </view>
</template>
@@ -51,28 +78,37 @@
import ProductionCard from "../../../components/ProductionCard.vue";
import { useToast } from "wot-design-uni";
import PlateForm from "./form.vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { onLoad, onUnload, onShow, onHide } from "@dcloudio/uni-app";
import ManageApi from "@/api/product/manage";
import TwistApi from "@/api/product/twist";
import zPaging from "@/components/z-paging/z-paging.vue";
import Scan from "@/components/scan/index.vue";
const paramsId = ref();
const pagingRef = ref();
const addFormRef = ref();
const editFormRef = ref();
const scanRef = ref();
const toast = useToast();
const addDialog = reactive({
  visible: false,
});
const editDialog = reactive({
  visible: false,
  currentItem: null as any,
  editId: undefined as number | undefined,
});
const cardList = ref<any[]>([]);
const plateData = ref<any[]>([]);
const isPageVisible = ref(true);
/** 工单要求的盘具规格型号 / 材质(展示在新增按钮旁,领用后作为规格型号默认值) */
const defaultDishModel = ref("");
const defaultDishMaterial = ref("");
const cardAttr = ref<any[]>([
  {
    label: "尺寸",
    label: "盘具编号",
    prop: "dishCode",
  },
  {
    label: "规格型号",
    prop: "model",
  },
  {
@@ -88,48 +124,39 @@
    label: "单位",
    prop: "unit",
  },
  {
    label: "厂家",
    prop: "supplier",
    span: 14,
  },
]);
const toEdit = (id: number) => {
  // 找到需要编辑的项
  const itemToEdit = cardList.value.find((item) => item.id === id);
  if (itemToEdit) {
    editDialog.currentItem = itemToEdit;
    editDialog.editId = id;
    editDialog.visible = true;
  }
/** 统一盘具数据:盘具编号兼容 weighingCode,数量/单位补默认值 */
const normalizeDish = (item: any) => ({
  ...item,
  dishCode: item?.dishCode ?? item?.weighingCode ?? "",
  weighingCode: item?.weighingCode ?? item?.dishCode ?? "",
  amount: item?.amount ?? 1,
  unit: item?.unit ?? "只",
});
/** 是否已被领用:兼容 1/"1"/true/"是" */
const isReceived = (row: any) => {
  const value = row?.received ?? row?.isReceived;
  return [true, 1, "1", "true", "是"].includes(value);
};
const addReport = () => {
  // 重置表单数据
  if (addFormRef.value) {
    addFormRef.value.resetFormData();
  }
  addDialog.visible = true;
const toEdit = (item: any) => {
  editDialog.currentItem = item;
  editDialog.editId = item?.id;
  editDialog.visible = true;
};
const submitAdd = async () => {
  toast.show("提交");
  const success = await addFormRef.value.submit();
  if (success) {
    addDialog.visible = false;
/** 移除尚未保存的扫码记录 */
const removeDish = (item: any) => {
  const index = plateData.value.findIndex((row) => row.dishCode === item.dishCode && !row.id);
  if (index > -1) {
    plateData.value.splice(index, 1);
  }
};
const cancelAdd = () => {
  toast.show("取消");
  addDialog.visible = false;
  // 清空表单数据
  addFormRef.value?.$refs?.form?.resetFields();
};
const submitEdit = async () => {
  const success = await editFormRef.value.submitEdit(cardList.value, editDialog.editId);
  const success = await editFormRef.value.submitEdit(plateData.value, editDialog.editId);
  if (success) {
    editDialog.visible = false;
    reloadList();
@@ -137,33 +164,268 @@
};
const cancelEdit = () => {
  toast.show("取消");
  editDialog.visible = false;
};
const reloadList = () => {
  pagingRef.value.refresh();
  pagingRef.value?.refresh();
};
const getList = async () => {
  const { code, data } = await ManageApi.getStrandedWireDish({
    wireId: paramsId.value,
    type: "盘具",
  });
  if (code == 200) {
    pagingRef.value.complete(data);
  try {
    const { code, data } = await ManageApi.getStrandedWireDish({
      wireId: paramsId.value,
      type: "盘具",
    });
    if (code == 200) {
      const list = (Array.isArray(data) ? data : []).map(normalizeDish);
      pagingRef.value.complete(list);
    } else {
      pagingRef.value.complete([]);
    }
  } catch (error) {
    pagingRef.value.complete([]);
  }
};
// ============ 扫码新增盘具 ============
/** 解析扫码内容:兼容 JSON 与纯文本称重编号 */
const parseScanCode = (payload: any) => {
  const raw = payload?.code ?? payload;
  if (raw === undefined || raw === null) return null;
  if (typeof raw === "object") return raw;
  const text = String(raw).trim();
  if (!text) return null;
  if (text.startsWith("{")) {
    try {
      return JSON.parse(text);
    } catch {
      // JSON 解析失败时按纯文本称重编号处理
    }
  }
  return { weighingCode: text };
};
/** 从扫码内容里取盘具(称重)编号 */
const pickWeighingCode = (data: any) => {
  const keys = [
    "weighingCode",
    "weighingNo",
    "dishCode",
    "receiveDishNo",
    "batchCode",
    "code",
    "称重编号",
    "盘具编号",
    "盘号",
  ];
  for (const key of keys) {
    const value = data?.[key];
    if (value !== undefined && value !== null && String(value).trim() !== "") {
      return String(value).trim();
    }
  }
  return "";
};
/** 台账行 → 盘具领用行 */
const buildDishFromLedger = (record: any) =>
  normalizeDish({
    wireId: paramsId.value,
    diskMaterial: record?.diskMaterial ?? undefined,
    // 规格型号取该盘具在称重台账里的规格型号
    model: record?.specificationModel ?? undefined,
    weight: record?.weight ?? undefined,
    amount: 1,
    unit: "只",
    dishCode: String(record?.weighingCode ?? "").trim(),
    weighingCode: String(record?.weighingCode ?? "").trim(),
    type: "盘具",
  });
const getScanCode = async (payload: any) => {
  if (!isPageVisible.value) return;
  const parsed = parseScanCode(payload);
  if (!parsed) {
    toast.error("扫码内容为空,请重新扫码");
    return;
  }
  const code = pickWeighingCode(parsed);
  if (!code) {
    toast.error("二维码无法识别盘具编号,请更换二维码");
    return;
  }
  if (plateData.value.some((item) => String(item.dishCode ?? item.weighingCode ?? "") === code)) {
    toast.error("该盘具已在本单领用列表中,请勿重复扫码");
    return;
  }
  try {
    const { data } = await TwistApi.queryBobbinWeighingPage({
      current: 1,
      size: 50,
      weighingCode: code,
    });
    const pageData: any = data ?? {};
    const records =
      pageData?.records ??
      pageData?.list ??
      pageData?.rows ??
      (Array.isArray(pageData) ? pageData : []);
    const exact = (records || []).find((row: any) => String(row?.weighingCode ?? "") === code);
    const record = exact ?? (records?.length === 1 ? records[0] : null);
    if (!record) {
      toast.error("盘具称重台账中未找到该盘具,请先在盘具称重台账中称重");
      return;
    }
    if (isReceived(record)) {
      toast.error("该盘具已被领用,请更换盘具");
      return;
    }
    plateData.value.push(buildDishFromLedger(record));
    toast.success("扫码成功,请点击保存提交");
  } catch (error) {
    // 请求层已统一提示失败原因
  }
};
const openScan = () => {
  scanRef.value?.triggerScan();
};
// ============ 保存 ============
const isNewDish = (item: any) => item?.id === undefined || item?.id === null;
const save = () => {
  const newCount = plateData.value.filter(isNewDish).length;
  if (!newCount) {
    toast.error("没有新扫码的盘具需要保存");
    return;
  }
  uni.showModal({
    title: "提示",
    content: `确认保存 ${newCount} 条盘具领用数据吗?`,
    success: async (res) => {
      if (res.confirm) {
        await handleConfirmSave();
      }
    },
  });
};
const handleConfirmSave = async () => {
  const newData = plateData.value.filter(isNewDish);
  if (newData.length === 0) {
    toast.error("没有新扫码的盘具需要保存");
    return;
  }
  const invalid = newData.find((item) => !item.dishCode || !item.diskMaterial);
  if (invalid) {
    toast.error(`${invalid.dishCode || "盘具"}:盘具数据不完整,请重新扫码`);
    return;
  }
  const { code, msg } = await TwistApi.addStrandedWireDish(newData);
  if (code == 200) {
    toast.success(msg || "保存成功");
    pagingRef.value?.refresh();
  } else {
    toast.error(msg || "保存失败");
  }
};
/**
 * 取工单要求的盘具型号 / 盘具材质(展示在扫码按钮旁)
 * 只取盘具相关字段 diskModel / diskMaterial,不拿产品规格 model 兜底
 */
const loadDishDefaults = async () => {
  if (!paramsId.value) return;
  try {
    const { data } = await TwistApi.getTwistDetailById({ id: paramsId.value });
    defaultDishModel.value = data?.diskModel || "";
    defaultDishMaterial.value = data?.diskMaterial || "";
  } catch (error) {
    // 取不到就展示 "-",不阻塞领用
  }
};
onLoad((options: any) => {
  paramsId.value = options.id;
  uni.$off("scanPlate", getScanCode);
  uni.$on("scanPlate", getScanCode);
  paramsId.value = options?.id;
  loadDishDefaults();
});
// 盘具领用是「芯线/单丝/盘具」页签之一,组件可能晚于页面 onShow 才挂载,
// 这里兜底置为可见,避免扫码被误判为不可见而静默丢弃。
onMounted(() => {
  isPageVisible.value = true;
});
onShow(() => {
  isPageVisible.value = true;
});
onHide(() => {
  isPageVisible.value = false;
});
onUnload(() => {
  uni.$off("scanPlate", getScanCode);
  isPageVisible.value = false;
});
</script>
<style lang="scss" scoped>
.list {
  height: calc(100vh - 120px);
  padding: 12px;
.list_box {
  height: calc(100vh - 160px);
  background: #f3f9f8;
  display: flex;
  flex-direction: column;
}
.action {
  display: flex;
  justify-content: space-between;
}
:deep(.wd-button__content) {
  color: #0d867f;
}
.scan-tip {
  padding: 0 12px 8px;
  font-size: 12px;
  color: #8a8a8a;
}
.action-wrap {
  display: flex;
  align-items: center;
}
.reel-defaults {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  margin-right: 8px;
}
.reel-default-item {
  font-size: 11px;
  line-height: 16px;
  color: #646874;
}
.content {
  width: 100%;
  padding-bottom: 76px;
  box-sizing: border-box;
  :deep() {
    .round {
@@ -172,8 +434,33 @@
  }
}
.action {
.save-footer {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 20;
  padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
  background: #ffffff;
  border-top: 1px solid #eeeeee;
  box-shadow: 0 -2px 10px rgb(0 0 0 / 6%);
}
:deep(.zp-paging-container) {
  background: transparent !important;
  flex: 1;
  display: flex;
  justify-content: space-between;
  flex-direction: column;
}
:deep(.zp-paging-container-content) {
  background: transparent !important;
  flex: 1;
  display: flex;
  flex-direction: column;
}
:deep(.zp-paging-container-top) {
  flex-shrink: 0;
}
</style>