| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="picking-container"> |
| | | <!-- åºåºåå表 --> |
| | | <div class="card mb-4" v-for="order in pickingOrders" :key="order.outStockOrderId"> |
| | | <div class="card-header bg-light d-flex justify-content-between align-items-center"> |
| | | <div> |
| | | <h5 class="mb-0">åºåºå: {{ order.orderNumber }}</h5> |
| | | <small class="text-muted"> |
| | | éæ±: {{ order.totalRequiredQuantity }} | |
| | | å·²éå®: {{ order.totalLockedQuantity }} |
| | | </small> |
| | | </div> |
| | | <button @click="confirmOrder(order)" class="btn btn-primary btn-sm" |
| | | :disabled="!canConfirmOrder(order)"> |
| | | <i class="fas fa-check"></i> 确认æ´ååºåº |
| | | </button> |
| | | </div> |
| | | |
| | | <div class="card-body p-0"> |
| | | <div class="table-responsive"> |
| | | <table class="table table-hover mb-0"> |
| | | <thead> |
| | | <tr> |
| | | <th width="5%">#</th> |
| | | <th>产ååç§°</th> |
| | | <th>æ¹å·/æ¡ç </th> |
| | | <th>åºåºæ°é</th> |
| | | <th>åºåæ°é</th> |
| | | <th>åºä½</th> |
| | | <th width="20%">æä½</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr v-for="(item, index) in order.pickingItems" :key="item.outStockLockId" |
| | | :class="{ |
| | | 'table-success': item.status === 'Confirmed', |
| | | 'table-warning': needsSplit(item) |
| | | }"> |
| | | <td>{{ index + 1 }}</td> |
| | | <td>{{ item.productName }}</td> |
| | | <td> |
| | | <div> |
| | | <strong>{{ item.batchNumber }}</strong> |
| | | <br> |
| | | <small class="text-muted">{{ item.barCode }}</small> |
| | | </div> |
| | | </td> |
| | | <td> |
| | | <span class="font-weight-bold text-primary"> |
| | | {{ item.outStockQuantity }} |
| | | </span> |
| | | <small class="text-muted ml-1">{{ item.packageUnit }}</small> |
| | | </td> |
| | | <td> |
| | | <span :class="{ |
| | | 'text-success': item.isWholePackage, |
| | | 'text-warning': needsSplit(item) |
| | | }"> |
| | | {{ item.stockQuantity }} {{ item.packageUnit }} |
| | | </span> |
| | | </td> |
| | | <td>{{ item.location }}</td> |
| | | <td> |
| | | <!-- æ´å
ç´æ¥ç¡®è®¤ --> |
| | | <button v-if="item.isWholePackage && item.status === 'Pending'" |
| | | @click="confirmWholePicking(item)" |
| | | class="btn btn-success btn-sm" |
| | | :disabled="loading"> |
| | | <i class="fas fa-check"></i> 确认 |
| | | </button> |
| | | |
| | | <!-- éè¦æå
çæ
åµ --> |
| | | <div v-else-if="needsSplit(item) && item.status === 'Pending'"> |
| | | <button @click="showSplitModal(item)" |
| | | class="btn btn-warning btn-sm mb-1" |
| | | :disabled="loading"> |
| | | <i class="fas fa-cube"></i> æå
|
| | | </button> |
| | | <div class="small text-muted"> |
| | | éæå
: {{ item.outStockQuantity }}/{{ item.stockQuantity }} |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å·²ç¡®è®¤ç¶æ --> |
| | | <span v-else-if="item.status === 'Confirmed'" class="text-success"> |
| | | <i class="fas fa-check-circle"></i> 已确认 |
| | | </span> |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æå
æ¨¡ææ¡ --> |
| | | <div class="modal fade" id="splitModal" tabindex="-1"> |
| | | <div class="modal-dialog"> |
| | | <div class="modal-content"> |
| | | <div class="modal-header"> |
| | | <h5 class="modal-title">æå
æä½</h5> |
| | | <button type="button" class="close" data-dismiss="modal"> |
| | | <span>×</span> |
| | | </button> |
| | | </div> |
| | | <div class="modal-body"> |
| | | <div v-if="currentSplitItem"> |
| | | <div class="form-group"> |
| | | <label>产ååç§°</label> |
| | | <input type="text" class="form-control" :value="currentSplitItem.productName" readonly> |
| | | </div> |
| | | <div class="form-group"> |
| | | <label>åæ¡ç </label> |
| | | <input type="text" class="form-control" :value="currentSplitItem.barCode" readonly> |
| | | </div> |
| | | <div class="row"> |
| | | <div class="col-6"> |
| | | <div class="form-group"> |
| | | <label>ååºåæ°é</label> |
| | | <input type="text" class="form-control" :value="currentSplitItem.stockQuantity" readonly> |
| | | </div> |
| | | </div> |
| | | <div class="col-6"> |
| | | <div class="form-group"> |
| | | <label>åºåºæ°é</label> |
| | | <input type="text" class="form-control" :value="currentSplitItem.outStockQuantity" readonly> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="form-group"> |
| | | <label>æå
æ°é</label> |
| | | <input type="number" class="form-control" v-model="splitQuantity" |
| | | :max="currentSplitItem.stockQuantity - 0.01" step="0.01" min="0.01"> |
| | | <small class="form-text text-muted"> |
| | | æåºæ°é: {{ splitQuantity }} | |
| | | å©ä½æ°é: {{ (currentSplitItem.stockQuantity - splitQuantity).toFixed(2) }} |
| | | </small> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="modal-footer"> |
| | | <button type="button" class="btn btn-secondary" data-dismiss="modal">åæ¶</button> |
| | | <button type="button" class="btn btn-warning" @click="confirmSplit" |
| | | :disabled="!splitQuantity || splitQuantity <= 0"> |
| | | <i class="fas fa-cube"></i> 确认æå
|
| | | </button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æä½æç¤º --> |
| | | <div v-if="message" :class="['alert', messageType === 'success' ? 'alert-success' : 'alert-danger', 'mt-3']"> |
| | | {{ message }} |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | data() { |
| | | return { |
| | | pickingOrders: [], |
| | | loading: false, |
| | | message: '', |
| | | messageType: 'success', |
| | | currentSplitItem: null, |
| | | splitQuantity: 0 |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.loadPickingList(); |
| | | // åå§åæ¨¡ææ¡ |
| | | $('#splitModal').on('hidden.bs.modal', () => { |
| | | this.currentSplitItem = null; |
| | | this.splitQuantity = 0; |
| | | }); |
| | | }, |
| | | methods: { |
| | | async loadPickingList() { |
| | | try { |
| | | this.loading = true; |
| | | const response = await this.$http.get('/Picking/PickingList'); |
| | | this.pickingOrders = response.data; |
| | | } catch (error) { |
| | | this.showMessage('å è½½æ£éå表失败', 'error'); |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | |
| | | // 夿æ¯å¦éè¦æå
|
| | | needsSplit(item) { |
| | | return !item.isWholePackage && item.outStockQuantity <= item.stockQuantity; |
| | | }, |
| | | |
| | | // æ´å
确认 |
| | | async confirmWholePicking(item) { |
| | | try { |
| | | this.loading = true; |
| | | const response = await this.$http.post('/Picking/ConfirmWholePicking', { |
| | | outStockLockId: item.outStockLockId |
| | | }); |
| | | |
| | | if (response.data.success) { |
| | | this.showMessage(response.data.message, 'success'); |
| | | this.loadPickingList(); |
| | | } else { |
| | | this.showMessage(response.data.message, 'error'); |
| | | } |
| | | } catch (error) { |
| | | this.showMessage('æä½å¤±è´¥', 'error'); |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | |
| | | // æ¾ç¤ºæå
æ¨¡ææ¡ |
| | | showSplitModal(item) { |
| | | this.currentSplitItem = item; |
| | | this.splitQuantity = item.outStockQuantity; // é»è®¤æåºæ°é为åºåºæ°é |
| | | $('#splitModal').modal('show'); |
| | | }, |
| | | |
| | | // 确认æå
|
| | | async confirmSplit() { |
| | | if (!this.splitQuantity || this.splitQuantity <= 0) { |
| | | this.showMessage('请è¾å
¥ææçæå
æ°é', 'error'); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | this.loading = true; |
| | | const response = await this.$http.post('/Picking/SplitPackage', { |
| | | outStockLockId: this.currentSplitItem.outStockLockId, |
| | | splitQuantity: this.splitQuantity |
| | | }); |
| | | |
| | | if (response.data.success) { |
| | | this.showMessage(`æå
æåï¼æ°æ¡ç : ${response.data.data.newBarCode}`, 'success'); |
| | | $('#splitModal').modal('hide'); |
| | | this.loadPickingList(); |
| | | } else { |
| | | this.showMessage(response.data.message, 'error'); |
| | | } |
| | | } catch (error) { |
| | | this.showMessage('æå
失败', 'error'); |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | |
| | | // 确认æ´ååºåº |
| | | async confirmOrder(order) { |
| | | if (!confirm(`ç¡®å®è¦ç¡®è®¤æ´ä¸ªåºåºå ${order.orderNumber} åï¼`)) return; |
| | | |
| | | try { |
| | | this.loading = true; |
| | | const response = await this.$http.post('/Picking/ConfirmOutStockOrder', { |
| | | outStockOrderId: order.outStockOrderId |
| | | }); |
| | | |
| | | if (response.data.success) { |
| | | this.showMessage(response.data.message, 'success'); |
| | | this.loadPickingList(); |
| | | } else { |
| | | this.showMessage(response.data.message, 'error'); |
| | | } |
| | | } catch (error) { |
| | | this.showMessage('æä½å¤±è´¥', 'error'); |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | |
| | | // æ£æ¥æ¯å¦å¯ä»¥ç¡®è®¤æ´å |
| | | canConfirmOrder(order) { |
| | | return order.pickingItems.every(item => |
| | | item.status === 'Pending' && |
| | | (item.isWholePackage || this.needsSplit(item)) |
| | | ); |
| | | }, |
| | | |
| | | showMessage(msg, type) { |
| | | this.message = msg; |
| | | this.messageType = type; |
| | | setTimeout(() => { |
| | | this.message = ''; |
| | | }, 3000); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .picking-container { |
| | | max-width: 1400px; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | .table td { |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .btn-sm { |
| | | min-width: 80px; |
| | | } |
| | | |
| | | .badge { |
| | | font-family: monospace; |
| | | } |
| | | </style> |