From b5d01891fbbd69d8d50d2b4fb562fac3130fc2d6 Mon Sep 17 00:00:00 2001
From: 647556386 <647556386@qq.com>
Date: 星期一, 20 四月 2026 11:01:22 +0800
Subject: [PATCH] 虚拟出入库单据锁定

---
 项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/NoStockOut.vue |  394 +++++++++++++++++++++-----------------------------------
 1 files changed, 148 insertions(+), 246 deletions(-)

diff --git "a/\351\241\271\347\233\256\344\273\243\347\240\201/WIDESEA_WMSClient/src/extension/outbound/extend/NoStockOut.vue" "b/\351\241\271\347\233\256\344\273\243\347\240\201/WIDESEA_WMSClient/src/extension/outbound/extend/NoStockOut.vue"
index bb63598..7fc2023 100644
--- "a/\351\241\271\347\233\256\344\273\243\347\240\201/WIDESEA_WMSClient/src/extension/outbound/extend/NoStockOut.vue"
+++ "b/\351\241\271\347\233\256\344\273\243\347\240\201/WIDESEA_WMSClient/src/extension/outbound/extend/NoStockOut.vue"
@@ -35,7 +35,6 @@
               ref="outboundInputRef"
               :disabled="loading || submitLoading || isOutboundVerified"
             ></el-input>
-            <!-- 鏂板锛氶獙璇佺姸鎬佹彁绀� -->
             <span v-if="isOutboundVerified" class="verified-tag">鉁� 宸查獙璇�</span>
             <span v-else-if="loading" class="loading-tag">鉁� 楠岃瘉涓�...</span>
           </el-form-item>
@@ -89,7 +88,7 @@
           </el-form-item>
         </el-form>
 
-        <!-- 涓嬫柟鏄剧ず妗嗭紙鐩存帴娓叉煋鍚庣杩斿洖鐨勬暟缁勶級 -->
+        <!-- 涓嬫柟鏄剧ず妗� -->
         <div class="scan-list">
           <el-card shadow="hover" style="margin-bottom: 10px; border: none;" class="custom-card">
             <div class="card-header">
@@ -98,36 +97,15 @@
             <div class="card-body">
               <el-scrollbar height="400px" class="custom-scrollbar">
                 <transition-group name="barcode-item-transition">
-                  <div class="barcode-item" v-for="(item, index) in scannedBarcodes" :key="`${item.barcode}-${index}`" :data-index="index">
+                  <div class="barcode-item" v-for="(item, index) in scannedBarcodes" :key="`${item.barcode}-${index}`">
                     <div class="barcode-detail">
-                      <div class="detail-row">
-                        <span class="label">鏉$爜锛�</span>
-                        <span class="value">{{ item.barcode || '-' }}</span>
-                      </div>
-                      <div class="detail-row">
-                        <span class="label">鐗╂枡缂栫爜锛�</span>
-                        <span class="value">{{ item.materielCode || '-' }}</span>
-                      </div>
-                      <div class="detail-row">
-                        <span class="label">鐗╂枡鍚嶇О锛�</span>
-                        <span class="value">{{ item.materielName || '-' }}</span>
-                      </div>
-                      <div class="detail-row">
-                        <span class="label">鎵规鍙凤細</span>
-                        <span class="value">{{ item.batchNo || '-' }}</span>
-                      </div>
-                      <div class="detail-row">
-                        <span class="label">鏉$爜鏁伴噺锛�</span>
-                        <span class="value">{{ item.orderQuantity || item.quantity || 0 }}</span>
-                      </div>
-                      <div class="detail-row">
-                        <span class="label">渚涘簲鍟嗙紪鐮侊細</span>
-                        <span class="value">{{ item.supplyCode || '-' }}</span>
-                      </div>
-                      <div class="detail-row">
-                        <span class="label">閲囪喘鍗曞彿锛�</span>
-                        <span class="value">{{ item.purchaseOrderNo || '-' }}</span>
-                      </div>
+                      <div class="detail-row"><span class="label">鏉$爜锛�</span><span class="value">{{ item.barcode || '-' }}</span></div>
+                      <div class="detail-row"><span class="label">鐗╂枡缂栫爜锛�</span><span class="value">{{ item.materielCode || '-' }}</span></div>
+                      <div class="detail-row"><span class="label">鐗╂枡鍚嶇О锛�</span><span class="value">{{ item.materielName || '-' }}</span></div>
+                      <div class="detail-row"><span class="label">鎵规鍙凤細</span><span class="value">{{ item.batchNo || '-' }}</span></div>
+                      <div class="detail-row"><span class="label">鏉$爜鏁伴噺锛�</span><span class="value">{{ item.orderQuantity || item.quantity || 0 }}</span></div>
+                      <div class="detail-row"><span class="label">渚涘簲鍟嗙紪鐮侊細</span><span class="value">{{ item.supplyCode || '-' }}</span></div>
+                      <div class="detail-row"><span class="label">閲囪喘鍗曞彿锛�</span><span class="value">{{ item.purchaseOrderNo || '-' }}</span></div>
                     </div>
                     <el-button
                       class="delete-btn"
@@ -162,11 +140,7 @@
           <el-button 
             type="text" 
             size="small" 
-            @click="(e) => {
-              e.stopPropagation();
-              e.preventDefault();
-              showDetailBox = false;
-            }" 
+            @click="handleCancel" 
             class="cancel-btn" 
             :disabled="loading || submitLoading"
           >
@@ -179,28 +153,21 @@
 </template>
 
 <script setup>
-import { ref, reactive, onMounted, nextTick } from 'vue';
+import { ref, reactive, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
 import { ElMessage } from 'element-plus';
 import { Search, Check } from '@element-plus/icons-vue';
-
 import VolBox from "@/components/basic/VolBox.vue";
 import http from '@/api/http';
 
 // 鍝嶅簲寮忔暟鎹�
 const showDetailBox = ref(false);
-const orderForm = reactive({
-  outboundOrderNo: "",
-  purchaseOrderNo: ""
-});
-const formData = reactive({
-  barcode: "",
-});
+const orderForm = reactive({ outboundOrderNo: "", purchaseOrderNo: "" });
+const formData = reactive({ barcode: "" });
 const scannedBarcodes = ref([]);
 const loading = ref(false);
-// 鏂板锛氭彁浜や笓灞瀕oading鐘舵�侊紝鎺у埗閬僵灞傛樉绀�
 const submitLoading = ref(false);
-// 鏍稿績鏂板锛氬嚭搴撳崟楠岃瘉鐘舵�佹爣璇�
 const isOutboundVerified = ref(false);
+let unlockCalled = false; // 闃叉閲嶅瑙i攣
 
 // 妯℃澘寮曠敤
 const formRef = ref(null);
@@ -208,227 +175,172 @@
 const outboundInputRef = ref(null);
 const purchaseInputRef = ref(null);
 
-// 缁勪欢鎸傝浇鏃惰仛鐒﹀埌鍑哄簱鍗曡緭鍏ユ
-onMounted(() => {
-  nextTick(() => {
-    outboundInputRef.value?.focus();
-  });
-});
+// 闊抽璧勬簮
+const successAudioSrc = require('@/assets/audio/success.mp3');
+const errorAudioSrc = require('@/assets/audio/error.mp3');
+const playAudio = (src, volume = 0.8) => {
+  try { const audio = new Audio(src); audio.volume = volume; audio.play().catch(() => {}); } catch(e) {}
+};
+const playSuccess = () => playAudio(successAudioSrc);
+const playError = () => playAudio(errorAudioSrc);
 
-// 绠�鍗曢槻鎶栧嚱鏁�
-const debounce = (fn, delay = 100) => {
-  let timer = null;
-  return (...args) => {
-    if (timer) clearTimeout(timer);
-    timer = setTimeout(() => {
-      fn.apply(this, args);
-    }, delay);
-  };
+// ========== 瑙i攣鏍稿績閫昏緫锛堜娇鐢ㄦ柊鎺ュ彛 MovePickingOrders锛� ==========
+const unlockOutboundOrder = async () => {
+  if (!isOutboundVerified.value || !orderForm.outboundOrderNo?.trim()) return;
+  if (unlockCalled) return;
+  unlockCalled = true;
+  const outboundOrderNo = orderForm.outboundOrderNo.trim();
+  try {
+    // 鏇挎崲涓� MovePickingOrders 鎺ュ彛
+    await http.post(`/api/OutboundPicking/MovePickingOrders?outOrder=${outboundOrderNo}`, null, "瑙i攣鍑哄簱鍗曚腑...").catch(() => {});
+  } catch (error) {
+    // 闈欓粯澶辫触
+  }
 };
 
-// 鎵撳紑寮圭獥
+// 椤甸潰鍏抽棴/鍒锋柊鏃剁殑鍚屾瑙i攣锛堜娇鐢� sendBeacon 鎴� fetch keepalive锛�
+const unlockOnPageUnload = () => {
+  if (!isOutboundVerified.value || !orderForm.outboundOrderNo?.trim()) return;
+  const outboundOrderNo = orderForm.outboundOrderNo.trim();
+  const url = `/api/OutboundPicking/MovePickingOrders?outOrder=${encodeURIComponent(outboundOrderNo)}`;
+  // 浣跨敤 fetch keepalive 鍙戦��
+  fetch(url, { method: 'POST', keepalive: true, headers: { 'Content-Type': 'application/json' } }).catch(() => {});
+};
+
+// 鐩戝惉寮圭獥鍏抽棴锛堜换浣曟柟寮忥細鍙夊彿銆丒SC銆侀伄缃┿�佸彇娑堟寜閽瓑锛�
+watch(showDetailBox, (newVal, oldVal) => {
+  if (oldVal === true && newVal === false) {
+    // 寮圭獥鍏抽棴鏃惰皟鐢ㄨВ閿�
+    unlockOutboundOrder();
+  }
+});
+
+// 鍙栨秷鎸夐挳
+const handleCancel = (e) => {
+  e?.stopPropagation();
+  e?.preventDefault();
+  showDetailBox.value = false;
+};
+
+// 鎵撳紑寮圭獥锛堥噸缃墍鏈夌姸鎬侊級
 const open = () => {
+  unlockCalled = false;
   showDetailBox.value = true;
   scannedBarcodes.value = [];
   formData.barcode = "";
   orderForm.outboundOrderNo = "";
   orderForm.purchaseOrderNo = "";
-  submitLoading.value = false; // 鎵撳紑寮圭獥鏃堕噸缃彁浜oading
-  isOutboundVerified.value = false; // 鎵撳紑寮圭獥鏃堕噸缃嚭搴撳崟楠岃瘉鐘舵��
-  nextTick(() => {
-    outboundInputRef.value?.focus(); // 鎵撳紑寮圭獥浠嶈仛鐒﹀嚭搴撳崟杈撳叆妗�
-  });
+  submitLoading.value = false;
+  isOutboundVerified.value = false;
+  nextTick(() => outboundInputRef.value?.focus());
 };
 
-/**
- * 楠岃瘉鍑哄簱鍗曟嵁鍙风殑鏈夋晥鎬�
- * 鏍稿績淇敼锛氶獙璇佹垚鍔熷悗鏍囪isOutboundVerified涓簍rue锛屽け璐ュ垯閲嶇疆涓篺alse
- */
+// 楠岃瘉鍑哄簱鍗曞彿锛堜粛浣跨敤鍘熸帴鍙o紝姝ゆ帴鍙g敤浜庨獙璇佸崟鎹湁鏁堟�э級
 const validateOutboundOrder = async () => {
   const outboundOrderNo = orderForm.outboundOrderNo.trim();
-  if (!outboundOrderNo) {
-    ElMessage.warning("璇疯緭鍏ュ嚭搴撳崟鎹彿");
-    return;
-  }
-
+  if (!outboundOrderNo) { ElMessage.warning("璇疯緭鍏ュ嚭搴撳崟鎹彿"); return; }
   try {
     loading.value = true;
-    const res = await http.post(
-      `/api/OutboundPicking/GetAvailablePickingOrders?outOrder=`+ outboundOrderNo,
-      "楠岃瘉鍑哄簱鍗曟嵁鍙蜂腑..."
-    );
-
+    const res = await http.post(`/api/OutboundPicking/GetAvailablePickingOrders?outOrder=${outboundOrderNo}`, "楠岃瘉鍑哄簱鍗曟嵁鍙蜂腑...");
     if (res.status !== true) {
-      // 楠岃瘉澶辫触锛氭竻绌鸿緭鍏ユ锛屾彁绀洪敊璇紝閲嶇疆楠岃瘉鐘舵�侊紝鑱氱劍鍥炲嚭搴撳崟杈撳叆妗�
       orderForm.outboundOrderNo = "";
       isOutboundVerified.value = false;
-      ElMessage.error(res.message || "鍑哄簱鍗曟嵁鍙烽獙璇佸け璐ワ紝璇锋鏌ュ崟鎹彿鏄惁姝g‘");
-      nextTick(() => {
-        outboundInputRef.value?.focus(); // 澶辫触鑱氱劍鍑哄簱鍗曡緭鍏ユ
-      });
+      ElMessage.error(res.message || "鍑哄簱鍗曟嵁鍙烽獙璇佸け璐�");
+      nextTick(() => outboundInputRef.value?.focus());
       return;
     }
-
-    // 楠岃瘉鎴愬姛锛氭爣璁伴獙璇佺姸鎬佷负true锛屾彁绀虹敤鎴凤紝鑱氱劍鏉$爜鎵弿妗�
     isOutboundVerified.value = true;
     ElMessage.success("鍑哄簱鍗曟嵁鍙烽獙璇侀�氳繃");
-    nextTick(() => {
-      barcodeInputRef.value?.focus(); // 鎴愬姛鐩存帴璺宠浆鍒版潯鐮佹壂鎻忔
-    });
+    nextTick(() => barcodeInputRef.value?.focus());
   } catch (error) {
-    // 鎺ュ彛寮傚父锛氭竻绌鸿緭鍏ユ锛屾彁绀洪敊璇紝閲嶇疆楠岃瘉鐘舵�侊紝鑱氱劍鍥炲嚭搴撳崟杈撳叆妗�
     orderForm.outboundOrderNo = "";
     isOutboundVerified.value = false;
-    ElMessage.error(`鍑哄簱鍗曟嵁鍙烽獙璇佸紓甯革細${error.message || "缃戠粶閿欒锛岃閲嶈瘯"}`);
-    nextTick(() => {
-      outboundInputRef.value?.focus(); // 寮傚父鑱氱劍鍑哄簱鍗曡緭鍏ユ
-    });
+    ElMessage.error(`楠岃瘉寮傚父锛�${error.message || "缃戠粶閿欒"}`);
+    nextTick(() => outboundInputRef.value?.focus());
   } finally {
     loading.value = false;
   }
 };
 
-// 鍑哄簱鍗曡緭鍏ュ鐞�
-const handleOutboundInput = (value) => {
-  // 鏍稿績淇敼锛氳緭鍏ユ椂鑷姩閲嶇疆楠岃瘉鐘舵�侊紙闃叉鎵嬪姩淇敼宸查獙璇佺殑鍑哄簱鍗曞彿锛�
-  if (value && value.trim()) {
-    isOutboundVerified.value = false;
-  }
-};
+const handleOutboundInput = (val) => { if (val?.trim()) isOutboundVerified.value = false; };
+const handlePurchaseInput = (val) => {};
 
-// 閲囪喘鍗曡緭鍏ュ鐞�
-const handlePurchaseInput = (value) => {
-  if (value && value.trim()) {
-    // 鍙繚鐣欓噰璐崟鍙锋牸寮忛獙璇侀�昏緫
-  }
-};
-
-// 鑱氱劍鏉$爜杈撳叆妗嗭紙澶嶇敤鍑芥暟锛�
-const focusBarcodeInputDirectly = () => {
-  if (isOutboundVerified.value) {
-    barcodeInputRef.value?.focus();
-  } else {
-    ElMessage.warning("璇峰厛杈撳叆骞堕獙璇佹湁鏁堢殑鍑哄簱鍗曟嵁鍙�");
-    nextTick(() => {
-      outboundInputRef.value?.focus();
-    });
-  }
-};
-
-/**
- * 鏍规嵁鏉$爜鏌ヨ閲囪喘鍗曞彿
- */
+// 鑾峰彇閲囪喘鍗曞彿
 const getPurchaseOrderByBarcode = async (barcode) => {
   const res = await http.post(`/api/OutboundPicking/GetPurchaseOrderByBarcode?barCode=${encodeURIComponent(barcode)}`, "鏌ヨ閲囪喘鍗曞彿涓�...");
-
-  if (res.status !== true) {
-    throw new Error(res.message || "鏌ヨ閲囪喘鍗曞彿澶辫触");
-  }
-
+  if (res.status !== true) throw new Error(res.message || "鏌ヨ澶辫触");
   let purchaseOrderNo = '';
-  if (Array.isArray(res.data) && res.data.length > 0) {
-    purchaseOrderNo = res.data[0].purchaseOrderNo || res.data[0].orderId;
-  } else {
-    purchaseOrderNo = res.data?.purchaseOrderNo || res.data?.orderId;
-  }
-
+  if (Array.isArray(res.data) && res.data.length > 0) purchaseOrderNo = res.data[0].purchaseOrderNo || res.data[0].orderId;
+  else purchaseOrderNo = res.data?.purchaseOrderNo || res.data?.orderId;
   return purchaseOrderNo;
 };
 
-// 鎵弿鏉$爜鏍稿績閫昏緫
+// 鎵弿鏉$爜
 const handleScan = async () => {
-  // 鏍稿績鏂板锛氬墠缃牎楠岋紝纭繚鍑哄簱鍗曞凡楠岃瘉
   if (!isOutboundVerified.value) {
-    ElMessage.warning("璇峰厛楠岃瘉鏈夋晥鐨勫嚭搴撳崟鎹彿鍚庡啀鎵弿鏉$爜");
-    nextTick(() => {
-      outboundInputRef.value?.focus();
-    });
+    ElMessage.warning("璇峰厛楠岃瘉鍑哄簱鍗曟嵁鍙�");
+    playError();
+    nextTick(() => outboundInputRef.value?.focus());
     return;
   }
-
-  if (!formRef.value) return;
-  await formRef.value.validateField('barcode');
-
   const barcode = formData.barcode.trim();
-  const outboundOrderNo = orderForm.outboundOrderNo.trim();
-
-  // 鏉$爜鍘婚噸
+  if (!barcode) return;
   const isDuplicate = scannedBarcodes.value.some(item => item.barcode === barcode);
   if (isDuplicate) {
-    ElMessage.warning(`鏉$爜銆�${barcode}銆戝凡瀛樺湪锛屾棤闇�閲嶅鎵弿`);
+    ElMessage.warning(`鏉$爜銆�${barcode}銆戝凡瀛樺湪`);
+    playError();
     formData.barcode = "";
-    nextTick(() => barcodeInputRef.value?.focus()); // 鍘婚噸鍚庝粛鑱氱劍鏉$爜妗�
+    nextTick(() => barcodeInputRef.value?.focus());
     return;
   }
-
   try {
     loading.value = true;
-
-    // 姝ラ1锛氭煡璇㈤噰璐崟鍙�
-    const purchaseOrderNo = await getPurchaseOrderByBarcode(barcode);
-    if (purchaseOrderNo) {
-      orderForm.purchaseOrderNo = purchaseOrderNo;
-    } else {
-      ElMessage.info("鏈煡璇㈠埌璇ユ潯鐮佸搴旂殑閲囪喘鍗曞彿锛岀户缁獙璇佹潯鐮佹湁鏁堟��");
-      formData.barcode = "";
-      nextTick(() => barcodeInputRef.value?.focus());
-    }
-
-    // 姝ラ2锛氶獙璇佹潯鐮佸苟鑾峰彇鐗╂枡淇℃伅
+    let purchaseOrderNo = '';
+    try {
+      purchaseOrderNo = await getPurchaseOrderByBarcode(barcode);
+      if (purchaseOrderNo) orderForm.purchaseOrderNo = purchaseOrderNo;
+    } catch (e) { ElMessage.info("鏈煡璇㈠埌閲囪喘鍗曞彿锛岀户缁獙璇佹潯鐮�"); playError(); }
     const validateRes = await http.post("/api/OutboundPicking/BarcodeValidate", {
-      outOder: outboundOrderNo,
+      outOder: orderForm.outboundOrderNo,
       inOder: purchaseOrderNo || orderForm.purchaseOrderNo,
       barCode: barcode
     });
-
-    if (validateRes.status === true) {
-      if (!Array.isArray(validateRes.data) || validateRes.data.length === 0) {
-        ElMessage.warning("璇ユ潯鐮侀獙璇佹垚鍔燂紝浣嗘湭杩斿洖鐗╂枡淇℃伅");
-        formData.barcode = "";
-        nextTick(() => barcodeInputRef.value?.focus());
-      } else {
-        const newItems = validateRes.data.map(item => ({
-          barcode: item.barcode || '',
-          materielCode: item.materielCode || '',
-          materielName: item.materielName || '',
-          batchNo: item.batchNo || '',
-          orderQuantity: item.orderQuantity || item.quantity || 0,
-          supplyCode: item.supplyCode || '',
-          purchaseOrderNo: purchaseOrderNo || ''
-        }));
-        scannedBarcodes.value.push(...newItems);
-        ElMessage.success(`鎵弿鎴愬姛锛屾柊澧� ${newItems.length} 鏉$墿鏂欎俊鎭紝绱 ${scannedBarcodes.value.length} 鏉);
-        formData.barcode = "";
-      }
-    } else {
-      ElMessage.error("鎵弿澶辫触锛�" + (validateRes.message || '鏉$爜楠岃瘉澶辫触'));
+    if (validateRes.status === true && Array.isArray(validateRes.data) && validateRes.data.length) {
+      const newItems = validateRes.data.map(item => ({
+        barcode: item.barcode || '',
+        materielCode: item.materielCode || '',
+        materielName: item.materielName || '',
+        batchNo: item.batchNo || '',
+        orderQuantity: item.orderQuantity || item.quantity || 0,
+        supplyCode: item.supplyCode || '',
+        purchaseOrderNo: purchaseOrderNo || ''
+      }));
+      scannedBarcodes.value.push(...newItems);
+      ElMessage.success(`鎵弿鎴愬姛锛屾柊澧� ${newItems.length} 鏉);
+      playSuccess();
       formData.barcode = "";
-      nextTick(() => barcodeInputRef.value?.focus());
+    } else {
+      ElMessage.error(validateRes.message || '鏉$爜楠岃瘉澶辫触');
+      playError();
+      formData.barcode = "";
     }
   } catch (error) {
-    ElMessage.error(error.message);
+    ElMessage.error('鏉$爜楠岃瘉寮傚父');
+    playError();
     formData.barcode = "";
-    nextTick(() => barcodeInputRef.value?.focus());
   } finally {
     loading.value = false;
-    // 鎵弿瀹屾垚鍚庡缁堣仛鐒︽潯鐮佽緭鍏ユ锛堟柟渚胯繛缁壂鎻忥級
-    nextTick(() => {
-      barcodeInputRef.value?.focus();
-      if (barcodeInputRef.value?.input) {
-        barcodeInputRef.value.input.select = () => {};
-      }
-    });
+    nextTick(() => barcodeInputRef.value?.focus());
   }
 };
 
-// 甯﹂槻鎶栫殑鎵弿澶勭悊
-const debouncedHandleScan = debounce(async (e) => {
-  e.stopPropagation();
-  e.preventDefault();
-  await handleScan();
-}, 100);
+const debounce = (fn, delay = 100) => {
+  let timer = null;
+  return (...args) => { if (timer) clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); };
+};
+const debouncedHandleScan = debounce(async (e) => { e.stopPropagation(); e.preventDefault(); await handleScan(); }, 100);
 
-// 绉婚櫎鍗曟潯璁板綍
+// 鍒犻櫎鏉$爜
 const removeItem = async (index, barcode) => {
   try {
     loading.value = true;
@@ -438,82 +350,82 @@
       inOder: currentItem?.purchaseOrderNo || orderForm.purchaseOrderNo,
       barCode: barcode
     });
-
     if (res.status === true) {
       scannedBarcodes.value.splice(index, 1);
       ElMessage.success("鍒犻櫎鎴愬姛");
-      if (scannedBarcodes.value.length === 0) {
-        orderForm.purchaseOrderNo = "";
-      }
+      if (scannedBarcodes.value.length === 0) orderForm.purchaseOrderNo = "";
     } else {
-      ElMessage.error("鍒犻櫎澶辫触锛�" + (res.message || '鍒犻櫎澶辫触'));
+      ElMessage.error("鍒犻櫎澶辫触锛�" + (res.message || ''));
     }
   } catch (error) {
-    ElMessage.error("鍒犻櫎鏉$爜寮傚父锛�" + error.message);
+    ElMessage.error("鍒犻櫎寮傚父");
   } finally {
     loading.value = false;
-    // 鍒犻櫎鍚庝粛鑱氱劍鏉$爜杈撳叆妗�
     nextTick(() => barcodeInputRef.value?.focus());
   }
 };
 
 // 鎻愪氦鍑哄簱
 const submit = async () => {
-  // 鏍稿績鏂板锛氬墠缃牎楠屽嚭搴撳崟楠岃瘉鐘舵��
   if (!isOutboundVerified.value) {
-    ElMessage.warning("鍑哄簱鍗曟嵁鍙锋湭楠岃瘉锛屾棤娉曟彁浜�");
-    nextTick(() => {
-      outboundInputRef.value?.focus();
-    });
+    ElMessage.warning("鍑哄簱鍗曟嵁鍙锋湭楠岃瘉");
+    nextTick(() => outboundInputRef.value?.focus());
     return;
   }
-
   if (scannedBarcodes.value.length === 0) {
-    ElMessage.warning("璇峰厛鎵弿鑷冲皯涓�鏉℃潯鐮�");
+    ElMessage.warning("璇峰厛鎵弿鏉$爜");
     nextTick(() => barcodeInputRef.value?.focus());
     return;
   }
-
   const barcodes = scannedBarcodes.value.map(item => item.barcode);
   const purchaseOrderNos = [...new Set(scannedBarcodes.value.map(item => item.purchaseOrderNo).filter(Boolean))];
-
   try {
-    // 寮�鍚彁浜oading锛屾樉绀洪伄缃╁眰
     submitLoading.value = true;
     const res = await http.post("/api/OutboundPicking/NoStockOutSubmit", {
       OutOderSubmit: orderForm.outboundOrderNo,
       InOderSubmit: purchaseOrderNos.join(',') || '',
       BarCodeSubmit: barcodes
     });
-
     if (res.status === true) {
       ElMessage.success("鍑哄簱鎻愪氦鎴愬姛");
-      showDetailBox.value = false;
-      scannedBarcodes.value = [];
-      orderForm.purchaseOrderNo = "";
-      isOutboundVerified.value = false; // 鎻愪氦鎴愬姛鍚庨噸缃獙璇佺姸鎬�
+      unlockOutboundOrder();
+      showDetailBox.value = false; // 瑙﹀彂 watch 瑙i攣锛堜絾鎻愪氦鎴愬姛鍚庨�氬父涓嶉渶瑕佽В閿侊紝鍥犱负宸茬粡鍑哄簱浜嗭紝涓嶈繃鍚庣搴旇嚜琛屽鐞嗙姸鎬侊級
+      // 娉ㄦ剰锛氭彁浜ゆ垚鍔熷悗涓嶅簲璇ュ啀璋冪敤瑙i攣鎺ュ彛锛屽洜涓哄崟鎹凡澶勭悊瀹屾瘯銆傝繖閲岄�氳繃鏍囧織閬垮厤锛歴ubmit 鎴愬姛鍚庨噸缃獙璇佺姸鎬�
+      isOutboundVerified.value = false;
+      unlockCalled = true; // 闃叉 watch 鍐嶆璋冪敤瑙i攣
     } else {
-      ElMessage.error("鍑哄簱鎻愪氦澶辫触锛�" + (res.message || '鎻愪氦澶辫触'));
+      unlockOutboundOrder();
+      ElMessage.error("鍑哄簱鎻愪氦澶辫触锛�" + (res.message || ''));
       nextTick(() => barcodeInputRef.value?.focus());
     }
   } catch (error) {
-    ElMessage.error("鍑哄簱鎻愪氦寮傚父锛�" + error.message);
+    unlockOutboundOrder();
+    ElMessage.error("鎻愪氦寮傚父锛�" + error.message);
     nextTick(() => barcodeInputRef.value?.focus());
   } finally {
-    // 鍏抽棴鎻愪氦loading锛岄殣钘忛伄缃╁眰
     submitLoading.value = false;
     loading.value = false;
   }
 };
 
-// 鏆撮湶缁欑埗缁勪欢鐨勬柟娉�
-defineExpose({
-  open
+// 鐢熷懡鍛ㄦ湡锛氭坊鍔犻〉闈㈠叧闂椂鐨勮В閿佺洃鍚�
+onMounted(() => {
+  window.addEventListener('beforeunload', unlockOnPageUnload);
 });
+onBeforeUnmount(() => {
+  window.removeEventListener('beforeunload', unlockOnPageUnload);
+  // 缁勪欢鍗歌浇鏃跺鏋滃脊绐楄繕寮�鐫�锛屼篃灏濊瘯瑙i攣锛堜絾閫氬父椤甸潰鍏抽棴浼氳Е鍙� beforeunload锛�
+  if (showDetailBox.value) {
+    unlockOutboundOrder();
+  }
+});
+
+// 鏆撮湶鏂规硶
+defineExpose({ open });
 </script>
 
 <style scoped>
-/*杩囨浮鍔ㄧ敾 */
+/* 杩囨浮鍔ㄧ敾 */
 .barcode-item-transition-enter-active,
 .barcode-item-transition-leave-active {
   transition: all 0.3s ease;
@@ -530,7 +442,7 @@
   transition: transform 1s ease;
 }
 
-/* 鏂板锛氭彁浜ら伄缃╁眰鏍峰紡 */
+/* 鎻愪氦閬僵灞傛牱寮� */
 .submit-mask {
   position: absolute;
   top: 0;
@@ -557,7 +469,7 @@
   animation: el-loading-circle 1.5s linear infinite;
 }
 
-/* 鏂板锛氶獙璇佺姸鎬佹爣绛炬牱寮� */
+/* 楠岃瘉鐘舵�佹爣绛炬牱寮� */
 .verified-tag {
   color: #67c23a;
   font-size: 12px;
@@ -579,7 +491,6 @@
 .scan-list {
   width: 100%;
 }
-
 .custom-card {
   border-radius: 12px;
   box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important;
@@ -588,7 +499,6 @@
 .custom-card:hover {
   box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12) !important;
 }
-
 .card-header {
   display: flex;
   justify-content: space-between;
@@ -602,11 +512,9 @@
   font-size: 15px;
   color: #333;
 }
-
 .card-body {
   padding: 0;
 }
-
 .custom-scrollbar :deep(.el-scrollbar__thumb) {
   background: rgba(0, 0, 0, 0.2);
   border-radius: 4px;
@@ -619,7 +527,6 @@
 .custom-scrollbar :deep(.el-scrollbar__wrap) {
   overflow-x: hidden;
 }
-
 .barcode-item {
   display: flex;
   justify-content: space-between;
@@ -665,7 +572,6 @@
   flex: 1;
   word-break: break-all;
 }
-
 .delete-btn {
   color: #ea1919;
   font-size: 16px;
@@ -681,7 +587,6 @@
   color: #f56c6c !important;
   background: rgba(245, 108, 108, 0.1);
 }
-
 .empty-tip {
   text-align: center;
   padding: 80px 0;
@@ -697,7 +602,6 @@
   font-size: 40px;
   color: #dcdfe6;
 }
-
 .custom-input :deep(.el-input__inner) {
   border-radius: 6px;
   border-color: #e4e7ed;
@@ -709,7 +613,6 @@
   border-color: #409eff;
   box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
 }
-
 .custom-button {
   border-radius: 6px;
   height: 36px;
@@ -722,7 +625,6 @@
   transform: translateY(-1px);
   box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
 }
-
 .footer-actions {
   text-align: right;
 }

--
Gitblit v1.9.3