spring
8 小时以前 a87c52ba861983052139295de5f78e00ae174051
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,93 +53,49 @@
        </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}`,
  });
};
// 获取物料详情
@@ -171,15 +118,15 @@
    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 || "获取物料详情失败");
@@ -193,251 +140,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;
    }
    // 规格型号校验:检查扫码的规格型号是否在物料列表中
    const scannedModel = tagData?.model || "";
    const modelExists = materialList.value.some(
      (material) => material.materialspec === scannedModel
    );
    if (!modelExists && scannedModel) {
      toast.error(`规格型号"${scannedModel}"不在当前发货单的物料列表中`);
      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>