| | |
| | | <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> |
| | |
| | | @refresh="reloadList" |
| | | /> |
| | | </wd-popup> |
| | | |
| | | <Scan ref="scanRef" emitName="scanPlate" /> |
| | | <wd-toast /> |
| | | </view> |
| | | </template> |
| | |
| | | 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", |
| | | }, |
| | | { |
| | |
| | | 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(); |
| | |
| | | }; |
| | | |
| | | 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 { |
| | |
| | | } |
| | | } |
| | | |
| | | .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> |