<template>
|
<div class="OutboundPicking-container">
|
<div class="page-header">
|
<el-page-header @back="goBack">
|
<template #content>
|
<span class="title">出库拣选确认 - {{ this.$route.query.orderNo }}</span>
|
<el-tag v-if="currentBatchNo" type="success" style="margin-left: 10px;">
|
当前批次: {{ currentBatchNo }}
|
</el-tag>
|
</template>
|
</el-page-header>
|
</div>
|
|
<!-- 批次操作区域 -->
|
<div class="batch-operations">
|
<el-card>
|
<div class="batch-actions">
|
<el-button type="primary" @click="openBatchAllocateDialog">分批分配</el-button>
|
<el-select v-model="selectedBatchNo" placeholder="选择批次" @change="onBatchChange" style="width: 200px; margin-left: 10px;">
|
<el-option
|
v-for="batch in batchList"
|
:key="batch.batchNo"
|
:label="`${batch.batchNo} (${batch.batchStatusText})`"
|
:value="batch.batchNo">
|
</el-option>
|
</el-select>
|
<el-button type="info" @click="refreshBatchList">刷新批次</el-button>
|
</div>
|
</el-card>
|
</div>
|
|
<!-- 扫码区域 -->
|
<div class="scanner-area">
|
<el-card>
|
<div class="scanner-form">
|
<el-input
|
ref="palletInput"
|
v-model="scanData.palletCode"
|
placeholder="扫描托盘码"
|
@change="onPalletScan"
|
@keyup.enter.native="onPalletScan">
|
</el-input>
|
<el-input
|
ref="barcodeInput"
|
v-model="scanData.barcode"
|
placeholder="扫描物料条码"
|
@change="onBarcodeScan"
|
@keyup.enter.native="onBarcodeScan">
|
</el-input>
|
<el-button type="success" @click="confirmPicking">确认拣选</el-button>
|
<el-button type="warning" @click="openSplitDialog">拆包</el-button>
|
<el-button type="info" @click="openRevertSplitDialog">撤销拆包</el-button>
|
<el-button type="info" @click="handleEmptyPallet">取空箱</el-button>
|
<el-button type="primary" @click="openBatchReturnDialog">回库</el-button>
|
</div>
|
</el-card>
|
</div>
|
|
<!-- 批次汇总信息 -->
|
<div class="batch-summary-area" v-if="currentBatchNo">
|
<el-card>
|
<div class="batch-summary-info">
|
<el-tag type="info">批次号: {{ batchSummary.batchNo }}</el-tag>
|
<el-tag :type="getBatchStatusType(batchSummary.batchStatus)">
|
{{ batchSummary.batchStatusText }}
|
</el-tag>
|
<el-tag type="warning">分配数量: {{ batchSummary.batchQuantity }}</el-tag>
|
<el-tag type="success">完成数量: {{ batchSummary.completedQuantity }}</el-tag>
|
<el-tag type="danger">剩余数量: {{ batchSummary.remainingQuantity }}</el-tag>
|
</div>
|
</el-card>
|
</div>
|
|
<!-- 汇总信息 -->
|
<div class="summary-area">
|
<el-card>
|
<div class="summary-info">
|
<el-tag type="warning">未拣选条数: {{summary.unpickedCount}}</el-tag>
|
<el-tag type="danger">未拣选数量: {{summary.unpickedQuantity}}</el-tag>
|
<el-tag type="info">托盘状态: {{palletStatus}}</el-tag>
|
</div>
|
</el-card>
|
</div>
|
|
<!-- 数据列表 -->
|
<div class="content-area">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-card header="未拣选列表">
|
<el-table :data="unpickedList" border height="440">
|
<el-table-column prop="materielCode" label="物料编码" width="120"></el-table-column>
|
<el-table-column prop="assignQuantity" label="分配数量" width="100"></el-table-column>
|
<el-table-column prop="pickedQty" label="已拣数量" width="100"></el-table-column>
|
<el-table-column prop="remainQuantity" label="剩余数量" width="100"></el-table-column>
|
<el-table-column prop="locationCode" label="货位" width="100"></el-table-column>
|
<el-table-column prop="currentBarcode" label="条码"></el-table-column>
|
</el-table>
|
</el-card>
|
</el-col>
|
|
<el-col :span="12">
|
<el-card header="已拣选列表">
|
<div class="table-actions">
|
<el-button
|
size="mini"
|
type="danger"
|
:disabled="selectedPickedRows.length === 0"
|
@click="batchCancelSelected">
|
取消拣选
|
</el-button>
|
<span class="selection-count">已选择 {{selectedPickedRows.length}} 项</span>
|
</div>
|
<el-table
|
:data="pickedList"
|
border
|
height="400"
|
style="width: 100%"
|
@selection-change="handlePickedSelectionChange">
|
<el-table-column type="selection" width="55"></el-table-column>
|
<el-table-column prop="materielCode" label="物料编码" width="120"></el-table-column>
|
<el-table-column prop="pickedQty" label="已拣数量" width="100"></el-table-column>
|
<el-table-column prop="locationCode" label="货位" width="100"></el-table-column>
|
<el-table-column prop="currentBarcode" label="条码"></el-table-column>
|
</el-table>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
|
<!-- 分批分配弹窗 -->
|
<div v-if="showBatchAllocateDialog" class="custom-dialog-overlay">
|
<div class="custom-dialog-wrapper">
|
<div class="custom-dialog">
|
<div class="custom-dialog-header">
|
<h3>分批分配库存</h3>
|
<el-button type="text" @click="closeBatchAllocateDialog" class="close-button">×</el-button>
|
</div>
|
<div class="custom-dialog-body">
|
<el-form :model="batchAllocateForm" :rules="batchAllocateFormRules" ref="batchAllocateFormRef" label-width="100px">
|
<el-form-item label="订单编号">
|
<el-input v-model="batchAllocateForm.orderNo" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="物料明细" prop="orderDetailId">
|
<el-select v-model="batchAllocateForm.orderDetailId" placeholder="选择物料明细" style="width: 100%">
|
<el-option
|
v-for="detail in allocatableDetails"
|
:key="detail.id"
|
:label="`${detail.materielCode} - 需求:${detail.needOutQuantity} 已分配:${detail.allocatedQuantity} 可分配:${detail.availableQuantity}`"
|
:value="detail.id">
|
</el-option>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="分配数量" prop="batchQuantity">
|
<el-input-number
|
v-model="batchAllocateForm.batchQuantity"
|
:min="0.01"
|
:precision="2"
|
:step="1"
|
style="width: 100%"
|
placeholder="输入分配数量">
|
</el-input-number>
|
</el-form-item>
|
<el-form-item label="可分配数量">
|
<el-input :value="getAvailableQuantity()" disabled></el-input>
|
</el-form-item>
|
</el-form>
|
</div>
|
<div class="custom-dialog-footer">
|
<el-button @click="closeBatchAllocateDialog">取消</el-button>
|
<el-button type="primary" @click="handleBatchAllocate" :loading="batchAllocateLoading">确认分配</el-button>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 拆包弹窗 -->
|
<div v-if="showCustomSplitDialog" class="custom-dialog-overlay">
|
<div class="custom-dialog-wrapper">
|
<div class="custom-dialog">
|
<div class="custom-dialog-header">
|
<h3>拆包操作</h3>
|
<el-button type="text" @click="closeCustomSplitDialog" class="close-button">X</el-button>
|
</div>
|
<div class="custom-dialog-body">
|
<el-form :model="splitForm" :rules="splitFormRules" ref="splitFormRef" label-width="100px">
|
<el-form-item label="订单编号">
|
<el-input v-model="splitForm.orderNo" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="批次编号">
|
<el-input v-model="splitForm.batchNo" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="托盘编号">
|
<el-input v-model="splitForm.palletCode" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="原条码" prop="originalBarcode">
|
<el-input
|
v-model="splitForm.originalBarcode"
|
placeholder="扫描原条码"
|
@keyup.enter.native="onSplitBarcodeScan"
|
@change="onSplitBarcodeScan"
|
clearable>
|
</el-input>
|
</el-form-item>
|
<el-form-item label="物料编码">
|
<el-input v-model="splitForm.materielCode" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="剩余数量">
|
<el-input v-model="splitForm.maxQuantity" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="拆包数量" prop="splitQuantity">
|
<el-input-number
|
v-model="splitForm.splitQuantity"
|
:min="1"
|
:max="splitForm.maxQuantity"
|
:precision="2"
|
:step="1"
|
style="width: 100%">
|
</el-input-number>
|
</el-form-item>
|
</el-form>
|
</div>
|
<div class="custom-dialog-footer">
|
<el-button @click="closeCustomSplitDialog">取消</el-button>
|
<el-button type="primary" @click="handleSplitPackage" :loading="splitLoading">确认拆包</el-button>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 撤销拆包弹窗 -->
|
<div v-if="showRevertSplitDialog" class="custom-dialog-overlay">
|
<div class="custom-dialog-wrapper">
|
<div class="custom-dialog">
|
<div class="custom-dialog-header">
|
<h3>撤销拆包</h3>
|
<el-button type="text" @click="closeRevertSplitDialog" class="close-button">×</el-button>
|
</div>
|
<div class="custom-dialog-body">
|
<el-form :model="revertSplitForm" :rules="revertSplitFormRules" ref="revertSplitFormRef" label-width="100px">
|
<el-form-item label="新条码" prop="newBarcode">
|
<el-input
|
v-model="revertSplitForm.newBarcode"
|
placeholder="扫描新条码"
|
@keyup.enter.native="onRevertSplitBarcodeScan"
|
@change="onRevertSplitBarcodeScan"
|
clearable>
|
</el-input>
|
</el-form-item>
|
</el-form>
|
</div>
|
<div class="custom-dialog-footer">
|
<el-button @click="closeRevertSplitDialog">取消</el-button>
|
<el-button type="primary" @click="handleRevertSplit" :loading="revertSplitLoading">确认撤销</el-button>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 批量回库弹窗 -->
|
<div v-if="showBatchReturnDialog" class="custom-dialog-overlay">
|
<div class="custom-dialog-wrapper">
|
<div class="custom-dialog">
|
<div class="custom-dialog-header">
|
<h3>批次回库</h3>
|
<el-button type="text" @click="closeBatchReturnDialog" class="close-button">×</el-button>
|
</div>
|
<div class="custom-dialog-body">
|
<el-form :model="batchReturnForm" :rules="batchReturnFormRules" ref="batchReturnFormRef" label-width="100px">
|
<el-form-item label="订单编号">
|
<el-input v-model="batchReturnForm.orderNo" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="批次编号">
|
<el-input v-model="batchReturnForm.batchNo" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="未拣选数量">
|
<el-input v-model="batchReturnForm.unpickedQuantity" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="未拣选条数">
|
<el-input v-model="batchReturnForm.unpickedCount" disabled></el-input>
|
</el-form-item>
|
</el-form>
|
</div>
|
<div class="custom-dialog-footer">
|
<el-button @click="closeBatchReturnDialog">取消</el-button>
|
<el-button type="primary" @click="handleBatchReturnConfirm" :loading="batchReturnLoading">确认回库</el-button>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 取走空箱弹窗 -->
|
<div v-if="showEmptyPalletDialog" class="custom-dialog-overlay">
|
<div class="custom-dialog-wrapper">
|
<div class="custom-dialog">
|
<div class="custom-dialog-header">
|
<h3>取走空箱</h3>
|
<el-button type="text" @click="closeEmptyPalletDialog" class="close-button">×</el-button>
|
</div>
|
<div class="custom-dialog-body">
|
<el-form :model="emptypalletOutForm" :rules="emptypalletOutFormRules" ref="emptypalletOutFormRef" label-width="100px">
|
<el-form-item label="订单编号">
|
<el-input v-model="emptypalletOutForm.orderNo" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="托盘编号" prop="palletCode">
|
<el-input
|
v-model="emptypalletOutForm.palletCode"
|
placeholder="扫描托盘码"
|
@keyup.enter.native="onEmptyPalletScan"
|
@change="onEmptyPalletScan"
|
clearable>
|
</el-input>
|
</el-form-item>
|
</el-form>
|
</div>
|
<div class="custom-dialog-footer">
|
<el-button @click="closeEmptyPalletDialog">取消</el-button>
|
<el-button type="primary" @click="handleEmptyPalletConfirm" :loading="emptypalletOutLoading">确认取走空箱</el-button>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<print-view ref="childs" @parentcall="parentcall"></print-view>
|
</div>
|
</template>
|
|
<script>
|
import http from '@/api/http.js'
|
import { ref, defineComponent } from "vue";
|
import { ElMessage } from 'element-plus'
|
import { useRoute } from 'vue-router'
|
import printView from "@/extension/outbound/extend/printView.vue"
|
|
export default defineComponent({
|
name: 'BatchOutboundPicking',
|
components: {printView},
|
data() {
|
// 验证规则定义...
|
const validateBatchQuantity = (rule, value, callback) => {
|
if (value === null || value === undefined || value === '') {
|
callback(new Error('请输入分配数量'));
|
} else if (value <= 0) {
|
callback(new Error('分配数量必须大于0'));
|
} else {
|
callback();
|
}
|
};
|
|
const validateOrderDetailId = (rule, value, callback) => {
|
if (!value) {
|
callback(new Error('请选择物料明细'));
|
} else {
|
callback();
|
}
|
};
|
|
return {
|
scanData: {
|
orderNo: '',
|
palletCode: '',
|
barcode: '',
|
batchNo: ''
|
},
|
currentBatchNo: '', // 当前批次号
|
batchList: [], // 批次列表
|
selectedBatchNo: '', // 选中的批次号
|
batchSummary: {}, // 批次汇总信息
|
unpickedList: [],
|
pickedList: [],
|
selectedPickedRows: [],
|
summary: {
|
unpickedCount: 0,
|
unpickedQuantity: 0,
|
pickedCount: 0
|
},
|
palletStatus: '未知',
|
|
// 弹窗状态
|
showBatchAllocateDialog: false,
|
showCustomSplitDialog: false,
|
showRevertSplitDialog: false,
|
showBatchReturnDialog: false,
|
showEmptyPalletDialog: false,
|
|
// 加载状态
|
batchAllocateLoading: false,
|
splitLoading: false,
|
revertSplitLoading: false,
|
batchReturnLoading: false,
|
emptypalletOutLoading: false,
|
|
// 表单数据
|
batchAllocateForm: {
|
orderNo: '',
|
orderDetailId: '',
|
batchQuantity: 0
|
},
|
allocatableDetails: [], // 可分配的订单明细
|
|
splitForm: {
|
orderNo: '',
|
batchNo: '',
|
palletCode: '',
|
originalBarcode: '',
|
materielCode: '',
|
splitQuantity: 0,
|
maxQuantity: 0
|
},
|
|
revertSplitForm: {
|
newBarcode: ''
|
},
|
|
batchReturnForm: {
|
orderNo: '',
|
batchNo: '',
|
unpickedCount: 0,
|
unpickedQuantity: 0
|
},
|
|
emptypalletOutForm: {
|
orderNo: '',
|
palletCode: ''
|
},
|
|
// 验证规则
|
batchAllocateFormRules: {
|
orderDetailId: [
|
{ required: true, validator: validateOrderDetailId, trigger: 'change' }
|
],
|
batchQuantity: [
|
{ required: true, validator: validateBatchQuantity, trigger: 'blur' }
|
]
|
},
|
|
// 其他验证规则...
|
isProcessing: false
|
}
|
},
|
mounted() {
|
if (this.$route.query.orderNo) {
|
this.scanData.orderNo = this.$route.query.orderNo;
|
this.batchAllocateForm.orderNo = this.$route.query.orderNo;
|
this.loadBatchList();
|
}
|
this.$nextTick(() => {
|
this.$refs.palletInput.focus();
|
});
|
},
|
methods: {
|
goBack(){
|
this.$router.back()
|
},
|
|
// 批次相关方法
|
async loadBatchList() {
|
try {
|
const res = await http.post('/api/BatchOutbound/order-batch-list', {
|
orderNo: this.scanData.orderNo
|
});
|
if (res.status) {
|
this.batchList = res.data || [];
|
if (this.batchList.length > 0) {
|
this.selectedBatchNo = this.batchList[0].batchNo;
|
this.currentBatchNo = this.selectedBatchNo;
|
this.scanData.batchNo = this.selectedBatchNo;
|
this.loadBatchData();
|
}
|
}
|
} catch (error) {
|
this.$message.error('加载批次列表失败');
|
}
|
},
|
|
async refreshBatchList() {
|
await this.loadBatchList();
|
this.$message.success('批次列表已刷新');
|
},
|
|
onBatchChange(batchNo) {
|
this.currentBatchNo = batchNo;
|
this.scanData.batchNo = batchNo;
|
this.loadBatchData();
|
},
|
|
async loadBatchData() {
|
if (!this.currentBatchNo) return;
|
|
await this.loadBatchSummary();
|
await this.loadUnpickedList();
|
await this.loadPickedList();
|
},
|
|
async loadBatchSummary() {
|
try {
|
const res = await http.post('/api/BatchOutbound/batch-summary', {
|
orderNo: this.scanData.orderNo,
|
batchNo: this.currentBatchNo
|
});
|
if (res.status) {
|
this.batchSummary = res.data || {};
|
}
|
} catch (error) {
|
this.$message.error('加载批次汇总失败');
|
}
|
},
|
|
async loadUnpickedList() {
|
try {
|
const res = await http.post('/api/BatchOutbound/batch-unpicked-list', {
|
orderNo: this.scanData.orderNo,
|
batchNo: this.currentBatchNo
|
});
|
this.unpickedList = res.data || [];
|
this.summary.unpickedCount = this.unpickedList.length;
|
this.summary.unpickedQuantity = this.unpickedList.reduce((sum, item) => sum + (item.remainQuantity || 0), 0);
|
} catch (error) {
|
this.$message.error('加载未拣选列表失败');
|
}
|
},
|
|
async loadPickedList() {
|
try {
|
const res = await http.post('/api/BatchOutbound/batch-picked-list', {
|
orderNo: this.scanData.orderNo,
|
batchNo: this.currentBatchNo
|
});
|
this.pickedList = res.data || [];
|
this.summary.pickedCount = this.pickedList.length;
|
} catch (error) {
|
this.$message.error('加载已拣选列表失败');
|
}
|
},
|
|
getBatchStatusType(status) {
|
const statusMap = {
|
0: 'info', // 分配中
|
1: 'warning', // 执行中
|
2: 'success', // 已完成
|
3: 'danger' // 已回库
|
};
|
return statusMap[status] || 'info';
|
},
|
|
// 分批分配相关方法
|
async openBatchAllocateDialog() {
|
this.showBatchAllocateDialog = true;
|
await this.loadAllocatableDetails();
|
this.batchAllocateForm.orderDetailId = '';
|
this.batchAllocateForm.batchQuantity = 0;
|
},
|
|
async loadAllocatableDetails() {
|
try {
|
const res = await http.post('/api/BatchOutbound/allocatable-order-details', {
|
orderNo: this.scanData.orderNo
|
});
|
if (res.status) {
|
this.allocatableDetails = res.data || [];
|
}
|
} catch (error) {
|
this.$message.error('加载可分配明细失败');
|
}
|
},
|
|
getAvailableQuantity() {
|
const detail = this.allocatableDetails.find(d => d.id === this.batchAllocateForm.orderDetailId);
|
return detail ? detail.availableQuantity : 0;
|
},
|
|
async handleBatchAllocate() {
|
if (this.$refs.batchAllocateFormRef) {
|
this.$refs.batchAllocateFormRef.validate(async (valid) => {
|
if (valid) {
|
this.batchAllocateLoading = true;
|
try {
|
const res = await http.post('/api/BatchOutbound/batch-allocate-stock', this.batchAllocateForm);
|
if (res.status) {
|
this.$message.success('分批分配成功');
|
this.showBatchAllocateDialog = false;
|
await this.loadBatchList(); // 刷新批次列表
|
} else {
|
this.$message.error(res.message || '分批分配失败');
|
}
|
} catch (error) {
|
this.$message.error('分批分配失败');
|
} finally {
|
this.batchAllocateLoading = false;
|
}
|
}
|
});
|
}
|
},
|
|
closeBatchAllocateDialog() {
|
this.showBatchAllocateDialog = false;
|
},
|
|
// 分拣相关方法
|
async confirmPicking() {
|
if (this.isProcessing) return;
|
|
if (!this.scanData.orderNo || !this.scanData.palletCode || !this.scanData.barcode) {
|
this.$message.warning('请先扫描托盘码和物料条码');
|
this.focusBarcodeInput();
|
return;
|
}
|
|
if (!this.currentBatchNo) {
|
this.$message.warning('请先选择批次');
|
return;
|
}
|
|
this.isProcessing = true;
|
|
try {
|
const res = await http.post('/api/BatchOutbound/confirm-picking', this.scanData);
|
if (res.status) {
|
this.$message.success('拣选确认成功');
|
this.scanData.barcode = '';
|
await this.loadBatchData();
|
if(res.data && res.data.splitResults && res.data.splitResults.length>0){
|
this.$refs.childs.open(res.data.splitResults);
|
}
|
this.$nextTick(() => {
|
this.$refs.barcodeInput.focus();
|
});
|
} else {
|
this.$message.error(res.message || '拣选确认失败');
|
this.focusBarcodeInput(true);
|
}
|
} catch (error) {
|
this.$message.error('拣选确认失败: ' + (error.message || '网络错误'));
|
this.focusBarcodeInput(true);
|
} finally {
|
this.isProcessing = false;
|
}
|
},
|
|
// 拆包相关方法
|
openSplitDialog() {
|
if (!this.scanData.palletCode) {
|
this.$message.warning('请先扫描托盘码');
|
return;
|
}
|
if (!this.currentBatchNo) {
|
this.$message.warning('请先选择批次');
|
return;
|
}
|
this.showCustomSplitDialog = true;
|
this.resetSplitForm();
|
this.splitForm.orderNo = this.scanData.orderNo;
|
this.splitForm.batchNo = this.currentBatchNo;
|
this.splitForm.palletCode = this.scanData.palletCode;
|
},
|
|
async onSplitBarcodeScan() {
|
if (!this.splitForm.originalBarcode) return;
|
this.splitForm.originalBarcode = this.splitForm.originalBarcode.replace(/\n/g, '').trim();
|
|
try {
|
const res = await http.post('/api/BatchOutbound/split-package-info', {
|
orderNo: this.splitForm.orderNo,
|
batchNo: this.splitForm.batchNo,
|
barcode: this.splitForm.originalBarcode
|
});
|
|
if (res.status) {
|
this.splitForm.materielCode = res.data.materielCode;
|
this.splitForm.maxQuantity = res.data.remainQuantity;
|
this.splitForm.splitQuantity = Math.min(1, this.splitForm.maxQuantity);
|
} else {
|
this.$message.error(res.message || '获取拆包信息失败');
|
}
|
} catch (error) {
|
this.$message.error('获取拆包信息失败');
|
}
|
},
|
|
async handleSplitPackage() {
|
if (this.$refs.splitFormRef) {
|
this.$refs.splitFormRef.validate(async (valid) => {
|
if (valid) {
|
this.splitLoading = true;
|
try {
|
const res = await http.post('/api/BatchOutbound/manual-split-package', this.splitForm);
|
if (res.status) {
|
this.$message.success('拆包成功');
|
this.showCustomSplitDialog = false;
|
await this.loadBatchData();
|
} else {
|
this.$message.error(res.message || '拆包失败');
|
}
|
} catch (error) {
|
this.$message.error('拆包失败');
|
} finally {
|
this.splitLoading = false;
|
}
|
}
|
});
|
}
|
},
|
|
// 撤销拆包
|
async onRevertSplitBarcodeScan() {
|
if (!this.revertSplitForm.newBarcode) return;
|
this.revertSplitForm.newBarcode = this.revertSplitForm.newBarcode.replace(/\n/g, '').trim();
|
},
|
|
async handleRevertSplit() {
|
if (this.$refs.revertSplitFormRef) {
|
this.$refs.revertSplitFormRef.validate(async (valid) => {
|
if (valid) {
|
this.revertSplitLoading = true;
|
try {
|
const res = await http.post('/api/BatchOutbound/cancel-split-package', {
|
orderNo: this.scanData.orderNo,
|
batchNo: this.currentBatchNo,
|
newBarcode: this.revertSplitForm.newBarcode
|
});
|
if (res.status) {
|
this.$message.success('撤销拆包成功');
|
this.showRevertSplitDialog = false;
|
await this.loadBatchData();
|
} else {
|
this.$message.error(res.message || '撤销拆包失败');
|
}
|
} catch (error) {
|
this.$message.error('撤销拆包失败');
|
} finally {
|
this.revertSplitLoading = false;
|
}
|
}
|
});
|
}
|
},
|
|
// 回库相关方法
|
openBatchReturnDialog() {
|
if (!this.currentBatchNo) {
|
this.$message.warning('请先选择批次');
|
return;
|
}
|
this.showBatchReturnDialog = true;
|
this.batchReturnForm.orderNo = this.scanData.orderNo;
|
this.batchReturnForm.batchNo = this.currentBatchNo;
|
this.batchReturnForm.unpickedCount = this.summary.unpickedCount;
|
this.batchReturnForm.unpickedQuantity = this.summary.unpickedQuantity;
|
},
|
|
async handleBatchReturnConfirm() {
|
this.batchReturnLoading = true;
|
try {
|
const res = await http.post('/api/BatchOutbound/batch-return-stock', {
|
orderNo: this.scanData.orderNo,
|
batchNo: this.currentBatchNo
|
});
|
if (res.status) {
|
this.$message.success('批次回库成功');
|
this.showBatchReturnDialog = false;
|
await this.loadBatchData();
|
} else {
|
this.$message.error(res.message || '批次回库失败');
|
}
|
} catch (error) {
|
this.$message.error('批次回库失败');
|
} finally {
|
this.batchReturnLoading = false;
|
}
|
},
|
|
// 取空箱方法
|
async handleEmptyPalletConfirm() {
|
this.emptypalletOutLoading = true;
|
try {
|
const res = await http.post('/api/BatchOutbound/remove-empty-pallet', this.emptypalletOutForm);
|
if (res.status) {
|
this.$message.success('取走空箱成功');
|
this.showEmptyPalletDialog = false;
|
await this.loadBatchData();
|
} else {
|
this.$message.error(res.message || '取走空箱失败');
|
}
|
} catch (error) {
|
this.$message.error('取走空箱失败');
|
} finally {
|
this.emptypalletOutLoading = false;
|
}
|
},
|
|
// 其他原有方法...
|
onPalletScan() {
|
this.scanData.palletCode = this.scanData.palletCode.replace(/\n/g, '').trim();
|
if (!this.scanData.palletCode) return;
|
|
this.loadActiveBatch();
|
this.$nextTick(() => {
|
this.$refs.barcodeInput.focus();
|
});
|
},
|
|
async loadActiveBatch() {
|
try {
|
const res = await http.post('/api/BatchOutbound/active-batch', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode
|
});
|
if (res.status && res.data) {
|
this.currentBatchNo = res.data.batchNo;
|
this.scanData.batchNo = res.data.batchNo;
|
this.selectedBatchNo = res.data.batchNo;
|
await this.loadBatchData();
|
}
|
} catch (error) {
|
console.log('获取活跃批次失败,可能托盘没有关联批次');
|
}
|
},
|
|
onBarcodeScan() {
|
this.scanData.barcode = this.scanData.barcode.replace(/\n/g, '').trim();
|
if (!this.scanData.barcode) return;
|
this.confirmPicking();
|
},
|
|
focusBarcodeInput(selectText = false) {
|
this.$nextTick(() => {
|
const input = this.$refs.barcodeInput;
|
if (input && input.$el && input.$el.querySelector('input')) {
|
const inputEl = input.$el.querySelector('input');
|
inputEl.focus();
|
if (selectText) {
|
inputEl.select();
|
}
|
}
|
});
|
},
|
|
handlePickedSelectionChange(selection) {
|
this.selectedPickedRows = selection;
|
},
|
|
async batchCancelSelected() {
|
if (this.selectedPickedRows.length === 0) {
|
this.$message.warning('请先选择要取消的项');
|
return;
|
}
|
|
this.$confirm(`确定要取消选中的 ${this.selectedPickedRows.length} 项吗?`, '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}).then(async () => {
|
try {
|
for (const row of this.selectedPickedRows) {
|
try {
|
const res = await http.post('/api/BatchOutbound/cancel-picking', {
|
orderNo: this.scanData.orderNo,
|
batchNo: this.currentBatchNo,
|
palletCode: this.scanData.palletCode,
|
barcode: row.currentBarcode
|
});
|
if (!res.status) {
|
this.$message.warning(`取消拣选失败: ${row.currentBarcode} - ${res.message}`);
|
}
|
} catch (error) {
|
this.$message.warning(`取消拣选失败: ${row.currentBarcode} - ${error.message}`);
|
}
|
}
|
this.$message.success('批量取消完成');
|
await this.loadBatchData();
|
this.selectedPickedRows = [];
|
} catch (error) {
|
this.$message.error('批量取消操作失败');
|
}
|
});
|
},
|
|
// 重置方法
|
resetSplitForm() {
|
this.splitForm.originalBarcode = '';
|
this.splitForm.materielCode = '';
|
this.splitForm.splitQuantity = 0;
|
this.splitForm.maxQuantity = 0;
|
},
|
|
closeCustomSplitDialog() {
|
this.showCustomSplitDialog = false;
|
this.resetSplitForm();
|
},
|
|
openRevertSplitDialog() {
|
this.showRevertSplitDialog = true;
|
this.revertSplitForm.newBarcode = '';
|
},
|
|
closeRevertSplitDialog() {
|
this.showRevertSplitDialog = false;
|
this.revertSplitForm.newBarcode = '';
|
},
|
|
closeBatchReturnDialog() {
|
this.showBatchReturnDialog = false;
|
},
|
|
openEmptyPalletDialog() {
|
this.showEmptyPalletDialog = true;
|
this.emptypalletOutForm.orderNo = this.scanData.orderNo;
|
this.emptypalletOutForm.palletCode = '';
|
},
|
|
closeEmptyPalletDialog() {
|
this.showEmptyPalletDialog = false;
|
this.emptypalletOutForm.palletCode = '';
|
},
|
|
onEmptyPalletScan() {
|
if (!this.emptypalletOutForm.palletCode) return;
|
this.emptypalletOutForm.palletCode = this.emptypalletOutForm.palletCode.replace(/\n/g, '').trim();
|
}
|
}
|
})
|
</script>
|
|
<style scoped>
|
.OutboundPicking-container {
|
padding: 20px;
|
}
|
|
.batch-operations {
|
margin-bottom: 15px;
|
}
|
|
.batch-actions {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.batch-summary-area {
|
margin-bottom: 15px;
|
}
|
|
.batch-summary-info {
|
display: flex;
|
gap: 15px;
|
flex-wrap: wrap;
|
}
|
|
.scanner-form {
|
display: flex;
|
gap: 10px;
|
align-items: center;
|
flex-wrap: wrap;
|
}
|
|
.scanner-form .el-input {
|
width: 200px;
|
}
|
|
.summary-info {
|
display: flex;
|
gap: 20px;
|
flex-wrap: wrap;
|
}
|
|
.table-actions {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 10px;
|
padding: 0 10px;
|
}
|
|
.selection-count {
|
font-size: 12px;
|
color: #909399;
|
}
|
|
/* 自定义弹窗样式 */
|
.custom-dialog-overlay {
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background-color: rgba(0, 0, 0, 0.5);
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
z-index: 9999;
|
}
|
|
.custom-dialog-wrapper {
|
position: relative;
|
z-index: 10000;
|
}
|
|
.custom-dialog {
|
background: white;
|
border-radius: 4px;
|
width: 500px;
|
max-width: 90vw;
|
max-height: 90vh;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
overflow: auto;
|
}
|
|
.custom-dialog-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 20px 20px 10px;
|
border-bottom: 1px solid #ebeef5;
|
}
|
|
.custom-dialog-header h3 {
|
margin: 0;
|
color: #303133;
|
}
|
|
.close-button {
|
font-size: 18px;
|
color: #909399;
|
padding: 0;
|
width: 24px;
|
height: 24px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.close-button:hover {
|
color: #409EFF;
|
background-color: transparent;
|
}
|
|
.custom-dialog-body {
|
padding: 20px;
|
}
|
|
.custom-dialog-footer {
|
padding: 10px 20px 20px;
|
text-align: right;
|
border-top: 1px solid #ebeef5;
|
}
|
|
.custom-dialog-footer .el-button {
|
margin-left: 10px;
|
}
|
|
@media (max-width: 768px) {
|
.custom-dialog {
|
width: 95vw;
|
margin: 10px;
|
}
|
|
.scanner-form {
|
flex-direction: column;
|
align-items: stretch;
|
}
|
|
.scanner-form .el-input {
|
width: 100%;
|
}
|
}
|
</style>
|