| | |
| | | :lazy="true" |
| | | width="65%" |
| | | :padding="20" |
| | | title="æ åºååºåº" |
| | | title="èæåºå
¥åº" |
| | | class="custom-vol-box" |
| | | > |
| | | <div> |
| | | <!-- åæ®è¾å
¥åºåï¼æ¯ææ«ç ï¼ --> |
| | | <el-form :inline="true" :model="orderForm" style="margin-bottom: 20px; align-items: flex-end;"> |
| | | <el-form-item label="åºåºåæ®:" name="outboundOrderNo"> |
| | | <el-input |
| | | v-model="orderForm.outboundOrderNo" |
| | | placeholder="请è¾å
¥ææ«æåºåºåæ®å·" |
| | | clearable |
| | | style="width: 220px; margin-right: 10px;" |
| | | @input="handleOutboundInput" |
| | | @keyup.enter="focusPurchaseInput" |
| | | ref="outboundInputRef" |
| | | ></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="éè´åæ®:" name="purchaseOrderNo"> |
| | | <el-input |
| | | v-model="orderForm.purchaseOrderNo" |
| | | placeholder="请è¾å
¥ææ«æéè´åæ®å·" |
| | | clearable |
| | | style="width: 220px; margin-right: 10px;" |
| | | @input="handlePurchaseInput" |
| | | @keyup.enter="focusBarcodeInput" |
| | | ref="purchaseInputRef" |
| | | ></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- 䏿¹è¾å
¥æ¡ --> |
| | | <el-form :inline="true" :model="formData" ref="formData" style="margin-bottom: 20px; align-items: flex-end;"> |
| | | <el-form :inline="true" :model="formData" ref="formRef" style="margin-bottom: 20px; align-items: flex-end;"> |
| | | <el-form-item |
| | | label="æ«ææ¡ç :" |
| | | style="width: 80%" |
| | | prop="barcode" |
| | | name="barcode" |
| | | :rules="[{ required: true, message: 'è¯·æ«ææè¾å
¥æ¡ç ', trigger: 'blur' }]" |
| | | > |
| | | <el-input |
| | | ref="barcodeInput" |
| | | ref="barcodeInputRef" |
| | | v-model="formData.barcode" |
| | | placeholder="è¯·ä½¿ç¨æ«ç æªæ«ææ¡ç ï¼ææå¨è¾å
¥" |
| | | @keyup.enter="handleScan" |
| | | autofocus |
| | | class="custom-input" |
| | | :disabled="!orderForm.outboundOrderNo || !orderForm.purchaseOrderNo" |
| | | ></el-input> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" size="small" @click="handleScan" class="custom-button"> |
| | | <i class="el-icon-search"></i> ç¡®è®¤æ«æ |
| | | <el-button |
| | | type="primary" |
| | | size="small" |
| | | @click="handleScan" |
| | | class="custom-button" |
| | | :disabled="!orderForm.outboundOrderNo || !orderForm.purchaseOrderNo || loading" |
| | | > |
| | | <Search /> ç¡®è®¤æ«æ |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | <el-card shadow="hover" style="margin-bottom: 10px; border: none;" class="custom-card"> |
| | | <div class="card-header"> |
| | | <span class="header-title">å·²æ«ææ¡ç å表ï¼å
±{{ scannedBarcodes.length }}æ¡ï¼</span> |
| | | <el-button |
| | | class="clear-all-btn" |
| | | @click="clearAll" |
| | | :disabled="scannedBarcodes.length === 0" |
| | | >æ¸
空ææ</el-button> |
| | | </div> |
| | | <div class="card-body"> |
| | | <el-scrollbar height="400px" class="custom-scrollbar"> |
| | | <!-- ä½¿ç¨ transition-group å
裹以å®ç°å¨ç» --> |
| | | <transition-group name="barcode-item-transition"> |
| | | <div class="barcode-item" v-for="(barcode, index) in scannedBarcodes" :key="barcode" :data-index="index"> |
| | | <span class="barcode-text">{{ index + 1 }}. {{ barcode }}</span> |
| | | <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> |
| | | <el-button |
| | | class="delete-btn" |
| | | @click="removeItem(index)" |
| | | >å é¤</el-button> |
| | | @click="removeItem(index, item.barcode)" |
| | | icon="Delete" |
| | | circle |
| | | :disabled="loading" |
| | | ></el-button> |
| | | </div> |
| | | </transition-group> |
| | | <div class="empty-tip" v-if="scannedBarcodes.length === 0"> |
| | | <i class="el-icon-information"></i> |
| | | <span>ææ æ«æè®°å½ï¼è¯·æ«ææ¡ç </span> |
| | | <span>ææ æ«æè®°å½ï¼è¯·å
è¾å
¥åæ®åæ«ææ¡ç </span> |
| | | </div> |
| | | </el-scrollbar> |
| | | </div> |
| | |
| | | |
| | | <template #footer> |
| | | <div class="footer-actions"> |
| | | <el-button type="primary" size="small" @click="submit" :disabled="scannedBarcodes.length === 0" class="submit-btn"> |
| | | <i class="el-icon-check"></i> æäº¤åºåº |
| | | <el-button |
| | | type="primary" |
| | | size="small" |
| | | @click="submit" |
| | | :disabled="scannedBarcodes.length === 0 || !orderForm.outboundOrderNo || !orderForm.purchaseOrderNo || loading" |
| | | class="submit-btn" |
| | | > |
| | | <Check /> æäº¤åºåº |
| | | </el-button> |
| | | <el-button type="text" size="small" @click="showDetailBox = false" class="cancel-btn"> |
| | | <el-button type="text" size="small" @click="showDetailBox = false" class="cancel-btn" :disabled="loading"> |
| | | åæ¶ |
| | | </el-button> |
| | | </div> |
| | |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from 'vue'; |
| | | import { ElMessage } from 'element-plus'; |
| | | import { Search } from '@element-plus/icons-vue'; |
| | | |
| | | import VolBox from "@/components/basic/VolBox.vue"; |
| | | import http from '@/api/http'; |
| | | |
| | | export default { |
| | | components: { VolBox }, |
| | | data() { |
| | | return { |
| | | showDetailBox: false, |
| | | formData: { |
| | | barcode: "", |
| | | }, |
| | | scannedBarcodes: [], |
| | | }; |
| | | }, |
| | | methods: { |
| | | open() { |
| | | this.showDetailBox = true; |
| | | this.scannedBarcodes = []; |
| | | this.formData.barcode = ""; |
| | | this.$nextTick(() => { |
| | | this.$refs.barcodeInput.focus(); |
| | | }); |
| | | }, |
| | | // ååºå¼æ°æ® |
| | | const showDetailBox = ref(false); |
| | | const orderForm = reactive({ |
| | | outboundOrderNo: "", |
| | | purchaseOrderNo: "" |
| | | }); |
| | | const formData = reactive({ |
| | | barcode: "", |
| | | }); |
| | | const scannedBarcodes = ref([]); |
| | | const loading = ref(false); |
| | | |
| | | handleScan() { |
| | | const barcode = this.formData.barcode.trim(); |
| | | if (!barcode) { |
| | | this.$refs.barcodeInput.focus(); |
| | | return; |
| | | } |
| | | if (this.scannedBarcodes.includes(barcode)) { |
| | | this.$message.warning(`æ¡ç ${barcode} å·²æ«æè¿ï¼è¯·å¿é夿«æ`); |
| | | this.formData.barcode = ""; |
| | | this.$refs.barcodeInput.focus(); |
| | | return; |
| | | } |
| | | // 模æ¿å¼ç¨ |
| | | const formRef = ref(null); |
| | | const barcodeInputRef = ref(null); |
| | | const outboundInputRef = ref(null); |
| | | const purchaseInputRef = ref(null); |
| | | |
| | | this.scannedBarcodes.push(barcode); |
| | | this.formData.barcode = ""; |
| | | // ç»ä»¶æè½½æ¶èç¦å°åºåºåè¾å
¥æ¡ |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | outboundInputRef.value?.focus(); |
| | | }); |
| | | }); |
| | | |
| | | this.$nextTick(() => { |
| | | this.$refs.barcodeInput.focus(); |
| | | }); |
| | | }, |
| | | |
| | | removeItem(index) { |
| | | this.scannedBarcodes.splice(index, 1); |
| | | }, |
| | | |
| | | clearAll() { |
| | | this.$confirm("ç¡®å®è¦æ¸
ç©ºæææ«æè®°å½åï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | this.scannedBarcodes = []; |
| | | }).catch(() => { |
| | | this.$refs.barcodeInput.focus(); |
| | | }); |
| | | }, |
| | | |
| | | submit() { |
| | | if (this.scannedBarcodes.length === 0) { |
| | | this.$message.warning("请å
æ«æè³å°ä¸æ¡æ¡ç "); |
| | | this.$refs.barcodeInput.focus(); |
| | | return; |
| | | } |
| | | |
| | | const params = { |
| | | barcodes: this.scannedBarcodes, |
| | | }; |
| | | |
| | | this.http |
| | | .post("/api/OutboundOrder/NoStockOut", params, "æ°æ®å¤çä¸...") |
| | | .then((res) => { |
| | | if (!res.status) { |
| | | this.$message.error(res.message); |
| | | this.$refs.barcodeInput.focus(); |
| | | return; |
| | | } |
| | | this.$message.success("åºåºæå"); |
| | | this.showDetailBox = false; |
| | | this.$emit("parentCall", ($vue) => { |
| | | $vue.refresh(); |
| | | }); |
| | | }) |
| | | .catch((err) => { |
| | | this.$message.error(`请æ±å¤±è´¥ï¼${err.message || "æªç¥é误"}`); |
| | | this.$refs.barcodeInput.focus(); |
| | | }); |
| | | }, |
| | | }, |
| | | // æå¼å¼¹çª |
| | | const open = () => { |
| | | showDetailBox.value = true; |
| | | scannedBarcodes.value = []; |
| | | formData.barcode = ""; |
| | | orderForm.outboundOrderNo = ""; |
| | | orderForm.purchaseOrderNo = ""; |
| | | nextTick(() => { |
| | | outboundInputRef.value?.focus(); |
| | | }); |
| | | }; |
| | | |
| | | // åºåºåè¾å
¥å¤çï¼æ«ç ææå¨è¾å
¥ï¼ |
| | | const handleOutboundInput = (value) => { |
| | | // æ«ç æªè¾å
¥é常ä¼èªå¨è§¦åenteräºä»¶ï¼è¿é主è¦å¤çæå¨è¾å
¥çæ
åµ |
| | | if (value && value.trim()) { |
| | | // å¯ä»¥å¨è¿éæ·»å åºåºåå·çæ ¼å¼éªè¯é»è¾ |
| | | } |
| | | }; |
| | | |
| | | // éè´åè¾å
¥å¤çï¼æ«ç ææå¨è¾å
¥ï¼ |
| | | const handlePurchaseInput = (value) => { |
| | | if (value && value.trim()) { |
| | | // å¯ä»¥å¨è¿éæ·»å éè´åå·çæ ¼å¼éªè¯é»è¾ |
| | | } |
| | | }; |
| | | |
| | | // ç¦ç¹è·³è½¬å½æ° |
| | | const focusPurchaseInput = () => { |
| | | if (orderForm.outboundOrderNo.trim()) { |
| | | purchaseInputRef.value?.focus(); |
| | | } else { |
| | | ElMessage.warning("请å
è¾å
¥ææçåºåºåæ®å·"); |
| | | } |
| | | }; |
| | | |
| | | const focusBarcodeInput = () => { |
| | | if (orderForm.purchaseOrderNo.trim()) { |
| | | barcodeInputRef.value?.focus(); |
| | | } else { |
| | | ElMessage.warning("请å
è¾å
¥ææçéè´åæ®å·"); |
| | | } |
| | | }; |
| | | |
| | | // æ«ææ¡ç å¤ç |
| | | const handleScan = async () => { |
| | | if (!formRef.value) return; |
| | | await formRef.value.validateField('barcode'); |
| | | |
| | | const barcode = formData.barcode.trim(); |
| | | |
| | | if (scannedBarcodes.value.some(item => item.barcode === barcode)) { |
| | | ElMessage.warning(`æ¡ç ${barcode} å·²æ«æè¿ï¼è¯·å¿é夿«æ`); |
| | | formData.barcode = ""; |
| | | nextTick(() => barcodeInputRef.value?.focus()); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | loading.value = true; |
| | | // è¿éä¿çäºåæçæ¡ç éªè¯æ¥å£ï¼ä½ å¯ä»¥æ ¹æ®å®é
éæ±ä¿®æ¹æä¿ç |
| | | const res = await http.post("/api/OutboundPicking/BarcodeValidate", { |
| | | outOder: orderForm.outboundOrderNo, // 注æï¼è¿éç°å¨ä¼ éçæ¯åæ®å·å符串ï¼è䏿¯ID |
| | | inOder: orderForm.purchaseOrderNo, // 注æï¼è¿éç°å¨ä¼ éçæ¯åæ®å·å符串ï¼è䏿¯ID |
| | | barCode: barcode |
| | | }); |
| | | |
| | | if (res.status === true) { |
| | | scannedBarcodes.value.push({ barcode }); |
| | | ElMessage.success("æ«ææå"); |
| | | formData.barcode = ""; |
| | | } else { |
| | | ElMessage.error("æ«æå¤±è´¥ï¼" + (res.message || 'éªè¯å¤±è´¥')); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("æ«æéªè¯å¼å¸¸ï¼" + error.message); |
| | | } finally { |
| | | loading.value = false; |
| | | nextTick(() => barcodeInputRef.value?.focus()); |
| | | } |
| | | }; |
| | | |
| | | // ç§»é¤åæ¡æ«æè®°å½ |
| | | const removeItem = async (index, barcode) => { |
| | | try { |
| | | loading.value = true; |
| | | // è¿éä¿çäºåæçå 餿¡ç æ¥å£ï¼ä½ å¯ä»¥æ ¹æ®å®é
éæ±ä¿®æ¹æä¿ç |
| | | const res = await http.post("/api/OutboundPicking/DeleteBarcode", { |
| | | outOder: orderForm.outboundOrderNo, |
| | | inOder: orderForm.purchaseOrderNo, |
| | | barCode: barcode |
| | | }); |
| | | |
| | | if (res.status === true) { |
| | | scannedBarcodes.value.splice(index, 1); |
| | | ElMessage.success("å 餿å"); |
| | | } else { |
| | | ElMessage.error("å é¤å¤±è´¥ï¼" + (res.message || 'å é¤å¤±è´¥')); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("å 餿¡ç å¼å¸¸ï¼" + error.message); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // æäº¤åºåº |
| | | const submit = async () => { |
| | | if (scannedBarcodes.value.length === 0) { |
| | | ElMessage.warning("请å
æ«æè³å°ä¸æ¡æ¡ç "); |
| | | return; |
| | | } |
| | | |
| | | const barcodes = scannedBarcodes.value.map(item => item.barcode); |
| | | |
| | | try { |
| | | loading.value = true; |
| | | // è¿éä¿çäºåæçæäº¤æ¥å£ï¼æ³¨æåæ°ç°å¨ä¼ éçæ¯åæ®å·å符串 |
| | | const res = await http.post("/api/OutboundPicking/NoStockOutSubmit", { |
| | | OutOderSubmit: orderForm.outboundOrderNo, |
| | | InOderSubmit: orderForm.purchaseOrderNo, |
| | | BarCodeSubmit: barcodes |
| | | }); |
| | | |
| | | if (res.status === true) { |
| | | ElMessage.success("åºåºæäº¤æå"); |
| | | showDetailBox.value = false; |
| | | } else { |
| | | ElMessage.error("åºåºæäº¤å¤±è´¥ï¼" + (res.message || 'æäº¤å¤±è´¥')); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("åºåºæäº¤å¼å¸¸ï¼" + error.message); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // æ´é²ç»ç¶ç»ä»¶çæ¹æ³ |
| | | defineExpose({ |
| | | open |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | font-size: 15px; |
| | | color: #333; |
| | | } |
| | | .clear-all-btn { |
| | | color: #f56c6c; |
| | | font-size: 13px; |
| | | transition: color 0.2s; |
| | | } |
| | | .clear-all-btn:hover { |
| | | color: #e53e3e; |
| | | background: rgba(245, 108, 108, 0.1); |
| | | } |
| | | |
| | | .card-body { |
| | | padding: 0; |
| | | } |
| | | |
| | | /* èªå®ä¹æ»å¨æ¡ */ |
| | | .custom-scrollbar ::v-deep .el-scrollbar__thumb { |
| | | .custom-scrollbar :deep(.el-scrollbar__thumb) { |
| | | background: rgba(0, 0, 0, 0.2); |
| | | border-radius: 4px; |
| | | width: 4px; |
| | | } |
| | | .custom-scrollbar ::v-deep .el-scrollbar__bar:hover .el-scrollbar__thumb { |
| | | .custom-scrollbar :deep(.el-scrollbar__bar:hover .el-scrollbar__thumb) { |
| | | background: rgba(0, 0, 0, 0.3); |
| | | width: 6px; |
| | | } |
| | | .custom-scrollbar ::v-deep .el-scrollbar__wrap { |
| | | .custom-scrollbar :deep(.el-scrollbar__wrap) { |
| | | overflow-x: hidden; |
| | | } |
| | | |
| | |
| | | } |
| | | /* ä¸ºå¥æ°è¡æ·»å 轻微çèæ¯è²ï¼å¢å¼ºå¯è¯»æ§ */ |
| | | .barcode-item:nth-child(odd) { |
| | | background-color: #e1e1e1; |
| | | background-color: #f9f9f9; |
| | | } |
| | | .barcode-text { |
| | | flex: 1; |
| | |
| | | |
| | | .delete-btn { |
| | | color: #ea1919; |
| | | font-size: 20px; |
| | | font-size: 16px; |
| | | transition: all 0.2s; |
| | | transform: scale(0.8); |
| | | opacity: 0.7; |
| | | } |
| | | .barcode-item:hover .delete-btn { |
| | | opacity: 1; |
| | | transform: scale(1); |
| | | } |
| | | .delete-btn:hover { |
| | | color: #f56c6c !important; /* ä½¿ç¨ !important è¦ç Element UI é»è®¤æ ·å¼ */ |
| | | color: #f56c6c !important; |
| | | background: rgba(245, 108, 108, 0.1); |
| | | } |
| | | |
| | |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 15px; |
| | | } |
| | | .empty-tip i { |
| | | font-size: 40px; |
| | | margin-bottom: 15px; |
| | | color: #dcdfe6; |
| | | } |
| | | |
| | | /* èªå®ä¹è¾å
¥æ¡ */ |
| | | .custom-input ::v-deep .el-input__inner { |
| | | .custom-input :deep(.el-input__inner) { |
| | | border-radius: 6px; |
| | | border-color: #e4e7ed; |
| | | transition: all 0.3s; |
| | | height: 36px; |
| | | line-height: 36px; |
| | | } |
| | | .custom-input ::v-deep .el-input__inner:focus { |
| | | .custom-input :deep(.el-input__inner:focus) { |
| | | border-color: #409eff; |
| | | box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1); |
| | | } |
| | |
| | | </style> |
| | | |
| | | <style> |
| | | /* ... (å
¨å±æ ·å¼é¨åä¿æä¸å) ... */ |
| | | /* å
¨å±æ ·å¼é¨åä¿æä¸å */ |
| | | .text-button:hover { |
| | | background-color: #f0f9eb !important; |
| | | } |