From dfe428b9d7e76c8172a8599d13db10c06fc336a2 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期六, 29 十一月 2025 16:38:58 +0800
Subject: [PATCH] fix: 完成扫码出库功能

---
 src/pages.json               |    6 
 src/api/product/outbound.ts  |   15 +
 src/pages/index/index.vue    |    6 
 src/pages/outbound/index.vue |  416 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 443 insertions(+), 0 deletions(-)

diff --git a/src/api/product/outbound.ts b/src/api/product/outbound.ts
new file mode 100644
index 0000000..9f46c06
--- /dev/null
+++ b/src/api/product/outbound.ts
@@ -0,0 +1,15 @@
+import request from "@/utils/request";
+import { BaseResult } from "@/models/base";
+
+const OutboundApi = {
+  // 瀹屾垚鍑哄簱
+  finishedOutbound(data: Array<{ outPutId: string | number; projectId: string | number }>) {
+    return request<BaseResult<any>>({
+      url: "/app/finishedOutbound",
+      method: "POST",
+      data: data,
+    });
+  },
+};
+
+export default OutboundApi;
diff --git a/src/pages.json b/src/pages.json
index f35c63a..1759892 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -278,6 +278,12 @@
       }
     },
     {
+      "path": "pages/outbound/index",
+      "style": {
+        "navigationBarTitleText": "鍑哄簱"
+      }
+    },
+    {
       "path": "pages/routingInspection/index",
       "style": {
         "navigationBarTitleText": "宸℃"
diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue
index b5db940..111981c 100644
--- a/src/pages/index/index.vue
+++ b/src/pages/index/index.vue
@@ -201,6 +201,12 @@
     url: "/pages/routingInspection/index",
     show: false,
   },
+  // {
+  //   icon: "/static/icons/log.png",
+  //   title: "鍑哄簱",
+  //   url: "/pages/outbound/index",
+  //   show: true,
+  // },
 ]);
 
 // 鍔犺浇璁块棶缁熻鏁版嵁
diff --git a/src/pages/outbound/index.vue b/src/pages/outbound/index.vue
new file mode 100644
index 0000000..a3a435c
--- /dev/null
+++ b/src/pages/outbound/index.vue
@@ -0,0 +1,416 @@
+<template>
+  <view class="list_box">
+    <CardTitle title="鍑哄簱" :hideAction="false">
+      <template #action>
+        <wd-button type="icon" icon="scan" color="#0D867F" @click="openScan"></wd-button>
+      </template>
+    </CardTitle>
+    <view class="list_content">
+      <view v-if="outboundList.length === 0" class="empty_tip">
+        <view class="empty_text">鏆傛棤鍑哄簱鏁版嵁</view>
+      </view>
+      <view v-for="(item, index) in outboundList" :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.batchNo || "-" }}</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.productionDate || "-" }}</text>
+            </view>
+          </view>
+        </view>
+        <view class="outbound_item_action">
+          <wd-button
+            type="icon"
+            icon="delete"
+            size="small"
+            custom-class="delete-btn"
+            @click.stop="removeOutboundItem(index)"
+          ></wd-button>
+        </view>
+      </view>
+    </view>
+    <view v-if="outboundList.length > 0" class="outbound_footer">
+      <wd-button block @click="handleOutbound">
+        <text class="text-[#fff]">鍑哄簱</text>
+      </wd-button>
+    </view>
+    <Scan ref="scanRef" emitName="scanOutbound" />
+    <wd-toast />
+  </view>
+</template>
+
+<script setup lang="ts">
+import CardTitle from "@/components/card-title/index.vue";
+import Scan from "@/components/scan/index.vue";
+import { useToast } from "wot-design-uni";
+import { dayjs } from "wot-design-uni";
+import OutboundApi from "@/api/product/outbound";
+
+const scanRef = ref();
+const toast = useToast();
+const outboundList = ref<any[]>([]);
+const projectId = ref<string | number>("");
+
+// 鏍煎紡鍖栨椂闂�
+const formatTime = (date: Date) => {
+  return dayjs(date).format("YYYY-MM-DD HH:mm:ss");
+};
+
+// 瑙f瀽鎵爜鍐呭
+const parseScanCode = (scanCode: string) => {
+  try {
+    // 绗竴娆¤В鏋愶細瑙f瀽澶栧眰JSON
+    const outerParsed = JSON.parse(scanCode);
+
+    // 濡傛灉澶栧眰鏈� code 瀛楁锛屼笖鏄瓧绗︿覆锛岄渶瑕佸啀娆¤В鏋�
+    let innerData = null;
+    if (outerParsed.code && typeof outerParsed.code === "string") {
+      try {
+        const innerParsed = JSON.parse(outerParsed.code);
+
+        // 鏌ユ壘鎵�鏈夋暟瀛梜ey锛堝 "12480"锛夛紝杩欎釜鏁板瓧key灏辨槸id
+        const keys = Object.keys(innerParsed);
+        // 鎵惧埌鎵�鏈夋暟瀛梜ey锛屾帓闄� "code" 瀛楁
+        const numberKeys = keys.filter((key) => !isNaN(Number(key)) && key !== "code");
+
+        if (numberKeys.length > 0) {
+          // 鍙栫涓�涓暟瀛梜ey锛堣繖涓氨鏄痠d锛�
+          const dataKey = numberKeys[0];
+          const idValue = Number(dataKey); // 鏁板瓧key灏辨槸id鍊�
+          // 鎻愬彇鏁板瓧key瀵瑰簲鐨勬暟鎹璞�
+          const extractedData = innerParsed[dataKey];
+          if (extractedData) {
+            innerData = { ...extractedData }; // 澶嶅埗鏁版嵁瀵硅薄
+            // 纭繚鏁版嵁瀵硅薄涓湁id瀛楁锛屽鏋滄病鏈夊垯浣跨敤鏁板瓧key浣滀负id
+            if (!innerData.id) {
+              innerData.id = idValue;
+            }
+          }
+        } else {
+          // 濡傛灉娌℃湁鏁板瓧key锛屽皾璇曠洿鎺ヤ娇鐢ㄥ璞★紙鎺掗櫎 code 瀛楁锛�
+          const { code, ...rest } = innerParsed;
+          if (Object.keys(rest).length > 0) {
+            innerData = rest;
+          }
+        }
+      } catch (e) {
+        console.error("鍐呭眰JSON瑙f瀽澶辫触:", e);
+      }
+    } else {
+      // 濡傛灉娌℃湁 code 瀛楁锛岀洿鎺ヤ娇鐢ㄥ灞傛暟鎹�
+      innerData = outerParsed;
+    }
+
+    // 濡傛灉 innerData 浠嶇劧涓虹┖锛屽皾璇曠洿鎺ヨВ鏋�
+    if (!innerData) {
+      innerData = outerParsed;
+    }
+
+    // 濡傛灉 innerData 浠嶇劧鍖呭惈鏁板瓧key缁撴瀯锛堝 { "12480": {...}, "code": "..." }锛夛紝闇�瑕佸啀娆℃彁鍙�
+    if (innerData && typeof innerData === "object" && !innerData.id && !innerData.contractno) {
+      const keys = Object.keys(innerData);
+      const numberKeys = keys.filter((key) => !isNaN(Number(key)) && key !== "code");
+      if (numberKeys.length > 0) {
+        const dataKey = numberKeys[0];
+        const extractedData = innerData[dataKey];
+        if (extractedData) {
+          innerData = { ...extractedData };
+        }
+      }
+    }
+
+    // 鎻愬彇鏁版嵁瀛楁锛堟牴鎹簩缁寸爜瀹為檯瀛楁鍚嶇粺涓�锛�
+    const data = {
+      id: innerData?.id,
+      contractNo: innerData?.contractno,
+      batchNo: innerData?.monofilamentnumber,
+      model: innerData?.model,
+      weight: innerData?.actuallyweight,
+      productionDate: innerData?.producttime,
+      projectId: innerData?.projectid,
+      // 淇濈暀鍘熷鏁版嵁锛堜繚瀛樻彁鍙栧悗鐨勬暟鎹璞★級
+      rawData: innerData,
+      scanCode: scanCode,
+    };
+
+    // 濡傛灉鎵爜鏁版嵁涓湁 projectId锛屼繚瀛樺畠
+    if (data.projectId && !projectId.value) {
+      projectId.value = data.projectId;
+    }
+
+    return data;
+  } catch (error) {
+    console.error("JSON瑙f瀽澶辫触:", error);
+    // 濡傛灉涓嶆槸JSON锛屽皾璇曟寜閫楀彿鍒嗗壊锛堝彲鑳芥槸CSV鏍煎紡锛�
+    const parts = scanCode.split(",");
+    if (parts.length >= 5) {
+      return {
+        id: parts[0] || "-", // 鍋囪绗竴涓槸id
+        contractNo: parts[1] || "-",
+        batchNo: parts[2] || "-",
+        model: parts[3] || "-",
+        weight: parts[4] || "-",
+        productionDate: parts[5] || "-",
+        projectId: projectId.value || "-",
+        scanCode: scanCode,
+      };
+    }
+    // 濡傛灉閮芥棤娉曡В鏋愶紝杩斿洖鍘熷瀛楃涓�
+    return {
+      id: scanCode, // 浣跨敤鍘熷瀛楃涓蹭綔涓篿d
+      contractNo: scanCode,
+      batchNo: "-",
+      model: "-",
+      weight: "-",
+      productionDate: "-",
+      projectId: projectId.value || "-",
+      scanCode: scanCode,
+    };
+  }
+};
+
+// 鎵爜鍥炶皟
+const getScanCode = (code: any) => {
+  // 濡傛灉 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;
+  }
+
+  // 瑙f瀽鎵爜鍐呭
+  const parsedData = parseScanCode(scanCode);
+
+  // 浣跨敤id浣滀负鍞竴鏍囪瘑
+  const uniqueId = parsedData.id;
+
+  // 濡傛灉娌℃湁id锛屾彁绀洪敊璇�
+  if (!uniqueId || uniqueId === "-" || uniqueId === null || uniqueId === undefined) {
+    toast.error("鎵爜鍐呭缂哄皯鍞竴鏍囪瘑锛屾棤娉曟坊鍔�");
+    return;
+  }
+
+  // 妫�鏌ユ槸鍚﹀凡瀛樺湪锛堟牴鎹甶d鍒ゆ柇锛�
+  const exists = outboundList.value.some((item) => {
+    const itemId = item.id;
+    return itemId && itemId === uniqueId && itemId !== "-";
+  });
+
+  if (exists) {
+    toast.error("璇ユ潯鐮佸凡瀛樺湪锛岃鍕块噸澶嶆壂鐮�");
+    return;
+  }
+
+  // 娣诲姞鍒板垪琛�
+  const newItem = {
+    ...parsedData,
+    scanTime: formatTime(new Date()),
+  };
+
+  outboundList.value.push(newItem);
+  toast.success("鎵爜鎴愬姛");
+};
+
+// 瑙﹀彂鎵爜
+const openScan = () => {
+  scanRef.value.triggerScan();
+};
+
+// 鍒犻櫎椤�
+const removeOutboundItem = (index: number) => {
+  const item = outboundList.value[index];
+  const itemName = item.contractNo || item.batchNo || `绗�${index + 1}椤筦;
+
+  uni.showModal({
+    title: "纭鍒犻櫎",
+    content: `纭畾瑕佸垹闄�"${itemName}"鍚楋紵`,
+    confirmText: "鍒犻櫎",
+    cancelText: "鍙栨秷",
+    confirmColor: "#ff4444",
+    success: (res) => {
+      if (res.confirm) {
+        outboundList.value.splice(index, 1);
+        toast.success("鍒犻櫎鎴愬姛");
+      }
+    },
+  });
+};
+
+// 澶勭悊鍑哄簱
+const handleOutbound = async () => {
+  if (outboundList.value.length === 0) {
+    toast.error("鏆傛棤鍑哄簱鏁版嵁");
+    return;
+  }
+
+  // 鏋勫缓璇锋眰鏁版嵁
+  const requestData = outboundList.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("鍑哄簱鎴愬姛");
+      // 娓呯┖鍒楄〃
+      outboundList.value = [];
+    } else {
+      toast.error(msg || "鍑哄簱澶辫触");
+    }
+  } catch (error: any) {
+    uni.hideLoading();
+    console.error("鍑哄簱澶辫触:", error);
+  }
+};
+
+// 纭繚鍏堢Щ闄ゅ啀娣诲姞鐩戝惉
+const setupScanListener = () => {
+  uni.$off("scanOutbound", getScanCode); // 鍏堢Щ闄ゆ棫鐨�
+  uni.$on("scanOutbound", getScanCode); // 鍐嶆坊鍔犳柊鐨�
+};
+
+onMounted(() => {
+  setupScanListener();
+});
+
+onUnmounted(() => {
+  uni.$off("scanOutbound", getScanCode);
+});
+</script>
+
+<style lang="scss" scoped>
+.list_box {
+  height: calc(100vh - 100px);
+  background: #f3f9f8;
+  display: flex;
+  flex-direction: column;
+}
+
+.list_content {
+  flex: 1;
+  overflow-y: auto;
+  padding-bottom: 120rpx;
+}
+
+.empty_tip {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 100rpx 0;
+  color: #999;
+
+  .empty_text {
+    font-size: 32rpx;
+    margin-bottom: 20rpx;
+  }
+}
+
+.outbound_item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 30rpx;
+  margin: 20rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+
+  .outbound_item_left {
+    display: flex;
+    align-items: center;
+    flex: 1;
+  }
+
+  .outbound_item_content {
+    flex: 1;
+  }
+
+  .outbound_item_row {
+    display: flex;
+    align-items: center;
+    margin-bottom: 12rpx;
+    font-size: 28rpx;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  .outbound_item_label {
+    color: #666;
+    min-width: 140rpx;
+    flex-shrink: 0;
+  }
+
+  .outbound_item_value {
+    color: #333;
+    flex: 1;
+    word-break: break-all;
+  }
+
+  .outbound_item_action {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-left: 20rpx;
+    flex-shrink: 0;
+  }
+}
+
+:deep(.delete-btn) {
+  .wd-button__content {
+    color: #ff4444 !important;
+  }
+
+  &:active {
+    opacity: 0.7;
+  }
+}
+
+:deep(.wd-button__content) {
+  color: #0d867f;
+}
+
+.outbound_footer {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 20rpx 30rpx;
+  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+  z-index: 100;
+}
+</style>

--
Gitblit v1.9.3