From 38d723b6de39a6882a537a691159e40bd4c0e837 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期二, 12 五月 2026 09:25:27 +0800
Subject: [PATCH] 合格入库扫码发货

---
 src/pages/inventoryManagement/scanOut/index.vue |  504 +++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 390 insertions(+), 114 deletions(-)

diff --git a/src/pages/inventoryManagement/scanOut/index.vue b/src/pages/inventoryManagement/scanOut/index.vue
index 69b9d07..5849b10 100644
--- a/src/pages/inventoryManagement/scanOut/index.vue
+++ b/src/pages/inventoryManagement/scanOut/index.vue
@@ -2,7 +2,7 @@
 
   <view class="scan-container">
 
-    <PageHeader title="鎵爜鍑哄簱"
+    <PageHeader title="鎵爜鍙戣揣"
 
                 @back="goBack" />
 
@@ -26,9 +26,9 @@
 
         <view class="module-info">
 
-          <text class="module-label">鍚堟牸鍑哄簱</text>
+          <text class="module-label">鍚堟牸鍙戣揣</text>
 
-          <text class="module-desc">鎵弿鍚堟牸鍝佽繘琛岄鐢ㄥ嚭搴擄紝閿�鍞壂鐮佸彂璐�</text>
+          <text class="module-desc">鎵弿閿�鍞鍗曪紝濉啓鍙戣揣鏁伴噺銆佽溅鐗屼笌瀹℃壒浜哄悗鎻愪氦鍙戣揣瀹℃壒锛堥�氳繃鍚庤嚜鍔ㄥ彂璐э級</text>
 
         </view>
 
@@ -193,19 +193,27 @@
 
           <view class="kv-row stocked-qty-row">
 
-            <text class="kv-label">鍑哄簱鏁伴噺</text>
+            <text class="kv-label">{{ needScanShipFlow ? "鏈鍙戣揣鏁伴噺" : "鍑哄簱鏁伴噺" }}</text>
 
             <view class="kv-value stocked-qty-input-wrap">
 
-              <up-input :key="'stocked-' + idx"
+              <text v-if="needScanShipFlow"
+
+                    class="scan-ship-qty-readonly">{{ resolveScanShipLineQuantity(item) }}</text>
+
+              <up-input v-else
+
+                        :key="'stocked-' + idx"
 
                         v-model="item.operateQuantity"
 
                         type="number"
 
                         placeholder="璇疯緭鍏ュ嚭搴撴暟閲�"
+                        :clearable="!isSalesQualifiedOutboundQtyLocked"
 
-                        clearable
+                        :disabled="isSalesQualifiedOutboundQtyLocked"
+
 
                         border="surround"
 
@@ -215,9 +223,147 @@
 
           </view>
 
+          <text v-if="needScanShipFlow"
+
+                class="scan-ship-qty-hint">鏁村崟涓�娆℃�у彂璐э紝鏁伴噺浠ヨ鍗曚负鍑嗭紝涓嶅彲淇敼</text>
+
         </view>
 
       </view>
+
+      <view v-if="needScanShipFlow"
+
+            class="scan-ship-card">
+
+        <view class="scan-ship-title">鍙戣揣淇℃伅</view>
+
+        <u-form label-width="160rpx">
+
+          <u-form-item label="鍙戣揣鏂瑰紡"
+
+                       required>
+
+            <u-input v-model="scanShipTypeLabel"
+
+                     readonly
+
+                     placeholder="璇烽�夋嫨"
+
+                     @click="scanShipTypeSheetShow = true" />
+
+          </u-form-item>
+
+          <u-form-item v-if="scanShipTypeValue === '璐ц溅'"
+
+                       label="杞︾墝鍙�"
+
+                       required>
+
+            <u-input v-model="scanShipCarNumber"
+
+                     placeholder="璇疯緭鍏ヨ溅鐗屽彿"
+
+                     clearable />
+
+          </u-form-item>
+
+          <u-form-item v-if="scanShipTypeValue === '蹇��'"
+
+                       label="蹇�掑崟鍙�"
+
+                       required>
+
+            <u-input v-model="scanShipExpress"
+
+                     placeholder="璇疯緭鍏ュ揩閫掑崟鍙�"
+
+                     clearable />
+
+          </u-form-item>
+
+        </u-form>
+
+        <view class="scan-ship-approval">
+
+          <text class="scan-ship-subtitle">瀹℃壒浜�</text>
+
+          <view v-if="scanShipApprover.nickName"
+
+                class="scan-ship-approver-pill">
+
+            <text>{{ scanShipApprover.nickName }}</text>
+
+            <text class="scan-ship-remove"
+
+                  @click="clearScanShipApprover">脳</text>
+
+          </view>
+
+          <u-button v-else
+
+                    size="small"
+
+                    type="primary"
+
+                    plain
+
+                    @click="openScanShipContactSelect">閫夋嫨瀹℃壒浜�</u-button>
+
+        </view>
+
+        <view class="scan-ship-files">
+
+          <text class="scan-ship-subtitle">鍙戣揣闄勪欢锛堥�夊~锛屾渶澶�10寮狅級</text>
+
+          <u-button size="small"
+
+                    type="primary"
+
+                    :loading="scanShipUploading"
+
+                    :disabled="scanShipFiles.length >= 10"
+
+                    @click="chooseScanShipImage">娣诲姞鍥剧墖</u-button>
+
+          <view v-if="scanShipFiles.length"
+
+                class="scan-ship-file-grid">
+
+            <view v-for="(f, fi) in scanShipFiles"
+
+                  :key="fi"
+
+                  class="scan-ship-thumb-wrap">
+
+              <image :src="scanShipFileUrl(f)"
+
+                     class="scan-ship-thumb"
+
+                     mode="aspectFill"
+
+                     @click="previewScanShipImage(fi)" />
+
+              <text class="scan-ship-thumb-del"
+
+                    @click.stop="removeScanShipFile(fi)">脳</text>
+
+            </view>
+
+          </view>
+
+        </view>
+
+      </view>
+
+      <up-action-sheet :show="scanShipTypeSheetShow"
+
+                       :actions="scanShipTypeActions"
+
+                       title="鍙戣揣鏂瑰紡"
+
+                       @select="onScanShipTypeSelect"
+
+                       @close="scanShipTypeSheetShow = false" />
 
       <view class="footer-btns">
 
@@ -225,15 +371,13 @@
 
                   @click="cancelForm">杩斿洖</u-button>
 
-        <u-button v-if="showSalesShipButton"
+        <u-button v-if="needScanShipFlow"
 
                   class="footer-confirm-btn"
 
                   :loading="submitLoading"
 
-                  :disabled="salesShipButtonDisabled"
-
-                  @click="handleSalesShipFromScan">鍙戣揣</u-button>
+                  @click="confirmScanShipApply">鎻愪氦鍙戣揣瀹℃壒</u-button>
 
         <u-button v-else
 
@@ -255,16 +399,15 @@
 
 <script setup>
 
-  import { ref, computed } from "vue";
+  import { ref, computed, onMounted, onUnmounted } from "vue";
 
   import PageHeader from "@/components/PageHeader.vue";
 
-  import {
-    productList as salesProductList,
-    getSalesLedgerWithProducts,
-  } from "@/api/salesManagement/salesLedger";
+  import { productList as salesProductList, scanShipApply } from "@/api/salesManagement/salesLedger";
 
-  import { canLedgerShip, executeSalesLedgerShip, getLedgerShippingLabel } from "@/utils/salesLedgerShip";
+  import config from "@/config";
+  import { getToken } from "@/utils/auth";
+  import { getLedgerShippingLabel } from "@/utils/salesLedgerShip";
 
   import modal from "@/plugins/modal";
 
@@ -277,6 +420,8 @@
     resolveListTypeForDetail,
     resolveContractNo,
     buildSalesLedgerProductList,
+    buildScanShipProductList,
+    resolveScanShipLineQuantity,
     hasAnyPositiveStockedQty,
     resolveSubmitSceneKey,
   } from "./scanOut.logic";
@@ -298,25 +443,212 @@
   /** 浜岀淮鐮佷腑鐨勫彴璐︿富閿� id */
   const scanLedgerId = ref(null);
 
-  /** 鍚堟牸鍑哄簱+閿�鍞爜锛氱敤浜庛�屽彂璐с�嶆寜閽笌鍙拌处绾ф牎楠岋紙涓庨攢鍞彴璐︿竴鑷达級 */
-  const salesLedgerSnapshotForShip = ref(null);
-
   const submitLoading = ref(false);
 
-  const showSalesShipButton = computed(
+  /** 閿�鍞� + 鍚堟牸锛氳蛋銆屾彁浜ゅ彂璐у鎵广�嶏紝涓嶅啀鐩存帴鍑哄簱鎴栬烦杞師鍙戣揣椤� */
+  const needScanShipFlow = computed(
     () =>
       showForm.value &&
       type.value === QUALITY_TYPE.qualified &&
       contractKind.value === CONTRACT_KIND.sales
   );
 
-  const salesShipButtonDisabled = computed(() => {
-    if (!showSalesShipButton.value) return false;
-    const snap = salesLedgerSnapshotForShip.value;
-    if (!snap) return false;
-    return !canLedgerShip(snap);
-  });
+  const scanShipTypeValue = ref("璐ц溅");
+  const scanShipTypeLabel = computed(() => scanShipTypeValue.value);
+  const scanShipTypeSheetShow = ref(false);
+  const scanShipTypeActions = ref([
+    { name: "璐ц溅", value: "璐ц溅" },
+    { name: "蹇��", value: "蹇��" },
+  ]);
+  const scanShipCarNumber = ref("");
+  const scanShipExpress = ref("");
+  const scanShipApprover = ref({ userId: null, nickName: null });
+  const scanShipFiles = ref([]);
+  const scanShipUploading = ref(false);
 
+  const scanShipUploadConfig = {
+    action: "/file/upload",
+    limit: 10,
+    fileType: ["jpg", "jpeg", "png", "gif", "webp"],
+    formType: 10,
+  };
+
+  const uploadScanShipFileUrl = computed(
+    () => (config.baseUrl || "").replace(/\/$/, "") + scanShipUploadConfig.action
+  );
+
+  const onScanShipTypeSelect = item => {
+    scanShipTypeValue.value = item.name || item.value || "璐ц溅";
+    scanShipTypeSheetShow.value = false;
+    if (scanShipTypeValue.value === "璐ц溅") scanShipExpress.value = "";
+    else scanShipCarNumber.value = "";
+  };
+
+  const openScanShipContactSelect = () => {
+    uni.setStorageSync("stepIndex", 0);
+    uni.navigateTo({
+      url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect?approveType=7&source=scanShip",
+    });
+  };
+
+  const clearScanShipApprover = () => {
+    scanShipApprover.value = { userId: null, nickName: null };
+  };
+
+  const handleScanShipSelectContact = data => {
+    if (data?.source !== "scanShip") return;
+    if (!needScanShipFlow.value) return;
+    const c = data?.contact;
+    if (!c) return;
+    scanShipApprover.value = { userId: c.userId, nickName: c.nickName };
+  };
+
+  const scanShipFileUrl = file => {
+    const base = config.fileUrl || "";
+    const link = file?.link || file?.url || "";
+    if (link && String(link).startsWith("http")) return link;
+    if (link && link.startsWith("/")) return base + link;
+    return file?.tempFilePath || file?.path || "";
+  };
+
+  const chooseScanShipImage = () => {
+    if (scanShipFiles.value.length >= scanShipUploadConfig.limit) {
+      modal.msgError(`鏈�澶�${scanShipUploadConfig.limit}寮燻);
+      return;
+    }
+    uni.chooseImage({
+      count: 1,
+      sizeType: ["compressed"],
+      sourceType: ["album", "camera"],
+      success: res => {
+        const path = res?.tempFilePaths?.[0];
+        const tf = res?.tempFiles?.[0] || {};
+        if (!path) return;
+        const token = getToken();
+        if (!token) {
+          modal.msgError("鏈櫥褰�");
+          return;
+        }
+        scanShipUploading.value = true;
+        uni.uploadFile({
+          url: uploadScanShipFileUrl.value,
+          filePath: path,
+          name: "file",
+          formData: { type: scanShipUploadConfig.formType },
+          header: { Authorization: `Bearer ${token}` },
+          success: up => {
+            try {
+              const body = JSON.parse(up.data || "{}");
+              if (body.code === 200 && body.data) {
+                const d = body.data;
+                scanShipFiles.value.push({
+                  tempId: d.tempId ?? d.tempFileId ?? d.id,
+                  link: d.link || d.url,
+                  url: d.url,
+                  name: d.originalFilename || d.originalName || "鍥剧墖",
+                  tempFilePath: path,
+                });
+                modal.msgSuccess("涓婁紶鎴愬姛");
+              } else {
+                modal.msgError(body.msg || "涓婁紶澶辫触");
+              }
+            } catch (e) {
+              modal.msgError("涓婁紶瑙f瀽澶辫触");
+            }
+          },
+          fail: () => modal.msgError("涓婁紶澶辫触"),
+          complete: () => {
+            scanShipUploading.value = false;
+          },
+        });
+      },
+    });
+  };
+
+  const removeScanShipFile = idx => {
+    scanShipFiles.value.splice(idx, 1);
+  };
+
+  const previewScanShipImage = idx => {
+    const urls = scanShipFiles.value.map(f => scanShipFileUrl(f)).filter(Boolean);
+    const cur = urls[idx];
+    if (urls.length && cur) uni.previewImage({ urls, current: cur });
+  };
+
+  const getScanShipTempFileIds = () =>
+    scanShipFiles.value.map(f => f.tempId).filter(id => id != null && id !== "");
+
+  const confirmScanShipApply = async () => {
+    if (scanLedgerId.value == null || scanLedgerId.value === "") {
+      modal.msgError("缂哄皯璁㈠崟淇℃伅锛岃閲嶆柊鎵爜");
+      return;
+    }
+    if (!hasEditableOutboundItems.value) {
+      modal.msgError("璇ヤ骇鍝佸凡缁忓叏閮ㄥ嚭搴�");
+      return;
+    }
+    const salesLedgerProductList = buildScanShipProductList(recordList.value);
+    if (!hasAnyPositiveStockedQty(salesLedgerProductList)) {
+      modal.msgError("褰撳墠璁㈠崟鏃犲彲鍙戣揣鏁伴噺");
+      return;
+    }
+    if (scanShipTypeValue.value === "璐ц溅" && !String(scanShipCarNumber.value || "").trim()) {
+      modal.msgError("璇疯緭鍏ヨ溅鐗屽彿");
+      return;
+    }
+    if (scanShipTypeValue.value === "蹇��" && !String(scanShipExpress.value || "").trim()) {
+      modal.msgError("璇疯緭鍏ュ揩閫掑崟鍙�");
+      return;
+    }
+    if (!scanShipApprover.value?.userId) {
+      modal.msgError("璇烽�夋嫨瀹℃壒浜�");
+      return;
+    }
+    if (scanShipUploading.value) {
+      modal.msgError("闄勪欢涓婁紶涓紝璇风◢鍊�");
+      return;
+    }
+    const payload = {
+      salesLedgerId: scanLedgerId.value,
+      salesLedgerProductList,
+      approveUserIds: String(scanShipApprover.value.userId),
+      shipType: scanShipTypeValue.value,
+      shippingCarNumber:
+        scanShipTypeValue.value === "璐ц溅" ? String(scanShipCarNumber.value || "").trim() : "",
+      expressNumber:
+        scanShipTypeValue.value === "蹇��" ? String(scanShipExpress.value || "").trim() : "",
+      tempFileIds: getScanShipTempFileIds(),
+    };
+    try {
+      submitLoading.value = true;
+      modal.loading("鎻愪氦涓�...");
+      const res = await scanShipApply(payload);
+      modal.closeLoading();
+      if (res.code === 200) {
+        modal.msgSuccess(res.msg || "鍙戣揣瀹℃壒宸插彂璧�");
+        resetDetailView();
+      } else {
+        modal.msgError(res.msg || "鎻愪氦澶辫触");
+      }
+    } catch (e) {
+      modal.closeLoading();
+      console.error(e);
+    } finally {
+      submitLoading.value = false;
+    }
+  };
+
+  onMounted(() => {
+    uni.$on("selectContact", handleScanShipSelectContact);
+  });
+  onUnmounted(() => {
+    uni.$off("selectContact", handleScanShipSelectContact);
+  });
+  const isSalesQualifiedOutboundQtyLocked = computed(
+    () =>
+      type.value === QUALITY_TYPE.qualified &&
+      contractKind.value === CONTRACT_KIND.sales
+  );
   const submitConfigByScene = createSubmitConfig(scanLedgerId);
 
   const cardTitleMain = computed(() => {
@@ -670,11 +1002,17 @@
 
     scanLedgerId.value = null;
 
-    salesLedgerSnapshotForShip.value = null;
-
     expandedByIndex.value = {};
 
     recordList.value = [];
+
+    scanShipTypeValue.value = "璐ц溅";
+    scanShipCarNumber.value = "";
+    scanShipExpress.value = "";
+    scanShipApprover.value = { userId: null, nickName: null };
+    scanShipFiles.value = [];
+    scanShipUploading.value = false;
+    scanShipTypeSheetShow.value = false;
 
   };
 
@@ -770,64 +1108,6 @@
     }
 
   };
-
-  const handleSalesShipFromScan = async () => {
-
-    if (scanLedgerId.value == null || scanLedgerId.value === "") {
-
-      modal.msgError("缂哄皯璁㈠崟淇℃伅锛岃閲嶆柊鎵爜");
-
-      return;
-
-    }
-
-    if (salesLedgerSnapshotForShip.value && !canLedgerShip(salesLedgerSnapshotForShip.value)) return;
-
-    submitLoading.value = true;
-
-    try {
-
-      modal.loading("鍔犺浇涓�...");
-
-      let ledgerRow = salesLedgerSnapshotForShip.value;
-
-      if (!ledgerRow?.id) {
-
-        ledgerRow = await getSalesLedgerWithProducts({ id: scanLedgerId.value, type: 1 });
-
-      }
-
-      modal.closeLoading();
-
-      if (!ledgerRow?.id) {
-
-        modal.msgError("鑾峰彇鍙拌处澶辫触");
-
-        return;
-
-      }
-
-      const { productData: _pd, ...ledgerForShip } = ledgerRow;
-
-      await executeSalesLedgerShip(ledgerForShip);
-
-    } catch (e) {
-
-      modal.closeLoading();
-
-      console.error("鎵爜鍙戣揣澶辫触", e);
-
-      modal.msgError("鑾峰彇鍙拌处澶辫触");
-
-    } finally {
-
-      submitLoading.value = false;
-
-    }
-
-  };
-
-
 
   const goBack = () => {
 
@@ -994,35 +1274,9 @@
 
         showForm.value = true;
 
-        if (type.value === QUALITY_TYPE.qualified && kind === CONTRACT_KIND.sales) {
-
-          salesLedgerSnapshotForShip.value = null;
-
-          getSalesLedgerWithProducts({ id: scanData.id, type: 1 })
-
-            .then(res => {
-
-              salesLedgerSnapshotForShip.value = res;
-
-            })
-
-            .catch(() => {
-
-              salesLedgerSnapshotForShip.value = null;
-
-            });
-
-        } else {
-
-          salesLedgerSnapshotForShip.value = null;
-
-        }
-
       } else {
 
         scanLedgerId.value = null;
-
-        salesLedgerSnapshotForShip.value = null;
 
         modal.msgError("鏈煡璇㈠埌鏄庣粏鏁版嵁");
 
@@ -1033,8 +1287,6 @@
       modal.closeLoading();
 
       scanLedgerId.value = null;
-
-      salesLedgerSnapshotForShip.value = null;
 
       console.error("澶勭悊鎵爜缁撴灉澶辫触", error);
 
@@ -1426,6 +1678,30 @@
 
   }
 
+  .scan-ship-qty-readonly {
+
+    font-size: 30rpx;
+
+    font-weight: 600;
+
+    color: #1a1a1a;
+
+  }
+
+  .scan-ship-qty-hint {
+
+    display: block;
+
+    font-size: 24rpx;
+
+    color: #999;
+
+    padding: 8rpx 0 0;
+
+    line-height: 1.4;
+
+  }
+
 
 
   .footer-btns {

--
Gitblit v1.9.3