| | |
| | | <div class="page-header"> |
| | | <el-page-header @back="goBack"> |
| | | <template #content> |
| | | <span class="title">åºåºæ£é确认 - {{ orderInfo.orderNo }}</span> |
| | | <span class="title">åºåºæ£é确认 - {{ this.$route.query.orderNo }}</span> |
| | | </template> |
| | | </el-page-header> |
| | | </div> |
| | | |
| | | <el-row :gutter="20" class="main-content"> |
| | | <el-col :span="8"> |
| | | |
| | | <div class="content-layout"> |
| | | <!-- å·¦ä¾§ï¼æ«ç åºå --> |
| | | <div class="left-section"> |
| | | <div class="scan-section"> |
| | | <el-card header="æ«ç åºå"> |
| | | <el-form label-width="100px" size="small"> |
| | | <el-form-item label="æçæ¡ç "> |
| | | <el-input |
| | | v-model="scanForm.palletCode" |
| | | placeholder="æ«ææè¾å
¥æçæ¡ç " |
| | | @keyup.enter="handlePalletScan" |
| | | clearable |
| | | > |
| | | <template #append> |
| | | <el-button @click="handlePalletScan">确认</el-button> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | <el-alert |
| | | title="è¯·ä½¿ç¨æ«ç æªæ«ææçç åç©ææ¡ç ï¼æ«ç æªå¸¦å车åè½ï¼æ«å®ç©ææ¡ç èªå¨ç¡®è®¤" |
| | | type="info" |
| | | :closable="false" |
| | | class="scan-alert" |
| | | /> |
| | | |
| | | <el-form-item label="ç©ææ¡ç "> |
| | | <el-input |
| | | v-model="scanForm.barcode" |
| | | placeholder="æ«ææè¾å
¥ç©ææ¡ç " |
| | | @keyup.enter="handleBarcodeScan" |
| | | :disabled="!currentPallet" |
| | | clearable |
| | | > |
| | | <template #append> |
| | | <el-button @click="handleBarcodeScan" :disabled="!currentPallet">确认</el-button> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | <el-form :model="scanForm" label-width="100px" class="scan-form"> |
| | | <el-form-item label="æçç " required> |
| | | <el-input |
| | | ref="palletInput" |
| | | v-model="scanForm.palletCode" |
| | | placeholder="è¯·æ«ææçç " |
| | | @keyup.enter="handlePalletScan" |
| | | @blur="loadPalletSummary" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æ£éæ°é"> |
| | | <el-input-number |
| | | v-model="scanForm.quantity" |
| | | :min="1" |
| | | :max="maxPickQuantity" |
| | | :disabled="!currentLockInfo" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ç©ææ¡ç " required> |
| | | <el-input |
| | | ref="materialInput" |
| | | v-model="scanForm.materialBarcode" |
| | | placeholder="è¯·æ«æç©ææ¡ç " |
| | | :disabled="!scanForm.palletCode" |
| | | @keyup.enter="handleMaterialScan" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- ç©æä¿¡æ¯æ¾ç¤º --> |
| | | <el-form-item label="ç©æç¼ç " v-if="currentMaterialInfo"> |
| | | <el-input v-model="currentMaterialInfo.materielCode" readonly /> |
| | | </el-form-item> |
| | | <el-form-item label="ç©æåç§°" v-if="currentMaterialInfo"> |
| | | <el-input v-model="currentMaterialInfo.materielName" readonly /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="current-info" v-if="currentPallet"> |
| | | <p>å½åæç: {{ currentPallet.palletCode }}</p> |
| | | <p>è´§ä½: {{ currentPallet.locationCode }}</p> |
| | | <p>ç¶æ: {{ currentPallet.statusText }}</p> |
| | | </div> |
| | | </el-card> |
| | | <!-- æçæ£è´§ç»è®¡ --> |
| | | <div v-if="palletSummary" class="pallet-summary"> |
| | | <el-card header="æçæ£è´§ç»è®¡"> |
| | | <el-descriptions :column="3" border> |
| | | <el-descriptions-item label="æçå·"> |
| | | {{ scanForm.palletCode }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æªæ£è´§æ¡æ°"> |
| | | <el-text type="warning">{{ palletSummary.unpickedCount }}</el-text> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æªæ£è´§æ»æ°"> |
| | | <el-text type="danger">{{ palletSummary.unpickedTotal }}</el-text> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <div class="action-buttons"> |
| | | <el-button |
| | | type="warning" |
| | | @click="handleBackToStock" |
| | | :disabled="!currentPallet" |
| | | style="margin-bottom: 10px;" |
| | | > |
| | | ååº |
| | | <el-button type="primary" @click="handleConfirm" :loading="confirmLoading"> |
| | | æå¨ç¡®è®¤ |
| | | </el-button> |
| | | <el-button |
| | | type="success" |
| | | @click="handleDirectOutbound" |
| | | :disabled="!currentPallet" |
| | | style="margin-bottom: 10px;" |
| | | > |
| | | ç´æ¥åºåº |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | @click="handleOpenSplit" |
| | | :disabled="!currentLockInfo" |
| | | > |
| | | æå
|
| | | </el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | | <el-button @click="$emit('close')">åæ¶</el-button> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </div> |
| | | |
| | | <el-col :span="16"> |
| | | <el-card header="æ£éç»æ"> |
| | | <div class="summary-info"> |
| | | <el-alert |
| | | :title="`æªæ£è´§: ${unpickedCount} æ¡, ${unpickedQuantity} 个`" |
| | | type="warning" |
| | | :closable="false" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- å³ä¾§ï¼åºåºè¯¦æ
å表 --> |
| | | <div class="right-section"> |
| | | <el-card class="outbound-details-card" header="åºåºè¯¦æ
"> |
| | | <vol-table |
| | | :data="pickedList" |
| | | :columns="pickedColumns" |
| | | :pagination="false" |
| | | :height="400" |
| | | > |
| | | <template #action="{ row }"> |
| | | <el-button type="text" @click="handleCancelPick(row)">æ¤é</el-button> |
| | | </template> |
| | | </vol-table> |
| | | ref="outboundTable" |
| | | :table-config="outboundTableConfig" |
| | | :height="300" |
| | | /> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æå
å¼¹çª --> |
| | | <vol-box |
| | | v-model="splitVisible" |
| | | title="æå
æä½" |
| | | :width="600" |
| | | :height="500" |
| | | > |
| | | <SplitPackageModal |
| | | v-if="splitVisible" |
| | | :lockInfo="currentLockInfo" |
| | | @success="handleSplitSuccess" |
| | | @close="splitVisible = false" |
| | | /> |
| | | </vol-box> |
| | | <!-- å·²åæ£è®°å½å表 --> |
| | | <div class="picked-records"> |
| | | <el-card header="å·²åæ£è®°å½"> |
| | | <vol-table |
| | | ref="pickedTable" |
| | | :table-config="pickedTableConfig" |
| | | :height="300" |
| | | /> |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import SplitPackageModal from './SplitPackageModal.vue' |
| | | import http from '@/api/http.js' |
| | | import { ref, defineComponent } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { useRoute } from 'vue-router' |
| | | |
| | | export default { |
| | | components: { SplitPackageModal }, |
| | | export default defineComponent({ |
| | | name: 'PickingConfirm', |
| | | components: { |
| | | |
| | | }, |
| | | props: { |
| | | orderNo: { |
| | | type: String, |
| | | required: true |
| | | } |
| | | }, |
| | | emits: ['confirm', 'close'], |
| | | data() { |
| | | return { |
| | | orderInfo: {}, |
| | | scanForm: { |
| | | palletCode: '', |
| | | barcode: '', |
| | | quantity: 1 |
| | | materialBarcode: '' |
| | | }, |
| | | currentPallet: null, |
| | | currentLockInfo: null, |
| | | currentMaterialInfo: null, // æ°å¢ï¼å½åç©æä¿¡æ¯ |
| | | pickedList: [], |
| | | pickedColumns: [ |
| | | { field: 'barcode', title: 'ç©ææ¡ç ', width: 150 }, |
| | | { field: 'materielCode', title: 'ç©æç¼ç ', width: 120 }, |
| | | { field: 'materielName', title: 'ç©æåç§°', width: 150 }, |
| | | { field: 'pickQuantity', title: 'æ£éæ°é', width: 100 }, |
| | | { field: 'palletCode', title: 'æçç¼å·', width: 120 }, |
| | | { field: 'pickTime', title: 'æ£éæ¶é´', width: 160 }, |
| | | { field: 'operator', title: 'æä½äºº', width: 100 }, |
| | | { field: 'action', title: 'æä½', width: 80, slot: true } |
| | | ], |
| | | splitVisible: false, |
| | | maxPickQuantity: 0, |
| | | allLockInfos: [] // æ°å¢ï¼ä¿åææéå®ä¿¡æ¯ï¼ç¨äºæ¡ç å¹é
|
| | | } |
| | | }, |
| | | computed: { |
| | | unpickedCount() { |
| | | return this.orderInfo.unpickedCount || 0 |
| | | }, |
| | | unpickedQuantity() { |
| | | return this.orderInfo.unpickedQuantity || 0 |
| | | } |
| | | }, |
| | | methods: { |
| | | goBack() { |
| | | this.$router.back() |
| | | }, |
| | | |
| | | async loadOrderInfo() { |
| | | const orderId = this.$route.query.orderId |
| | | if (!orderId) return |
| | | |
| | | try { |
| | | const result = await this.http.get(`api/OutboundOrder/GetById?id=${orderId}`) |
| | | if (result.status) { |
| | | this.orderInfo = result.data |
| | | } |
| | | } catch (error) { |
| | | this.$message.error('å è½½åºåºåä¿¡æ¯å¤±è´¥') |
| | | } |
| | | }, |
| | | |
| | | async handlePalletScan() { |
| | | if (!this.scanForm.palletCode) { |
| | | this.$message.warning('请è¾å
¥æçæ¡ç ') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const result = await this.http.get( |
| | | `api/OutboundPicking/GetPalletOutboundStatus?palletCode=${this.scanForm.palletCode}` |
| | | ) |
| | | if (result.status) { |
| | | this.currentPallet = result.data |
| | | await this.loadPalletLockInfo() |
| | | this.$message.success(`æç ${this.scanForm.palletCode} è¯å«æå`) |
| | | } else { |
| | | this.$message.error(result.message) |
| | | } |
| | | } catch (error) { |
| | | this.$message.error('æçè¯å«å¤±è´¥') |
| | | } |
| | | }, |
| | | |
| | | async loadPalletLockInfo() { |
| | | if (!this.currentPallet) return |
| | | |
| | | try { |
| | | const result = await this.http.get( |
| | | `api/OutboundPicking/GetPalletLockInfos?palletCode=${this.currentPallet.palletCode}` |
| | | ) |
| | | if (result.status) { |
| | | this.allLockInfos = result.data |
| | | // é»è®¤éæ©ç¬¬ä¸ä¸ªéå®ä¿¡æ¯ |
| | | if (this.allLockInfos.length > 0) { |
| | | this.currentLockInfo = this.allLockInfos[0] |
| | | this.currentMaterialInfo = { |
| | | materielCode: this.currentLockInfo.materielCode, |
| | | materielName: this.currentLockInfo.materielName |
| | | } |
| | | this.maxPickQuantity = this.currentLockInfo.assignQuantity - this.currentLockInfo.pickedQty |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error('å è½½éå®ä¿¡æ¯å¤±è´¥:', error) |
| | | } |
| | | }, |
| | | |
| | | // æ ¹æ®æ¡ç æ¥æ¾å¯¹åºçéå®ä¿¡æ¯åç©æä¿¡æ¯ |
| | | findLockInfoByBarcode(barcode) { |
| | | if (!this.allLockInfos || this.allLockInfos.length === 0) { |
| | | return null |
| | | } |
| | | |
| | | // é¦å
精确å¹é
å½åæ¡ç |
| | | let lockInfo = this.allLockInfos.find(x => x.currentBarcode === barcode) |
| | | if (lockInfo) { |
| | | return lockInfo |
| | | } |
| | | |
| | | // å¦ææ²¡æç²¾ç¡®å¹é
ï¼æ¥æ¾è¯¥æ¡ç 对åºçç©ææ¯å¦å¨éå®ä¿¡æ¯ä¸ |
| | | // è¿ééè¦è°ç¨å端æ¥å£éªè¯æ¡ç 对åºçç©æ |
| | | return null |
| | | }, |
| | | |
| | | async handleBarcodeScan() { |
| | | if (!this.scanForm.barcode) { |
| | | this.$message.warning('请è¾å
¥ç©ææ¡ç ') |
| | | return |
| | | } |
| | | |
| | | if (!this.currentPallet) { |
| | | this.$message.warning('请å
æ«ææçæ¡ç ') |
| | | return |
| | | } |
| | | |
| | | if (this.scanForm.quantity <= 0) { |
| | | this.$message.warning('请è¾å
¥ææçæ£éæ°é') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | // éªè¯æ¡ç å¹¶è·åç©æä¿¡æ¯ |
| | | const materialInfo = await this.validateBarcode(this.scanForm.barcode) |
| | | if (!materialInfo) { |
| | | this.$message.error('æ æçç©ææ¡ç ') |
| | | return |
| | | } |
| | | |
| | | // æ¥æ¾å¯¹åºçéå®ä¿¡æ¯ |
| | | const targetLockInfo = this.findLockInfoByBarcodeAndMaterial(this.scanForm.barcode, materialInfo.materielCode) |
| | | if (!targetLockInfo) { |
| | | this.$message.error('è¯¥ç©ææ¡ç ä¸å¨å½åæççéå®ä¿¡æ¯ä¸') |
| | | return |
| | | } |
| | | |
| | | // æ£æ¥æ£éæ°é |
| | | const availableQuantity = targetLockInfo.assignQuantity - targetLockInfo.pickedQty |
| | | if (this.scanForm.quantity > availableQuantity) { |
| | | this.$message.error(`æ£éæ°éè¶
è¿å¯ç¨æ°éï¼å©ä½å¯æ£éï¼${availableQuantity}`) |
| | | return |
| | | } |
| | | |
| | | // åå¤è¯·æ±æ°æ® |
| | | const request = { |
| | | orderDetailId: targetLockInfo.orderDetailId, |
| | | barcode: this.scanForm.barcode, |
| | | materielCode: materialInfo.materielCode, // ä¼ éç©æç¼ç |
| | | pickQuantity: this.scanForm.quantity, |
| | | locationCode: this.currentPallet.locationCode, |
| | | palletCode: this.currentPallet.palletCode, |
| | | stockId: targetLockInfo.stockId, |
| | | outStockLockInfoId: targetLockInfo.id // ä¼ ééå®ä¿¡æ¯ID |
| | | } |
| | | |
| | | const result = await this.http.post('api/OutboundPicking/ConfirmPicking', request) |
| | | if (result.status) { |
| | | this.$message.success('æ£é确认æå') |
| | | |
| | | // é置表å |
| | | this.scanForm.barcode = '' |
| | | this.scanForm.quantity = 1 |
| | | this.currentMaterialInfo = null |
| | | |
| | | // å·æ°æ°æ® |
| | | this.loadOrderInfo() |
| | | this.loadPickedHistory() |
| | | this.loadPalletLockInfo() |
| | | } else { |
| | | this.$message.error(result.message) |
| | | } |
| | | } catch (error) { |
| | | this.$message.error('æ£é确认失败: ' + (error.message || 'æªç¥é误')) |
| | | } |
| | | }, |
| | | |
| | | // æ ¹æ®æ¡ç åç©æç¼ç æ¥æ¾éå®ä¿¡æ¯ |
| | | findLockInfoByBarcodeAndMaterial(barcode, materielCode) { |
| | | if (!this.allLockInfos || this.allLockInfos.length === 0) { |
| | | return null |
| | | } |
| | | |
| | | // é¦å
å°è¯ç²¾ç¡®å¹é
æ¡ç |
| | | let lockInfo = this.allLockInfos.find(x => |
| | | x.currentBarcode === barcode && x.materielCode === materielCode |
| | | ) |
| | | |
| | | if (lockInfo) { |
| | | return lockInfo |
| | | } |
| | | |
| | | // å¦æç²¾ç¡®å¹é
失败ï¼åªå¹é
ç©æç¼ç ï¼å
许ä»åä¸ç©æçä¸åæ¡ç æ£éï¼ |
| | | lockInfo = this.allLockInfos.find(x => |
| | | x.materielCode === materielCode && |
| | | (x.assignQuantity - x.pickedQty) > 0 |
| | | ) |
| | | |
| | | return lockInfo |
| | | }, |
| | | |
| | | // éªè¯æ¡ç å¹¶è·åç©æä¿¡æ¯ |
| | | async validateBarcode(barcode) { |
| | | try { |
| | | const result = await this.http.get(`api/OutboundPicking/ValidateBarcode?barcode=${barcode}`) |
| | | if (result.status) { |
| | | return result.data |
| | | } else { |
| | | this.$message.error(result.message) |
| | | return null |
| | | } |
| | | } catch (error) { |
| | | this.$message.error('æ¡ç éªè¯å¤±è´¥') |
| | | return null |
| | | } |
| | | }, |
| | | |
| | | async handleBackToStock() { |
| | | if (!this.currentPallet) return |
| | | |
| | | try { |
| | | await this.$confirm(`ç¡®å®å°æç ${this.currentPallet.palletCode} ååºåï¼`, 'æç¤º', { |
| | | type: 'warning' |
| | | }) |
| | | |
| | | const result = await this.http.post('api/BackToStock/GenerateBackToStockTask', { |
| | | palletCode: this.currentPallet.palletCode, |
| | | currentLocation: 'æ£éä½', |
| | | operator: 'å½åç¨æ·' |
| | | }) |
| | | |
| | | if (result.status) { |
| | | this.$message.success('ååºä»»å¡å·²çæ') |
| | | this.resetCurrentPallet() |
| | | } |
| | | } catch (error) { |
| | | // ç¨æ·åæ¶ |
| | | } |
| | | }, |
| | | |
| | | async handleDirectOutbound() { |
| | | if (!this.currentPallet) return |
| | | |
| | | try { |
| | | await this.$confirm(`ç¡®å®å°æç ${this.currentPallet.palletCode} ç´æ¥åºåºåï¼`, 'æç¤º', { |
| | | type: 'warning' |
| | | }) |
| | | |
| | | const result = await this.http.post('api/OutboundPicking/DirectOutbound', { |
| | | palletCode: this.currentPallet.palletCode |
| | | }) |
| | | |
| | | if (result.status) { |
| | | this.$message.success('ç´æ¥åºåºæå') |
| | | this.resetCurrentPallet() |
| | | this.loadOrderInfo() |
| | | } |
| | | } catch (error) { |
| | | // ç¨æ·åæ¶ |
| | | } |
| | | }, |
| | | |
| | | handleOpenSplit() { |
| | | if (!this.currentLockInfo) { |
| | | this.$message.warning('请å
éæ©éå®ä¿¡æ¯') |
| | | return |
| | | } |
| | | this.splitVisible = true |
| | | }, |
| | | |
| | | handleSplitSuccess() { |
| | | this.$message.success('æå
æå') |
| | | this.loadPalletLockInfo() |
| | | }, |
| | | |
| | | resetCurrentPallet() { |
| | | this.currentPallet = null |
| | | this.currentLockInfo = null |
| | | this.currentMaterialInfo = null |
| | | this.allLockInfos = [] |
| | | this.scanForm.palletCode = '' |
| | | }, |
| | | |
| | | async loadPickedHistory() { |
| | | const orderId = this.$route.query.orderId |
| | | if (!orderId) return |
| | | |
| | | try { |
| | | const result = await this.http.get(`api/OutboundPicking/GetPickingHistory?orderId=${orderId}`) |
| | | if (result.status) { |
| | | this.pickedList = result.data |
| | | } |
| | | } catch (error) { |
| | | console.error('å è½½æ£éåå²å¤±è´¥:', error) |
| | | } |
| | | }, |
| | | |
| | | async handleCancelPick(row) { |
| | | try { |
| | | await this.$confirm('ç¡®å®æ¤éè¿æ¡æ£éè®°å½åï¼', 'æç¤º', { type: 'warning' }) |
| | | |
| | | const result = await this.http.post('api/OutboundPicking/CancelPicking', { |
| | | pickingHistoryId: row.id |
| | | }) |
| | | |
| | | if (result.status) { |
| | | this.$message.success('æ¤éæå') |
| | | this.loadPickedHistory() |
| | | this.loadOrderInfo() |
| | | } |
| | | } catch (error) { |
| | | // ç¨æ·åæ¶ |
| | | } |
| | | palletSummary: null, |
| | | confirmLoading: false, |
| | | pickedTableConfig: { |
| | | url: '/api/outbound/getPickingRecords', |
| | | query: { orderNo: this.orderNo }, |
| | | columns: [ |
| | | { prop: 'TaskNo', label: 'ä»»å¡å·', width: 150 }, |
| | | { prop: 'Barcode', label: 'ç©ææ¡ç ', width: 150 }, |
| | | { prop: 'MaterielName', label: 'ç©æåç§°', width: 150 }, |
| | | { prop: 'PickQuantity', label: 'æ£è´§æ°é', width: 100 }, |
| | | { prop: 'LocationCode', label: 'è´§ä½', width: 120 }, |
| | | { prop: 'CreateTime', label: 'æ£è´§æ¶é´', width: 180 } |
| | | ] |
| | | }, |
| | | // åºåºè¯¦æ
è¡¨æ ¼é
ç½® |
| | | outboundTableConfig: { |
| | | url: '/api/outbound/getOutboundDetails', |
| | | query: { orderNo: this.orderNo }, |
| | | columns: [ |
| | | { prop: 'OrderNo', label: 'åºåºåå·', width: 150 }, |
| | | { prop: 'MaterialCode', label: 'ç©æç¼å·', width: 120 }, |
| | | { prop: 'MaterialBarcode', label: 'ç©ææ¡ç ', width: 150 }, |
| | | { prop: 'BatchNo', label: 'æ¹æ¬¡å·', width: 120 }, |
| | | { prop: 'AssignQuantity', label: 'åé
åºåºé', width: 100 }, |
| | | { prop: 'PalletCode', label: 'æçç¼å·', width: 120 }, |
| | | { prop: 'Unit', label: 'åä½', width: 80 } |
| | | ] |
| | | }, |
| | | orderInfo: {orderNo:''} |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.loadOrderInfo() |
| | | this.loadPickedHistory() |
| | | this.loadOrderInfo(); |
| | | this.$nextTick(() => { |
| | | if (this.$refs.palletInput) { |
| | | this.$refs.palletInput.focus() |
| | | } |
| | | }) |
| | | }, |
| | | methods: { |
| | | loadOrderInfo() { |
| | | const orderId = this.$route.query.orderId |
| | | if (!orderId) return |
| | | |
| | | try { |
| | | this.http.get(`/api/OutboundOrder/GetById?id=${orderId}`).then(response => {debugger; |
| | | if (response.status) { |
| | | this.orderInfo = response.data |
| | | |
| | | } |
| | | }) |
| | | } catch (error) { |
| | | ElMessage.error('å è½½åºåºåä¿¡æ¯å¤±è´¥') |
| | | } |
| | | }, |
| | | goBack() { |
| | | this.$router.back() |
| | | }, |
| | | async handlePalletScan() { |
| | | if (this.scanForm.palletCode) { |
| | | ElMessage.success(`å·²æ«ææç: ${this.scanForm.palletCode}`) |
| | | await this.loadPalletSummary() |
| | | |
| | | this.$nextTick(() => { |
| | | if (this.$refs.materialInput) { |
| | | this.$refs.materialInput.focus() |
| | | } |
| | | }) |
| | | } |
| | | }, |
| | | async handleMaterialScan() { |
| | | if (!this.scanForm.palletCode) { |
| | | ElMessage.warning('请å
æ«ææçç ') |
| | | this.$refs.palletInput.focus() |
| | | return |
| | | } |
| | | |
| | | if (!this.scanForm.materialBarcode) { |
| | | ElMessage.warning('è¯·æ«æç©ææ¡ç ') |
| | | return |
| | | } |
| | | |
| | | await this.executePickingConfirm() |
| | | }, |
| | | async loadPalletSummary() { |
| | | if (!this.scanForm.palletCode) { |
| | | this.palletSummary = null |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const result = await http.get('/api/outbound/getPalletPickingSummary', { |
| | | params: { |
| | | orderNo: this.orderNo, |
| | | palletCode: this.scanForm.palletCode |
| | | } |
| | | }) |
| | | |
| | | if (result.success) { |
| | | // å¤çç»è®¡ä¿¡æ¯ |
| | | const summary = result.data |
| | | const assigned = summary.find(x => x.Status === 'å·²åé
') || { TotalAssignQty: 0, TotalPickedQty: 0 } |
| | | const picked = summary.find(x => x.Status === 'å·²æ£é') || { TotalPickedQty: 0 } |
| | | |
| | | this.palletSummary = { |
| | | unpickedCount: assigned.TotalAssignQty > 0 ? 1 : 0, // ç®åè®¡ç® |
| | | unpickedTotal: assigned.TotalAssignQty - assigned.TotalPickedQty |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error('å è½½æçç»è®¡å¤±è´¥:', error) |
| | | } |
| | | }, |
| | | async handleConfirm() { |
| | | if (!this.scanForm.palletCode || !this.scanForm.materialBarcode) { |
| | | ElMessage.warning('请填å宿´çæ«ç ä¿¡æ¯') |
| | | return |
| | | } |
| | | |
| | | await this.executePickingConfirm() |
| | | }, |
| | | async executePickingConfirm() { |
| | | this.confirmLoading = true |
| | | |
| | | try { |
| | | // å
æ¾å°å¯¹åºçåºåºéå®ä¿¡æ¯ |
| | | const lockInfoResult = await this.http.get('/api/outbound/getOutStockLockInfo', { |
| | | params: { |
| | | orderNo: this.orderNo, |
| | | palletCode: this.scanForm.palletCode, |
| | | materialBarcode: this.scanForm.materialBarcode |
| | | } |
| | | }) |
| | | |
| | | if (!lockInfoResult.success || !lockInfoResult.data || lockInfoResult.data.length === 0) { |
| | | ElMessage.error('æªæ¾å°å¯¹åºçåºåºéå®ä¿¡æ¯') |
| | | return |
| | | } |
| | | |
| | | const lockInfo = lockInfoResult.data[0] |
| | | |
| | | const request = { |
| | | outStockLockId: lockInfo.Id, |
| | | taskNo: `TASK_${Date.now()}`, |
| | | palletCode: this.scanForm.palletCode, |
| | | materialBarcode: this.scanForm.materialBarcode, |
| | | locationCode: lockInfo.LocationCode |
| | | } |
| | | |
| | | const result = await this.http.post('/api/outbound/pickingConfirm', request) |
| | | |
| | | if (result.success) { |
| | | ElMessage.success('åæ£ç¡®è®¤æå') |
| | | this.handleReset() |
| | | this.$emit('confirm') |
| | | |
| | | // å·æ°è¡¨æ ¼ |
| | | if (this.$refs.pickedTable) { |
| | | this.$refs.pickedTable.refresh() |
| | | } |
| | | |
| | | // å·æ°åºåºè¯¦æ
è¡¨æ ¼ |
| | | if (this.$refs.outboundTable) { |
| | | this.$refs.outboundTable.refresh() |
| | | } |
| | | |
| | | // éæ°å è½½æçç»è®¡ |
| | | await this.loadPalletSummary() |
| | | } else { |
| | | ElMessage.error(result.ElMessage) |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('åæ£ç¡®è®¤å¤±è´¥') |
| | | } finally { |
| | | this.confirmLoading = false |
| | | } |
| | | }, |
| | | handleReset() { |
| | | this.scanForm.materialBarcode = '' |
| | | this.$nextTick(() => { |
| | | if (this.$refs.materialInput) { |
| | | this.$refs.materialInput.focus() |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .picking-confirm { |
| | | padding: 20px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 70vh; |
| | | } |
| | | |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | .content-layout { |
| | | display: flex; |
| | | gap: 16px; |
| | | margin-bottom: 16px; |
| | | flex: 1; |
| | | min-height: 0; /* éè¦ï¼é²æ¢flexåå
ç´ æº¢åº */ |
| | | } |
| | | |
| | | .title { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | .left-section { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .right-section { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .scan-section { |
| | | margin-bottom: 20px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .scan-alert { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .scan-form { |
| | | max-width: 500px; |
| | | } |
| | | |
| | | .pallet-summary { |
| | | margin: 16px 0; |
| | | } |
| | | |
| | | .action-buttons { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .outbound-details-card { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .action-buttons .el-button { |
| | | width: 100%; |
| | | .outbound-details-card :deep(.el-card__body) { |
| | | flex: 1; |
| | | padding: 0; |
| | | } |
| | | |
| | | .current-info { |
| | | margin-top: 15px; |
| | | padding: 10px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | .picked-records { |
| | | flex-shrink: 0; |
| | | height: 300px; |
| | | } |
| | | |
| | | .current-info p { |
| | | margin: 5px 0; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .summary-info { |
| | | margin-bottom: 15px; |
| | | .picked-records :deep(.el-card__body) { |
| | | padding: 0; |
| | | } |
| | | </style> |