| | |
| | | title="èæåºå
¥åº" |
| | | class="custom-vol-box" |
| | | > |
| | | <!-- æäº¤é®ç½©å±ï¼è¦çæ´ä¸ªå¼¹çªå
容åºå --> |
| | | <div class="submit-mask" v-show="submitLoading"> |
| | | <div class="mask-content"> |
| | | <el-loading-spinner class="loading-icon" /> |
| | | <span class="loading-text">æ£å¨æäº¤åºåºï¼è¯·ç¨å...</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div> |
| | | <!-- åæ®è¾å
¥åºåï¼æ¯ææ«ç ï¼ --> |
| | | <el-form |
| | |
| | | clearable |
| | | style="width: 220px; margin-right: 10px;" |
| | | @input="handleOutboundInput" |
| | | @keyup.enter="(e) => { |
| | | e.stopPropagation(); // 黿¢äºä»¶å泡 |
| | | e.preventDefault(); // 黿¢é»è®¤è¡ä¸º |
| | | focusBarcodeInputDirectly(); |
| | | }" |
| | | @keyup.enter="validateOutboundOrder" |
| | | 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> |
| | | <el-form-item label="éè´åæ®:" name="purchaseOrderNo"> |
| | | <el-input |
| | |
| | | @input="handlePurchaseInput" |
| | | readonly |
| | | ref="purchaseInputRef" |
| | | :disabled="submitLoading" |
| | | ></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | label="æ«ææ¡ç :" |
| | | style="width: 80%" |
| | | name="barcode" |
| | | :rules="[{ required: true, message: 'è¯·æ«ææè¾å
¥æ¡ç ', trigger: 'blur' }]" |
| | | > |
| | | <el-input |
| | | ref="barcodeInputRef" |
| | |
| | | @keydown.enter="debouncedHandleScan" |
| | | autofocus |
| | | class="custom-input" |
| | | :disabled="!orderForm.outboundOrderNo || loading" |
| | | :disabled="!isOutboundVerified || loading || submitLoading" |
| | | ></el-input> |
| | | </el-form-item> |
| | | <el-form-item> |
| | |
| | | size="small" |
| | | @click="handleScan" |
| | | class="custom-button" |
| | | :disabled="!orderForm.outboundOrderNo || loading" |
| | | :disabled="!isOutboundVerified || loading || submitLoading" |
| | | > |
| | | <Search /> ç¡®è®¤æ«æ |
| | | </el-button> |
| | |
| | | <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" :data-index="index"> |
| | | <span class="barcode-text">{{ index + 1 }}. {{ item.barcode }}</span> |
| | | <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> |
| | | <el-button |
| | | class="delete-btn" |
| | | @click="removeItem(index, item.barcode)" |
| | | icon="Delete" |
| | | circle |
| | | :disabled="loading" |
| | | :disabled="loading || submitLoading" |
| | | ></el-button> |
| | | </div> |
| | | </transition-group> |
| | | <div class="empty-tip" v-if="scannedBarcodes.length === 0"> |
| | | <span>ææ æ«æè®°å½ï¼è¯·å
è¾å
¥åºåºåæ®åæ«ææ¡ç </span> |
| | | <span v-if="isOutboundVerified">ææ æ«æè®°å½ï¼è¯·æ«ææ¡ç </span> |
| | | <span v-else>请å
è¾å
¥å¹¶éªè¯ææçåºåºåæ®å·</span> |
| | | </div> |
| | | </el-scrollbar> |
| | | </div> |
| | |
| | | type="primary" |
| | | size="small" |
| | | @click="submit" |
| | | :disabled="scannedBarcodes.length === 0 || !orderForm.outboundOrderNo || !orderForm.purchaseOrderNo || loading" |
| | | :disabled="scannedBarcodes.length === 0 || !isOutboundVerified || loading || submitLoading" |
| | | class="submit-btn" |
| | | > |
| | | <Check /> æäº¤åºåº |
| | |
| | | <el-button |
| | | type="text" |
| | | size="small" |
| | | @click="(e) => { |
| | | e.stopPropagation(); |
| | | e.preventDefault(); |
| | | showDetailBox = false; |
| | | }" |
| | | @click="handleCancel" |
| | | class="cancel-btn" |
| | | :disabled="loading" |
| | | :disabled="loading || submitLoading" |
| | | > |
| | | åæ¶ |
| | | </el-button> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick, watch } 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); |
| | | // æ°å¢ï¼åå¨é¦æ¬¡æ«æçéè´åå·ï¼ç¨äºä¸è´æ§æ ¡éªï¼ |
| | | const firstPurchaseOrderNo = ref(null); |
| | | const submitLoading = ref(false); |
| | | const isOutboundVerified = ref(false); |
| | | let unlockCalled = false; // 鲿¢éå¤è§£é |
| | | |
| | | // 模æ¿å¼ç¨ |
| | | const formRef = ref(null); |
| | |
| | | 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); |
| | | |
| | | // ç嬿«æå表ï¼è¥ä¸ºç©ºåéç½®é¦æ¬¡éè´åå· |
| | | watch(scannedBarcodes, (newVal) => { |
| | | if (newVal.length === 0) { |
| | | firstPurchaseOrderNo.value = null; |
| | | orderForm.purchaseOrderNo = ""; // 忥æ¸
空éè´åè¾å
¥æ¡ |
| | | // ========== è§£éæ ¸å¿é»è¾ï¼ä½¿ç¨æ°æ¥å£ 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, "è§£éåºåºåä¸...").catch(() => {}); |
| | | } catch (error) { |
| | | // éé»å¤±è´¥ |
| | | } |
| | | }, { deep: true }); |
| | | |
| | | // ç®å鲿彿°ï¼æ éä¾èµlodashï¼ |
| | | const debounce = (fn, delay = 100) => { |
| | | let timer = null; |
| | | return (...args) => { |
| | | if (timer) clearTimeout(timer); |
| | | timer = setTimeout(() => { |
| | | fn.apply(this, args); |
| | | }, delay); |
| | | }; |
| | | }; |
| | | |
| | | // æå¼å¼¹çª |
| | | // 页é¢å
³é/å·æ°æ¶çåæ¥è§£éï¼ä½¿ç¨ 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(() => {}); |
| | | }; |
| | | |
| | | // çå¬å¼¹çªå
³éï¼ä»»ä½æ¹å¼ï¼åå·ãESCãé®ç½©ãåæ¶æé®çï¼ |
| | | 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 = ""; |
| | | // éç½®é¦æ¬¡éè´åå· |
| | | firstPurchaseOrderNo.value = null; |
| | | nextTick(() => { |
| | | outboundInputRef.value?.focus(); |
| | | }); |
| | | submitLoading.value = false; |
| | | isOutboundVerified.value = false; |
| | | nextTick(() => outboundInputRef.value?.focus()); |
| | | }; |
| | | |
| | | // åºåºåè¾å
¥å¤çï¼æ«ç ææå¨è¾å
¥ï¼ |
| | | const handleOutboundInput = (value) => { |
| | | if (value && value.trim()) { |
| | | // åºåºåå·æ ¼å¼éªè¯é»è¾ï¼æéä¿çï¼ |
| | | } |
| | | }; |
| | | |
| | | // éè´åè¾å
¥å¤çï¼æ«ç ææå¨è¾å
¥ï¼ |
| | | const handlePurchaseInput = (value) => { |
| | | if (value && value.trim()) { |
| | | // éè´åå·æ ¼å¼éªè¯é»è¾ï¼æéä¿çï¼ |
| | | } |
| | | }; |
| | | |
| | | // ç´æ¥è·³è½¬å°æ¡ç è¾å
¥æ¡ï¼æ éå
å¡«éè´åï¼ |
| | | const focusBarcodeInputDirectly = () => { |
| | | if (orderForm.outboundOrderNo.trim()) { |
| | | barcodeInputRef.value?.focus(); |
| | | } else { |
| | | ElMessage.warning("请å
è¾å
¥ææçåºåºåæ®å·"); |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * æ ¹æ®æ¡ç æ¥è¯¢éè´åæ¥å£ï¼å®å
¨å¯¹é½ç¤ºä¾è¯·æ±æ ¼å¼ï¼ |
| | | * @param {string} barcode æ¡ç |
| | | * @returns {Promise<string>} éè´åå· |
| | | */ |
| | | const getPurchaseOrderByBarcode = async (barcode) => { |
| | | // å®å
¨æç
§ç¤ºä¾æ ¼å¼ï¼urlæ¼æ¥åæ° + 第äºä¸ªåæ°ä¼ æç¤ºææ¬ |
| | | const res = await http.post(`/api/OutboundPicking/GetPurchaseOrderByBarcode?barCode=${encodeURIComponent(barcode)}`, "æ¥è¯¢éè´åä¸..."); |
| | | |
| | | if (res.status !== true) { |
| | | throw new Error(res.message || "æ¥è¯¢éè´å失败"); |
| | | } |
| | | if (!res.data?.purchaseOrderNo) { |
| | | throw new Error("æªæ¥è¯¢å°è¯¥æ¡ç 对åºçéè´å"); |
| | | } |
| | | return res.data.purchaseOrderNo; |
| | | }; |
| | | |
| | | // æ«ææ¡ç æ ¸å¿é»è¾ |
| | | const handleScan = async () => { |
| | | if (!formRef.value) return; |
| | | // éªè¯æ¡ç å¿
å¡« |
| | | await formRef.value.validateField('barcode'); |
| | | |
| | | const barcode = formData.barcode.trim(); |
| | | // éªè¯åºåºåå·ï¼ä»ä½¿ç¨åæ¥å£ï¼æ¤æ¥å£ç¨äºéªè¯åæ®æææ§ï¼ |
| | | const validateOutboundOrder = async () => { |
| | | const outboundOrderNo = orderForm.outboundOrderNo.trim(); |
| | | if (!outboundOrderNo) { ElMessage.warning("请è¾å
¥åºåºåæ®å·"); return; } |
| | | try { |
| | | loading.value = true; |
| | | const res = await http.post(`/api/OutboundPicking/GetAvailablePickingOrders?outOrder=${outboundOrderNo}`, "éªè¯åºåºåæ®å·ä¸..."); |
| | | if (res.status !== true) { |
| | | orderForm.outboundOrderNo = ""; |
| | | isOutboundVerified.value = false; |
| | | ElMessage.error(res.message || "åºåºåæ®å·éªè¯å¤±è´¥"); |
| | | nextTick(() => outboundInputRef.value?.focus()); |
| | | return; |
| | | } |
| | | isOutboundVerified.value = true; |
| | | ElMessage.success("åºåºåæ®å·éªè¯éè¿"); |
| | | nextTick(() => barcodeInputRef.value?.focus()); |
| | | } catch (error) { |
| | | orderForm.outboundOrderNo = ""; |
| | | isOutboundVerified.value = false; |
| | | ElMessage.error(`éªè¯å¼å¸¸ï¼${error.message || "ç½ç»é误"}`); |
| | | nextTick(() => outboundInputRef.value?.focus()); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // æ£æ¥æ¡ç æ¯å¦å·²æ«æ |
| | | if (scannedBarcodes.value.some(item => item.barcode === barcode)) { |
| | | ElMessage.warning(`æ¡ç ${barcode} å·²æ«æè¿ï¼è¯·å¿é夿«æ`); |
| | | const handleOutboundInput = (val) => { if (val?.trim()) isOutboundVerified.value = false; }; |
| | | const handlePurchaseInput = (val) => {}; |
| | | |
| | | // è·åéè´åå· |
| | | const getPurchaseOrderByBarcode = async (barcode) => { |
| | | const res = await http.post(`/api/OutboundPicking/GetPurchaseOrderByBarcode?barCode=${encodeURIComponent(barcode)}`, "æ¥è¯¢éè´åå·ä¸..."); |
| | | 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; |
| | | return purchaseOrderNo; |
| | | }; |
| | | |
| | | // æ«ææ¡ç |
| | | const handleScan = async () => { |
| | | if (!isOutboundVerified.value) { |
| | | ElMessage.warning("请å
éªè¯åºåºåæ®å·"); |
| | | playError(); |
| | | nextTick(() => outboundInputRef.value?.focus()); |
| | | return; |
| | | } |
| | | const barcode = formData.barcode.trim(); |
| | | if (!barcode) return; |
| | | const isDuplicate = scannedBarcodes.value.some(item => item.barcode === barcode); |
| | | if (isDuplicate) { |
| | | ElMessage.warning(`æ¡ç ã${barcode}ãå·²åå¨`); |
| | | playError(); |
| | | formData.barcode = ""; |
| | | nextTick(() => barcodeInputRef.value?.focus()); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | loading.value = true; |
| | | |
| | | // æ¥éª¤1ï¼ä»
ä¼ æ¡ç æ¥è¯¢éè´åï¼ä½¿ç¨ç¤ºä¾æ ¼å¼ç请æ±ï¼ |
| | | const purchaseOrderNo = await getPurchaseOrderByBarcode(barcode); |
| | | |
| | | // æ ¸å¿æ ¡éªï¼éè´åä¸è´æ§æ£æ¥ |
| | | if (firstPurchaseOrderNo.value) { |
| | | // é馿¬¡æ«æï¼æ ¡éªéè´åå·æ¯å¦ä¸è´ |
| | | if (purchaseOrderNo !== firstPurchaseOrderNo.value) { |
| | | throw new Error(`å½åæ¡ç 对åºçéè´åã${purchaseOrderNo}ãä¸é¦æ¬¡æ«æçéè´åã${firstPurchaseOrderNo.value}ãä¸ä¸è´ï¼ç¦æ¢æ«æï¼`); |
| | | } |
| | | } else { |
| | | // 馿¬¡æ«æï¼è®°å½éè´åå· |
| | | firstPurchaseOrderNo.value = purchaseOrderNo; |
| | | } |
| | | |
| | | // èµå¼éè´åå°è¾å
¥æ¡ |
| | | orderForm.purchaseOrderNo = purchaseOrderNo; |
| | | ElMessage.success(`æåæ¥è¯¢å°éè´åï¼${purchaseOrderNo}`); |
| | | |
| | | // æ¥éª¤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, |
| | | inOder: purchaseOrderNo, |
| | | outOder: orderForm.outboundOrderNo, |
| | | inOder: purchaseOrderNo || orderForm.purchaseOrderNo, |
| | | barCode: barcode |
| | | }); |
| | | |
| | | if (validateRes.status === true) { |
| | | scannedBarcodes.value.push({ barcode }); |
| | | ElMessage.success("æ«ææå"); |
| | | 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 = ""; |
| | | } else { |
| | | ElMessage.error("æ«æå¤±è´¥ï¼" + (validateRes.message || 'æ¡ç éªè¯å¤±è´¥')); |
| | | 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(() => { |
| | | if (barcodeInputRef.value) { |
| | | 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; |
| | | const currentItem = scannedBarcodes.value[index]; |
| | | const res = await http.post("/api/OutboundPicking/DeleteBarcode", { |
| | | outOder: orderForm.outboundOrderNo, |
| | | inOder: orderForm.purchaseOrderNo, |
| | | inOder: currentItem?.purchaseOrderNo || orderForm.purchaseOrderNo, |
| | | barCode: barcode |
| | | }); |
| | | |
| | | if (res.status === true) { |
| | | scannedBarcodes.value.splice(index, 1); |
| | | ElMessage.success("å 餿å"); |
| | | // è¥å é¤åæ æ¡ç ï¼èªå¨éç½®é¦æ¬¡éè´åå·åéè´åè¾å
¥æ¡ |
| | | if (scannedBarcodes.value.length === 0) { |
| | | firstPurchaseOrderNo.value = null; |
| | | 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 (scannedBarcodes.value.length === 0) { |
| | | ElMessage.warning("请å
æ«æè³å°ä¸æ¡æ¡ç "); |
| | | if (!isOutboundVerified.value) { |
| | | ElMessage.warning("åºåºåæ®å·æªéªè¯"); |
| | | nextTick(() => outboundInputRef.value?.focus()); |
| | | return; |
| | | } |
| | | |
| | | if (scannedBarcodes.value.length === 0) { |
| | | 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 { |
| | | loading.value = true; |
| | | submitLoading.value = true; |
| | | const res = await http.post("/api/OutboundPicking/NoStockOutSubmit", { |
| | | OutOderSubmit: orderForm.outboundOrderNo, |
| | | InOderSubmit: orderForm.purchaseOrderNo, |
| | | InOderSubmit: purchaseOrderNos.join(',') || '', |
| | | BarCodeSubmit: barcodes |
| | | }); |
| | | |
| | | if (res.status === true) { |
| | | ElMessage.success("åºåºæäº¤æå"); |
| | | showDetailBox.value = false; |
| | | // æäº¤æååéç½®ç¶æ |
| | | firstPurchaseOrderNo.value = null; |
| | | scannedBarcodes.value = []; |
| | | unlockOutboundOrder(); |
| | | showDetailBox.value = false; // 触å watch è§£éï¼ä½æäº¤æååé常ä¸éè¦è§£éï¼å 为已ç»åºåºäºï¼ä¸è¿å端åºèªè¡å¤çç¶æï¼ |
| | | // 注æï¼æäº¤æååä¸åºè¯¥åè°ç¨è§£éæ¥å£ï¼å ä¸ºåæ®å·²å¤ç宿¯ãè¿ééè¿æ å¿é¿å
ï¼submit æååéç½®éªè¯ç¶æ |
| | | isOutboundVerified.value = false; |
| | | unlockCalled = true; // 鲿¢ watch 忬¡è°ç¨è§£é |
| | | } 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 { |
| | | submitLoading.value = false; |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // æ´é²ç»ç¶ç»ä»¶çæ¹æ³ |
| | | defineExpose({ |
| | | open |
| | | // çå½å¨æï¼æ·»å 页é¢å
³éæ¶çè§£éçå¬ |
| | | onMounted(() => { |
| | | window.addEventListener('beforeunload', unlockOnPageUnload); |
| | | }); |
| | | onBeforeUnmount(() => { |
| | | window.removeEventListener('beforeunload', unlockOnPageUnload); |
| | | // ç»ä»¶å¸è½½æ¶å¦æå¼¹çªè¿å¼çï¼ä¹å°è¯è§£éï¼ä½é常页é¢å
³éä¼è§¦å 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; |
| | |
| | | opacity: 0; |
| | | transform: translateX(30px); |
| | | } |
| | | /* ç¡®ä¿å 餿¶å
¶ä»å
ç´ å¹³æ»ä¸ç§» */ |
| | | .barcode-item-transition-move { |
| | | transition: transform 1s ease; |
| | | } |
| | | |
| | | /* æäº¤é®ç½©å±æ ·å¼ */ |
| | | .submit-mask { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | background-color: rgba(255, 255, 255, 0.85); |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | z-index: 100; |
| | | border-radius: inherit; |
| | | } |
| | | .mask-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 12px; |
| | | color: #409eff; |
| | | font-size: 15px; |
| | | } |
| | | .loading-icon { |
| | | font-size: 24px; |
| | | animation: el-loading-circle 1.5s linear infinite; |
| | | } |
| | | |
| | | /* éªè¯ç¶ææ ç¾æ ·å¼ */ |
| | | .verified-tag { |
| | | color: #67c23a; |
| | | font-size: 12px; |
| | | margin-left: 8px; |
| | | font-weight: 500; |
| | | } |
| | | .loading-tag { |
| | | color: #409eff; |
| | | font-size: 12px; |
| | | margin-left: 8px; |
| | | font-weight: 500; |
| | | animation: spin 1s linear infinite; |
| | | } |
| | | @keyframes spin { |
| | | 0% { transform: rotate(0deg); } |
| | | 100% { transform: rotate(360deg); } |
| | | } |
| | | |
| | | .scan-list { |
| | | width: 100%; |
| | | } |
| | | |
| | | .custom-card { |
| | | border-radius: 12px; |
| | | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important; |
| | |
| | | .custom-card:hover { |
| | | box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12) !important; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | |
| | | 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; |
| | |
| | | .custom-scrollbar :deep(.el-scrollbar__wrap) { |
| | | overflow-x: hidden; |
| | | } |
| | | |
| | | .barcode-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 10px 15px; |
| | | align-items: flex-start; |
| | | padding: 15px; |
| | | border-bottom: 1px solid #f7f7f7; |
| | | transition: background-color 0.2s ease; |
| | | } |
| | | .barcode-item:hover { |
| | | background-color: #fafafa; |
| | | } |
| | | /* ä¸ºå¥æ°è¡æ·»å 轻微çèæ¯è²ï¼å¢å¼ºå¯è¯»æ§ */ |
| | | .barcode-item:nth-child(odd) { |
| | | background-color: #f9f9f9; |
| | | } |
| | | .barcode-text { |
| | | .barcode-detail { |
| | | flex: 1; |
| | | display: grid; |
| | | grid-template-columns: repeat(4, 1fr); |
| | | gap: 8px 15px; |
| | | font-size: 14px; |
| | | } |
| | | @media (max-width: 1200px) { |
| | | .barcode-detail { |
| | | grid-template-columns: repeat(3, 1fr); |
| | | } |
| | | } |
| | | @media (max-width: 992px) { |
| | | .barcode-detail { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | } |
| | | .detail-row { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | .label { |
| | | color: #999; |
| | | margin-right: 5px; |
| | | white-space: nowrap; |
| | | } |
| | | .value { |
| | | color: #666; |
| | | transition: color 0.2s; |
| | | flex: 1; |
| | | word-break: break-all; |
| | | } |
| | | .barcode-item:hover .barcode-text { |
| | | color: #409eff; |
| | | } |
| | | |
| | | .delete-btn { |
| | | color: #ea1919; |
| | | font-size: 16px; |
| | | transition: all 0.2s; |
| | | opacity: 0.7; |
| | | margin-left: 10px; |
| | | flex-shrink: 0; |
| | | } |
| | | .barcode-item:hover .delete-btn { |
| | | opacity: 1; |
| | |
| | | color: #f56c6c !important; |
| | | background: rgba(245, 108, 108, 0.1); |
| | | } |
| | | |
| | | .empty-tip { |
| | | text-align: center; |
| | | padding: 80px 0; |
| | |
| | | font-size: 40px; |
| | | color: #dcdfe6; |
| | | } |
| | | |
| | | /* èªå®ä¹è¾å
¥æ¡ */ |
| | | .custom-input :deep(.el-input__inner) { |
| | | border-radius: 6px; |
| | | border-color: #e4e7ed; |
| | |
| | | border-color: #409eff; |
| | | box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1); |
| | | } |
| | | |
| | | /* èªå®ä¹æé® */ |
| | | .custom-button { |
| | | border-radius: 6px; |
| | | height: 36px; |
| | |
| | | transform: translateY(-1px); |
| | | box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2); |
| | | } |
| | | |
| | | .footer-actions { |
| | | text-align: right; |
| | | } |
| | |
| | | </style> |
| | | |
| | | <style> |
| | | /* å
¨å±æ ·å¼é¨åä¿æä¸å */ |
| | | .text-button:hover { |
| | | background-color: #f0f9eb !important; |
| | | } |