| | |
| | | <template> |
| | | <view class="material-page"> |
| | | <view class="material-header"> |
| | | <CardTitle title="物料详情" :hideAction="false"> |
| | | <template #action> |
| | | <wd-button |
| | | type="icon" |
| | | icon="scan" |
| | | color="#0d867f" |
| | | @click="openScan" |
| | | style="color: #0d867f" |
| | | ></wd-button> |
| | | </template> |
| | | </CardTitle> |
| | | <CardTitle title="物料详情" :hideAction="false" /> |
| | | |
| | | <!-- 物料信息 --> |
| | | <!-- 物料信息列表,点击进入对应物料的出库页面 --> |
| | | <view class="material-info"> |
| | | <wd-card |
| | | v-for="(item, index) in materialList" |
| | | :key="item.materialcode || index" |
| | | custom-class="info-card" |
| | | @click="toMaterialOutbound(item)" |
| | | > |
| | | <wd-row class="info-row"> |
| | | <wd-col :span="24"> |
| | |
| | | <wd-icon name="folder" color="#0D867F"></wd-icon> |
| | | </view> |
| | | <text class="text-[#646874] mx-2"> |
| | | 已发货数量: |
| | | 待发货数量: |
| | | <text class="text-[#252525]">{{ item.shippedQuantity || 0 }}</text> |
| | | </text> |
| | | </view> |
| | |
| | | </wd-card> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 货物列表 --> |
| | | <view class="list_content"> |
| | | <view v-if="goodsList.length === 0" class="empty_tip"> |
| | | <view class="empty_text">暂无货物数据</view> |
| | | </view> |
| | | <view v-for="(item, index) in goodsList" :key="index" class="outbound_item"> |
| | | <view class="outbound_item_left"> |
| | | <view class="outbound_item_content"> |
| | | <view class="outbound_item_row"> |
| | | <text class="outbound_item_label">合同号:</text> |
| | | <text class="outbound_item_value">{{ item.contractNo || "-" }}</text> |
| | | </view> |
| | | <view class="outbound_item_row"> |
| | | <text class="outbound_item_label">生产批次号:</text> |
| | | <text class="outbound_item_value">{{ item.monofilamentNumber || "-" }}</text> |
| | | </view> |
| | | <view class="outbound_item_row"> |
| | | <text class="outbound_item_label">规格型号:</text> |
| | | <text class="outbound_item_value">{{ item.model || "-" }}</text> |
| | | </view> |
| | | <view class="outbound_item_row"> |
| | | <text class="outbound_item_label">重量:</text> |
| | | <text class="outbound_item_value">{{ item.weight || "-" }} kg</text> |
| | | </view> |
| | | <view class="outbound_item_row"> |
| | | <text class="outbound_item_label">厂家:</text> |
| | | <text class="outbound_item_value">{{ item.clienteleName || "-" }}</text> |
| | | </view> |
| | | <view class="outbound_item_row"> |
| | | <text class="outbound_item_label">段长:</text> |
| | | <text class="outbound_item_value">{{ item.actuallyLength || "-" }} M</text> |
| | | </view> |
| | | <view class="outbound_item_row"> |
| | | <text class="outbound_item_label">生产日期:</text> |
| | | <text class="outbound_item_value">{{ item.productionDate || "-" }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="outbound_item_action"> |
| | | <wd-button |
| | | type="icon" |
| | | icon="delete" |
| | | size="small" |
| | | custom-class="delete-btn" |
| | | @click.stop="removeGoodsItem(index)" |
| | | ></wd-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 底部按钮 --> |
| | | <view v-if="goodsList.length > 1" class="outbound_footer"> |
| | | <wd-button block @click="handleOutbound" style="background: #0d867f"> |
| | | <text class="text-[#fff]">出库</text> |
| | | </wd-button> |
| | | </view> |
| | | |
| | | <Scan ref="scanRef" emitName="scanMaterial" /> |
| | | <wd-toast /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, onMounted, onUnmounted } from "vue"; |
| | | import { ref } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | // @ts-ignore |
| | | import CardTitle from "@/components/card-title/index.vue"; |
| | | // @ts-ignore |
| | | import Scan from "@/components/scan/index.vue"; |
| | | import { useToast, dayjs } from "wot-design-uni"; |
| | | import { useToast } from "wot-design-uni"; |
| | | import OutboundApi from "@/api/product/outbound"; |
| | | |
| | | const toast = useToast(); |
| | | const scanRef = ref(); |
| | | const materialId = ref<string>(""); |
| | | const vbillcode = ref<string>(""); |
| | | |
| | | // 物料信息列表 |
| | | const materialList = ref<any[]>([]); |
| | | |
| | | // 货物列表 |
| | | const goodsList = ref<any[]>([]); |
| | | // 安全解码(兼容后端已编码或未编码的情况) |
| | | const safeDecode = (val: any) => { |
| | | if (typeof val !== "string") return val || ""; |
| | | try { |
| | | return val.includes("%") ? decodeURIComponent(val) : val; |
| | | } catch (e) { |
| | | return val; |
| | | } |
| | | }; |
| | | |
| | | // 格式化时间 |
| | | const formatTime = (date: Date) => { |
| | | return dayjs(date).format("YYYY-MM-DD HH:mm:ss"); |
| | | // 点击某个物料,跳转到单独的出库页面 |
| | | const toMaterialOutbound = (item: any) => { |
| | | const query = [ |
| | | `materialname=${encodeURIComponent(item.materialname || "")}`, |
| | | `materialspec=${encodeURIComponent(item.materialspec || "")}`, |
| | | `shippedQuantity=${item.shippedQuantity || 0}`, |
| | | `vsrccode=${item.vsrccode || ""}`, |
| | | `cdeliveryid=${encodeURIComponent(item.cdeliveryid || "")}`, |
| | | `cdeliverybid=${encodeURIComponent(item.cdeliverybid || "")}`, |
| | | `materialcode=${encodeURIComponent(item.materialcode || "")}`, |
| | | ].join("&"); |
| | | |
| | | uni.navigateTo({ |
| | | url: `/pages/outbound/materialOutbound?${query}`, |
| | | }); |
| | | }; |
| | | |
| | | // 获取物料详情 |
| | |
| | | uni.hideLoading(); |
| | | |
| | | if (code === 200 && data) { |
| | | // 将接口返回的数据映射到 materialList |
| | | // 将接口返回的数据映射到 materialList,并对可能被 URL 编码的字段做一次安全解码 |
| | | materialList.value = data.map((item: any) => ({ |
| | | materialcode: item.materialcode, |
| | | materialname: item.materialname || "-", |
| | | materialspec: item.materialspec || "-", |
| | | materialcode: safeDecode(item.materialcode), |
| | | materialname: safeDecode(item.materialname || "-"), |
| | | materialspec: safeDecode(item.materialspec || "-"), |
| | | shippedQuantity: item.nnum || 0, |
| | | cdeliveryid: item.cdeliveryid, |
| | | cdeliverybid: item.cdeliverybid, |
| | | vsrccode: item.vsrccode, |
| | | cdeliveryid: safeDecode(item.cdeliveryid), |
| | | cdeliverybid: safeDecode(item.cdeliverybid), |
| | | vsrccode: safeDecode(item.vsrccode), |
| | | })); |
| | | } else { |
| | | toast.error(msg || "获取物料详情失败"); |
| | |
| | | } |
| | | }; |
| | | |
| | | // 直接扫码 |
| | | const openScan = () => { |
| | | scanRef.value?.triggerScan(); |
| | | }; |
| | | |
| | | // 扫码回调 |
| | | const getScanCode = async (code: any) => { |
| | | try { |
| | | // 如果 code 是对象且有 code 字段,使用 code.code;否则直接使用 code |
| | | let scanCode = code.code || code; |
| | | |
| | | // 如果 scanCode 是对象,尝试获取其 code 字段 |
| | | if (typeof scanCode === "object" && scanCode.code) { |
| | | scanCode = scanCode.code; |
| | | } |
| | | |
| | | // 如果 scanCode 是字符串,直接使用;如果是对象,转为字符串 |
| | | if (typeof scanCode !== "string") { |
| | | scanCode = JSON.stringify(scanCode); |
| | | } |
| | | |
| | | if (!scanCode) { |
| | | toast.error("扫码内容为空"); |
| | | return; |
| | | } |
| | | |
| | | // 解析扫码数据,现在二维码只包含id |
| | | let scanData; |
| | | try { |
| | | scanData = JSON.parse(scanCode); |
| | | } catch (e) { |
| | | toast.error("二维码格式错误"); |
| | | return; |
| | | } |
| | | |
| | | const outPutId = scanData.id; |
| | | |
| | | if (!outPutId) { |
| | | toast.error("二维码格式错误,缺少id信息"); |
| | | return; |
| | | } |
| | | |
| | | // 检查是否已存在(根据id判断) |
| | | const exists = goodsList.value.some((item) => { |
| | | const itemId = item.id; |
| | | return itemId && itemId === outPutId && itemId !== "-"; |
| | | }); |
| | | |
| | | if (exists) { |
| | | toast.error("该条码已存在,请勿重复扫码"); |
| | | return; |
| | | } |
| | | |
| | | // 调用接口获取绞线/拉丝详细信息(含出库状态) |
| | | const { data } = await OutboundApi.getTagByIdAll({ |
| | | outPutId: outPutId, |
| | | }); |
| | | |
| | | const list = data || []; |
| | | if (!list.length) { |
| | | toast.error("未查询到条码信息"); |
| | | return; |
| | | } |
| | | |
| | | const tagData = list[0]; |
| | | |
| | | // 已出库校验 |
| | | if (tagData?.state === "已出库") { |
| | | toast.error("该条码已出库,无法重复出库"); |
| | | return; |
| | | } |
| | | |
| | | // 提取数据字段(根据接口返回的数据结构) |
| | | const parsedData = { |
| | | id: tagData?.id || outPutId, |
| | | contractNo: tagData?.contractno || tagData?.contractNo || "-", |
| | | monofilamentNumber: |
| | | tagData?.monofilamentnumber || |
| | | tagData?.monofilamentNumber || |
| | | tagData?.systemno || |
| | | tagData?.systemNo || |
| | | "-", |
| | | model: tagData?.model || "-", |
| | | weight: tagData?.actuallylength || tagData?.actuallyLength || tagData?.weight || "-", |
| | | clienteleName: tagData?.clientelename || tagData?.clienteleName || "-", |
| | | actuallyLength: tagData?.actuallylength || tagData?.actuallyLength || "-", |
| | | productionDate: tagData?.producttime || tagData?.productionDate || "-", |
| | | type: tagData?.type || "", |
| | | devicemodel: tagData?.devicemodel || "", |
| | | state: tagData?.state || "", |
| | | projectId: tagData?.projectid || tagData?.projectId || "", |
| | | productuser: tagData?.productuser || "", |
| | | // 保留原始数据 |
| | | rawData: tagData, |
| | | scanCode: scanCode, |
| | | }; |
| | | |
| | | // 添加到列表 |
| | | const newItem = { |
| | | ...parsedData, |
| | | scanTime: formatTime(new Date()), |
| | | }; |
| | | |
| | | goodsList.value.push(newItem); |
| | | toast.success("扫码成功"); |
| | | } catch (error: any) { |
| | | console.error("扫码处理失败:", error); |
| | | toast.error(error.msg || "二维码异常,请更换二维码!"); |
| | | } |
| | | }; |
| | | |
| | | // 删除货物项 |
| | | const removeGoodsItem = (index: number) => { |
| | | const item = goodsList.value[index]; |
| | | const itemName = item.contractNo || item.monofilamentNumber || `第${index + 1}项`; |
| | | |
| | | uni.showModal({ |
| | | title: "确认删除", |
| | | content: `确定要删除"${itemName}"吗?`, |
| | | confirmText: "删除", |
| | | cancelText: "取消", |
| | | confirmColor: "#ff4444", |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | goodsList.value.splice(index, 1); |
| | | toast.success("删除成功"); |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | // 统计物料数量和类型规格 |
| | | const getMaterialStatistics = () => { |
| | | const statistics: Record<string, { type: string; model: string; count: number }> = {}; |
| | | |
| | | goodsList.value.forEach((item) => { |
| | | const type = item.type || "未知类型"; |
| | | const model = item.model || "未知规格"; |
| | | const key = `${type}_${model}`; |
| | | |
| | | if (!statistics[key]) { |
| | | statistics[key] = { |
| | | type, |
| | | model, |
| | | count: 0, |
| | | }; |
| | | } |
| | | statistics[key].count++; |
| | | }); |
| | | |
| | | return Object.values(statistics); |
| | | }; |
| | | |
| | | // 处理出库 |
| | | const handleOutbound = async () => { |
| | | if (goodsList.value.length === 0) { |
| | | toast.error("暂无货物数据"); |
| | | return; |
| | | } |
| | | |
| | | // 统计物料数量和类型规格 |
| | | const statistics = getMaterialStatistics(); |
| | | |
| | | // 构建统计信息文本 |
| | | let statisticsText = "出库物料统计:\n\n"; |
| | | statistics.forEach((item, index) => { |
| | | statisticsText += `${index + 1}. ${item.type} - ${item.model}:${item.count}件\n`; |
| | | }); |
| | | statisticsText += `\n总计:${goodsList.value.length}件\n\n确认出库吗?`; |
| | | |
| | | // 显示确认弹框 |
| | | uni.showModal({ |
| | | title: "确认出库", |
| | | content: statisticsText, |
| | | confirmText: "确认", |
| | | cancelText: "取消", |
| | | confirmColor: "#0d867f", |
| | | success: async (res) => { |
| | | if (res.confirm) { |
| | | // 构建请求数据 |
| | | const requestData = goodsList.value.map((item) => ({ |
| | | outPutId: item.id, |
| | | projectId: item.projectId || "", |
| | | })); |
| | | |
| | | try { |
| | | uni.showLoading({ |
| | | title: "出库中...", |
| | | mask: true, |
| | | }); |
| | | |
| | | console.log("requestData", requestData); |
| | | const { code, msg } = await OutboundApi.finishedOutbound(requestData); |
| | | |
| | | uni.hideLoading(); |
| | | |
| | | if (code === 200) { |
| | | toast.success("出库成功"); |
| | | // 清空列表 |
| | | goodsList.value = []; |
| | | // 返回上一页或刷新物料详情 |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 1500); |
| | | } else { |
| | | toast.error(msg || "出库失败"); |
| | | } |
| | | } catch (error: any) { |
| | | uni.hideLoading(); |
| | | console.error("出库失败:", error); |
| | | toast.error(error.msg || "出库失败"); |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | // 设置扫码监听 |
| | | const setupScanListener = () => { |
| | | uni.$off("scanMaterial", getScanCode); |
| | | uni.$on("scanMaterial", getScanCode); |
| | | }; |
| | | |
| | | onLoad((options: any) => { |
| | | materialId.value = options.id || ""; |
| | | vbillcode.value = options.vbillcode || ""; |
| | | getMaterialDetail(); |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | setupScanListener(); |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | uni.$off("scanMaterial", getScanCode); |
| | | }); |
| | | </script> |
| | | |