spring
3 天以前 6b6c15e63bb973adff45afe13c796cc364690206
fix: 出库功能调整
已添加1个文件
已修改3个文件
904 ■■■■■ 文件已修改
src/api/product/outbound.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/outbound/material.vue 343 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/outbound/materialOutbound.vue 541 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/product/outbound.ts
@@ -11,6 +11,20 @@
    });
  },
  // å®Œæˆå‡ºåº“(按出库单明细)
  finishedOutboundByOutboundId(data: {
    finishedOutDtos: Array<{ outPutId: string | number; projectId: string | number }>;
    cdeliveryid: string | number;
    cdeliverybid: string | number;
    materialcode: string;
  }) {
    return request<BaseResult<any>>({
      url: "/app/finishedOutboundByOutboundId",
      method: "POST",
      data,
    });
  },
  // å‡ºåº“单查询
  queryErpOutboundOrder(params: {
    contractNo?: string;
src/pages.json
@@ -290,6 +290,12 @@
      }
    },
    {
      "path": "pages/outbound/materialOutbound",
      "style": {
        "navigationBarTitleText": "物料出库"
      }
    },
    {
      "path": "pages/routingInspection/index",
      "style": {
        "navigationBarTitleText": "巡检"
src/pages/outbound/material.vue
@@ -1,24 +1,15 @@
<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">
@@ -53,7 +44,7 @@
                  <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>
@@ -62,94 +53,39 @@
        </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 > 0" 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 vsrccode = ref<string>("");
// ç‰©æ–™ä¿¡æ¯åˆ—表
const materialList = ref<any[]>([]);
// è´§ç‰©åˆ—表
const goodsList = ref<any[]>([]);
// ç‚¹å‡»æŸä¸ªç‰©æ–™ï¼Œè·³è½¬åˆ°å•独的出库页面
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("&");
// æ ¼å¼åŒ–æ—¶é—´
const formatTime = (date: Date) => {
  return dayjs(date).format("YYYY-MM-DD HH:mm:ss");
  uni.navigateTo({
    url: `/pages/outbound/materialOutbound?${query}`,
  });
};
// èŽ·å–ç‰©æ–™è¯¦æƒ…
@@ -194,249 +130,10 @@
  }
};
// ç›´æŽ¥æ‰«ç 
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;
    }
    // åˆåŒå·æ ¡éªŒï¼šæ£€æŸ¥æ‰«ç çš„合同号是否等于发货单的vsrccode
    const scannedContractNo = tagData?.contractno || "";
    if (scannedContractNo && vsrccode.value && scannedContractNo !== vsrccode.value) {
      toast.error(`合同号"${scannedContractNo}"与当前发货单不匹配`);
      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 || "";
  vsrccode.value = options.vsrccode || "";
  getMaterialDetail();
});
onMounted(() => {
  setupScanListener();
});
onUnmounted(() => {
  uni.$off("scanMaterial", getScanCode);
});
</script>
src/pages/outbound/materialOutbound.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,541 @@
<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>
      <!-- å½“前物料信息 -->
      <view class="material-info" v-if="currentMaterial">
        <wd-card custom-class="info-card">
          <view class="info-compact">
            <view class="icon_box">
              <wd-icon name="folder" color="#0D867F"></wd-icon>
            </view>
            <view class="info-text">
              <view class="info-line">
                <text class="label">物料名称:</text>
                <text class="value">{{ currentMaterial.materialname || "-" }}</text>
              </view>
              <view class="info-line">
                <text class="label">物料规格:</text>
                <text class="value">{{ currentMaterial.materialspec || "-" }}</text>
              </view>
              <view class="info-line">
                <text class="label">待货数量:</text>
                <text class="value">{{ currentMaterial.shippedQuantity || 0 }} å¨</text>
              </view>
            </view>
          </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 > 0" 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 { 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 OutboundApi from "@/api/product/outbound";
const toast = useToast();
const scanRef = ref();
const vsrccode = ref<string>("");
// å½“前物料信息
const currentMaterial = ref<any | null>(null);
// è´§ç‰©åˆ—表
const goodsList = ref<any[]>([]);
// æ ¼å¼åŒ–æ—¶é—´
const formatTime = (date: Date) => {
  return dayjs(date).format("YYYY-MM-DD HH:mm:ss");
};
// ç›´æŽ¥æ‰«ç 
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;
    }
    // åˆåŒå·æ ¡éªŒï¼šæ£€æŸ¥æ‰«ç çš„合同号是否等于发货单的vsrccode
    const scannedContractNo = tagData?.contractno || "";
    if (scannedContractNo && vsrccode.value && scannedContractNo !== vsrccode.value) {
      toast.error(`合同号"${scannedContractNo}"与当前发货单为"${vsrccode.value}"不匹配`);
      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 || "",
        }));
        // æŒ‰å‡ºåº“单明细构建请求体
        const payload = {
          finishedOutDtos: requestData,
          cdeliveryid: currentMaterial.value?.cdeliveryid || "",
          cdeliverybid: currentMaterial.value?.cdeliverybid || "",
          materialcode: currentMaterial.value?.materialcode || "",
        };
        try {
          uni.showLoading({
            title: "出库中...",
            mask: true,
          });
          console.log("request payload", payload);
          const { code, msg } = await OutboundApi.finishedOutboundByOutboundId(payload);
          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) => {
  // ä»Žè·¯ç”±å‚数中获取物料基本信息、出库单信息和 vsrccode
  currentMaterial.value = {
    materialname: options.materialname || "-",
    materialspec: options.materialspec || "-",
    shippedQuantity: Number(options.shippedQuantity || 0),
    cdeliveryid: options.cdeliveryid || "",
    cdeliverybid: options.cdeliverybid || "",
    materialcode: options.materialcode || "",
  };
  vsrccode.value = options.vsrccode || "";
});
onMounted(() => {
  setupScanListener();
});
onUnmounted(() => {
  uni.$off("scanMaterial", getScanCode);
});
</script>
<style lang="scss" scoped>
.material-page {
  min-height: 100vh;
  background: #f3f9f8;
  padding-bottom: 200rpx;
  display: flex;
  flex-direction: column;
}
.material-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: #f3f9f8;
  /* è®©å¤´éƒ¨æ•´ä½“更紧凑 */
  padding-bottom: 8rpx;
}
.material-info {
  /* å‡å°‘上下留白,让卡片更贴近导航栏 */
  padding: 4rpx 16rpx 0 16rpx;
}
.info-card {
  border-radius: 16rpx;
  background: linear-gradient(135deg, #ffffff, #f2fbfa);
  /* å‡å¼±é˜´å½±ï¼Œæ•´ä½“更扁平紧凑 */
  box-shadow: 0 4rpx 10rpx rgba(3, 112, 102, 0.05);
  overflow: hidden;
}
.info-row {
  /* æ”¶ç´§æ¯è¡Œé«˜åº¦ */
  padding: 6rpx 0;
}
.info-compact {
  display: flex;
  align-items: center;
  padding: 8rpx 12rpx;
}
.info-text {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  row-gap: 4rpx;
}
.info-line {
  display: flex;
  font-size: 24rpx;
  margin-right: 24rpx;
}
.info-line .label {
  color: #646874;
}
.info-line .value {
  color: #252525;
  max-width: 260rpx;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.icon_box {
  /* ç¼©å°å›¾æ ‡å®¹å™¨å°ºå¯¸ */
  width: 40rpx;
  height: 40rpx;
  border-radius: 10rpx;
  background: rgba(13, 134, 127, 0.08);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 8rpx;
}
.list_content {
  flex: 1;
  overflow-y: auto;
  padding: 24rpx;
  padding-bottom: 220rpx;
}
.empty_tip {
  margin-top: 120rpx;
  display: flex;
  justify-content: center;
}
.empty_text {
  color: #999;
  font-size: 28rpx;
}
.outbound_item {
  display: flex;
  justify-content: space-between;
  padding: 20rpx 24rpx;
  background: #fff;
  border-radius: 16rpx;
  margin-bottom: 20rpx;
  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
}
.outbound_item_left {
  flex: 1;
}
.outbound_item_content {
  display: flex;
  flex-direction: column;
  gap: 6rpx;
}
.outbound_item_row {
  display: flex;
  font-size: 26rpx;
  line-height: 1.6;
}
.outbound_item_label {
  color: #646874;
  min-width: 160rpx;
}
.outbound_item_value {
  color: #252525;
}
.outbound_item_action {
  margin-left: 16rpx;
  display: flex;
  align-items: flex-start;
}
.delete-btn {
  --wd-button-default-border-color: #f5f5f5;
}
.outbound_footer {
  position: fixed;
  bottom: 20rpx;
  left: 0;
  right: 0;
  padding: 0 30rpx;
  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  z-index: 100;
  background: transparent;
}
</style>