<template>
|
<div class="OutboundPicking-container">
|
<div class="page-header">
|
<el-page-header @back="goBack">
|
<template #content>
|
<span class="title">出库拣选确认 - {{ this.$route.query.orderNo }}</span>
|
</template>
|
</el-page-header>
|
</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="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="showCustomSplitDialog" class="custom-dialog-overlay" style="z-index: 2000;">
|
<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.palletCode" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="原条码" prop="originalBarcode">
|
<div style="display: flex; align-items: center; gap: 10px;">
|
<el-input
|
v-model="splitForm.originalBarcode"
|
placeholder="扫描原条码"
|
@keyup.enter.native="onSplitBarcodeScan"
|
@change="onSplitBarcodeScan"
|
clearable
|
style="flex: 1;">
|
</el-input>
|
<!-- 新增:查看拆包链按钮 -->
|
<el-button
|
type="primary"
|
@click="viewSplitChainFromSplit(splitForm.originalBarcode)"
|
:disabled="!splitForm.originalBarcode"
|
:loading="splitChainLoading">
|
查看拆包链
|
</el-button>
|
</div>
|
</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" style="z-index: 2001;">
|
<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">
|
<div style="display: flex; align-items: center; gap: 10px;">
|
<el-input
|
v-model="revertSplitForm.newBarcode"
|
placeholder="扫描新条码"
|
@keyup.enter.native="onRevertSplitBarcodeScan"
|
@change="onRevertSplitBarcodeScan"
|
clearable
|
style="flex: 1;">
|
</el-input>
|
<!-- 新增:查看拆包链按钮 -->
|
<el-button
|
type="primary"
|
@click="viewSplitChain(revertSplitForm.newBarcode)"
|
:disabled="!revertSplitForm.newBarcode">
|
查看拆包链
|
</el-button>
|
</div>
|
</el-form-item>
|
</el-form>
|
|
<!-- 新增:拆包链简要信息显示 -->
|
<div v-if="splitChainInfo.splitChain && splitChainInfo.splitChain.length > 0"
|
style="margin-top: 15px; padding: 10px; background: #f0f9ff; border-radius: 4px;">
|
<div style="font-size: 14px; color: #606266;">
|
<div>拆包链信息: 共 {{ splitChainInfo.totalSplitTimes }} 次拆包</div>
|
<div style="margin-top: 5px;">
|
<el-tag
|
v-for="item in splitChainInfo.splitChain.slice(0, 3)"
|
:key="item.newBarcode"
|
:type="item.isReverted ? 'success' : 'primary'"
|
size="small"
|
style="margin-right: 5px;">
|
{{ item.newBarcode }} ({{ item.splitQuantity }})
|
</el-tag>
|
<span v-if="splitChainInfo.splitChain.length > 3" style="color: #909399;">
|
等 {{ splitChainInfo.splitChain.length }} 个条码
|
</span>
|
</div>
|
</div>
|
</div>
|
</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="showSplitChainDialog" class="custom-dialog-overlay" style="z-index: 2002;">
|
<div class="custom-dialog-wrapper">
|
<div class="custom-dialog" style="width: 750px;">
|
<div class="custom-dialog-header">
|
<h3>拆包链信息</h3>
|
<el-button type="text" @click="closeSplitChainDialog" class="close-button">×</el-button>
|
</div>
|
<div class="custom-dialog-body">
|
<!-- 新增:拆包链说明 -->
|
<div style="margin-bottom: 15px; padding: 10px; background: #f0f9ff; border-radius: 4px;">
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
<div>
|
<div style="font-weight: bold; color: #303133;">拆包链说明</div>
|
<div style="font-size: 12px; color: #606266; margin-top: 5px;">
|
当前显示的是从 <el-tag type="primary" size="small">{{ splitChainInfo.originalBarcode }}</el-tag> 开始的拆包链
|
<br>共 {{ splitChainInfo.totalSplitTimes }} 次拆包操作,涉及 {{ splitChainInfo.splitChain.length }} 个条码
|
</div>
|
</div>
|
<el-button
|
type="primary"
|
size="small"
|
@click="findRootChain(splitChainInfo.originalBarcode)"
|
v-if="splitChainInfo.chainType !== 'root'">
|
查找完整拆包链
|
</el-button>
|
</div>
|
</div>
|
|
<div style="margin-bottom: 15px;">
|
<el-tag type="info">总拆包次数: {{ splitChainInfo.totalSplitTimes }}</el-tag>
|
<el-tag type="warning" style="margin-left: 10px;">
|
原始条码: {{ splitChainInfo.originalBarcode }}
|
</el-tag>
|
<el-tag :type="splitChainInfo.chainType === 'root' ? 'success' : 'warning'" style="margin-left: 10px;">
|
{{ splitChainInfo.chainType === 'root' ? '完整链' : '分支链' }}
|
</el-tag>
|
</div>
|
|
<el-table :data="splitChainInfo.splitChain" border height="300">
|
<el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
|
<el-table-column prop="splitTime" label="拆包时间" width="160">
|
<template #default="scope">
|
{{ formatDateTime(scope.row.splitTime) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="originalBarcode" label="原条码" width="140">
|
<template #default="scope">
|
<el-tag
|
:type="scope.row.originalBarcode === splitChainInfo.rootBarcode ? 'success' : 'primary'"
|
size="small">
|
{{ scope.row.originalBarcode }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="newBarcode" label="新条码" width="140">
|
<template #default="scope">
|
<el-tag
|
:type="scope.row.isReverted ? 'info' : 'warning'"
|
size="small">
|
{{ scope.row.newBarcode }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="splitQuantity" label="拆包数量" width="100" align="right">
|
<template #default="scope">
|
{{ scope.row.splitQuantity.toFixed(2) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="operator" label="操作员" width="100"></el-table-column>
|
<el-table-column prop="isReverted" label="状态" width="80" align="center">
|
<template #default="scope">
|
<el-tag
|
:type="scope.row.isReverted ? 'success' : 'danger'"
|
size="small">
|
{{ scope.row.isReverted ? '已撤销' : '有效' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" width="120" align="center">
|
<template #default="scope">
|
<el-button
|
v-if="!scope.row.isReverted"
|
type="danger"
|
size="mini"
|
@click="cancelSingleSplit(scope.row.newBarcode)"
|
:disabled="hasPicked(scope.row.newBarcode)">
|
取消
|
</el-button>
|
<span v-else style="color: #909399;">已撤销</span>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 批量操作区域 -->
|
<div style="margin-top: 15px; padding: 10px; background: #f5f7fa; border-radius: 4px;">
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
<div>
|
<span style="font-size: 14px; color: #606266;">
|
批量操作: 可以取消整个拆包链或选择单个拆包记录取消
|
</span>
|
<div style="font-size: 12px; color: #909399; margin-top: 5px;">
|
完整拆包链包含从最原始条码开始的所有拆包操作
|
</div>
|
</div>
|
<div>
|
<el-button
|
type="danger"
|
@click="cancelWholeSplitChain"
|
:disabled="!canCancelWholeChain"
|
:loading="revertSplitLoading">
|
取消整个拆包链
|
</el-button>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="custom-dialog-footer">
|
<el-button @click="closeSplitChainDialog">关闭</el-button>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 批量回库弹窗 -->
|
<div v-if="showBatchReturnDialog" class="custom-dialog-overlay" style="z-index: 2003;">
|
<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.palletCode" 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" style="z-index: 2004;">
|
<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 { defineComponent } from "vue";
|
import { ElMessage } from 'element-plus'
|
import printView from "@/extension/outbound/extend/printView.vue"
|
|
export default defineComponent({
|
name: 'BatchOutboundPicking',
|
components: {printView},
|
data() {
|
return {
|
// 保持所有原始数据结构不变...
|
scanData: {
|
orderNo: '',
|
palletCode: '',
|
barcode: ''
|
},
|
unpickedList: [],
|
pickedList: [],
|
selectedPickedRows: [],
|
summary: {
|
unpickedCount: 0,
|
unpickedQuantity: 0,
|
pickedCount: 0
|
},
|
palletStatus: '未知',
|
|
// 弹窗状态 - 关键修复:只允许一个弹窗打开
|
activeDialog: null, // 'split', 'revert', 'batchReturn', 'emptyPallet', 'splitChain'
|
showCustomSplitDialog: false,
|
showRevertSplitDialog: false,
|
showBatchReturnDialog: false,
|
showEmptyPalletDialog: false,
|
showSplitChainDialog: false,
|
|
// 添加防重复点击标志
|
isOpeningDialog: false,
|
// 加载状态
|
splitLoading: false,
|
revertSplitLoading: false,
|
batchReturnLoading: false,
|
emptypalletOutLoading: false,
|
splitChainLoading: false,
|
|
// 表单数据...
|
splitForm: {
|
orderNo: '',
|
palletCode: '',
|
originalBarcode: '',
|
materielCode: '',
|
splitQuantity: 0,
|
maxQuantity: 0
|
},
|
|
revertSplitForm: {
|
newBarcode: ''
|
},
|
|
batchReturnForm: {
|
orderNo: '',
|
palletCode: '',
|
unpickedCount: 0,
|
unpickedQuantity: 0
|
},
|
|
emptypalletOutForm: {
|
orderNo: '',
|
palletCode: ''
|
},
|
|
splitChainInfo: {
|
originalBarcode: '',
|
totalSplitTimes: 0,
|
splitChain: []
|
},
|
|
// 验证规则...
|
splitFormRules: {
|
originalBarcode: [
|
{ required: true, message: '请输入原条码', trigger: 'blur' }
|
],
|
splitQuantity: [
|
{ required: true, message: '请输入拆包数量', trigger: 'blur' },
|
{ type: 'number', min: 0.01, message: '拆包数量必须大于0', trigger: 'blur' }
|
]
|
},
|
|
revertSplitFormRules: {
|
newBarcode: [
|
{ required: true, message: '请输入新条码', trigger: 'blur' }
|
]
|
},
|
|
emptypalletOutFormRules: {
|
palletCode: [
|
{ required: true, message: '请输入托盘码', trigger: 'blur' }
|
]
|
},
|
|
isProcessing: false
|
}
|
},
|
watch: {
|
// 关键修复:确保同一时间只有一个弹窗打开
|
activeDialog(newVal, oldVal) {
|
this.showCustomSplitDialog = newVal === 'split'
|
this.showRevertSplitDialog = newVal === 'revert'
|
this.showBatchReturnDialog = newVal === 'batchReturn'
|
this.showEmptyPalletDialog = newVal === 'emptyPallet'
|
this.showSplitChainDialog = newVal === 'splitChain'
|
}
|
},
|
computed: {
|
canCancelWholeChain() {
|
return this.splitChainInfo.splitChain &&
|
this.splitChainInfo.splitChain.some(item => !item.isReverted);
|
}
|
},
|
mounted() {
|
if (this.$route.query.orderNo) {
|
this.scanData.orderNo = this.$route.query.orderNo;
|
this.splitForm.orderNo = this.$route.query.orderNo;
|
this.batchReturnForm.orderNo = this.$route.query.orderNo;
|
this.emptypalletOutForm.orderNo = this.$route.query.orderNo;
|
}
|
// 使用 requestAnimationFrame 确保页面完全加载
|
requestAnimationFrame(() => {
|
if (this.$refs.palletInput) {
|
this.$refs.palletInput.focus();
|
}
|
});
|
|
},
|
methods: {
|
goBack(){
|
this.$router.back()
|
},
|
|
// 分拣相关方法
|
async confirmPicking() {
|
if (this.isProcessing) return;
|
|
if (!this.scanData.orderNo || !this.scanData.palletCode || !this.scanData.barcode) {
|
this.$message.warning('请先扫描托盘码和物料条码');
|
this.focusBarcodeInput();
|
return;
|
}
|
|
this.isProcessing = true;
|
|
try {
|
const res = await http.post('/api/OutboundBatchPicking/confirm-picking', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode,
|
barcode: this.scanData.barcode
|
});
|
if (res.status) {
|
this.$message.success('拣选确认成功');
|
this.scanData.barcode = '';
|
await this.loadPalletData();
|
if(res.data && res.data && res.data.length>0){
|
this.$refs.childs.open(res.data);
|
}
|
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() {
|
console.log('紧急修复版:打开拆包弹窗');
|
|
if (!this.scanData.palletCode) {
|
this.$message.warning('请先扫描托盘码');
|
return;
|
}
|
|
// 1. 关闭所有Vue弹窗
|
this.closeAllDialogs();
|
|
// 2. 强制从DOM中移除所有弹窗
|
setTimeout(() => {
|
const dialogs = document.querySelectorAll('.custom-dialog-overlay');
|
dialogs.forEach(dialog => {
|
if (dialog.parentNode) {
|
dialog.parentNode.removeChild(dialog);
|
}
|
});
|
|
// 如果已经存在手动弹窗,先移除
|
if (this.manualDialog && this.manualDialog.parentNode) {
|
this.manualDialog.parentNode.removeChild(this.manualDialog);
|
}
|
|
// 3. 等待一帧
|
requestAnimationFrame(() => {
|
// 4. 直接创建新弹窗,不依赖Vue的响应式系统
|
this.createManualSplitDialog();
|
});
|
}, 10);
|
},
|
|
// 创建手动拆包弹窗
|
createManualSplitDialog() {
|
const newDialog = document.createElement('div');
|
newDialog.className = 'custom-dialog-overlay emergency-fix';
|
|
// 生成随机ID用于事件绑定
|
const dialogId = 'manual-dialog-' + Date.now();
|
newDialog.id = dialogId;
|
|
// 存储引用
|
this.manualDialog = newDialog;
|
|
// 弹窗内容
|
newDialog.innerHTML = `
|
<div class="custom-dialog-wrapper">
|
<div class="custom-dialog" style="width: 500px;">
|
<div class="custom-dialog-header">
|
<h3 style="margin: 0; color: #303133;">拆包操作</h3>
|
<button class="close-button" onclick="document.getElementById('${dialogId}').remove()" style="
|
font-size: 18px;
|
color: #909399;
|
padding: 0;
|
width: 24px;
|
height: 24px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
background: none;
|
border: none;
|
cursor: pointer;
|
">×</button>
|
</div>
|
<div class="custom-dialog-body" style="padding: 20px;">
|
<div style="margin-bottom: 15px;">
|
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
<span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">订单编号:</span>
|
<input type="text" value="${this.scanData.orderNo}" disabled style="
|
flex: 1;
|
padding: 8px 12px;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
background-color: #f5f7fa;
|
color: #909399;
|
">
|
</div>
|
</div>
|
|
<div style="margin-bottom: 15px;">
|
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
<span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">托盘编号:</span>
|
<input type="text" value="${this.scanData.palletCode}" disabled style="
|
flex: 1;
|
padding: 8px 12px;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
background-color: #f5f7fa;
|
color: #909399;
|
">
|
</div>
|
</div>
|
|
<div style="margin-bottom: 15px;">
|
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
<span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">原条码:</span>
|
<div style="flex: 1; display: flex; align-items: center; gap: 10px;">
|
<input type="text" id="${dialogId}-barcode" placeholder="扫描原条码" style="
|
flex: 1;
|
padding: 8px 12px;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
">
|
<button id="${dialogId}-viewChain" style="
|
padding: 8px 16px;
|
background: #409eff;
|
color: white;
|
border: none;
|
border-radius: 4px;
|
cursor: pointer;
|
white-space: nowrap;
|
">查看拆包链</button>
|
</div>
|
</div>
|
</div>
|
|
<div style="margin-bottom: 15px;">
|
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
<span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">物料编码:</span>
|
<input type="text" id="${dialogId}-materiel" disabled style="
|
flex: 1;
|
padding: 8px 12px;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
background-color: #f5f7fa;
|
color: #909399;
|
">
|
</div>
|
</div>
|
|
<div style="margin-bottom: 15px;">
|
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
<span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">剩余数量:</span>
|
<input type="text" id="${dialogId}-remain" disabled style="
|
flex: 1;
|
padding: 8px 12px;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
background-color: #f5f7fa;
|
color: #909399;
|
">
|
</div>
|
</div>
|
|
<div style="margin-bottom: 15px;">
|
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
<span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">拆包数量:</span>
|
<div style="flex: 1;">
|
<input type="number" id="${dialogId}-splitQty" value="1" min="0.01" step="0.01" style="
|
width: 100%;
|
padding: 8px 12px;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
">
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<div class="custom-dialog-footer" style="
|
padding: 10px 20px 20px;
|
text-align: right;
|
border-top: 1px solid #ebeef5;
|
">
|
<button id="${dialogId}-cancel" style="
|
padding: 9px 15px;
|
background: white;
|
color: #606266;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
cursor: pointer;
|
margin-right: 10px;
|
">取消</button>
|
<button id="${dialogId}-confirm" style="
|
padding: 9px 15px;
|
background: #409eff;
|
color: white;
|
border: none;
|
border-radius: 4px;
|
cursor: pointer;
|
">确认拆包</button>
|
</div>
|
</div>
|
</div>
|
`;
|
|
// 添加样式
|
newDialog.style.cssText = `
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: rgba(0,0,0,0.5);
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
z-index: 999999;
|
`;
|
|
// 弹窗容器样式
|
const wrapper = newDialog.querySelector('.custom-dialog-wrapper');
|
if (wrapper) {
|
wrapper.style.position = 'relative';
|
wrapper.style.zIndex = '1000000';
|
}
|
|
// 弹窗内容样式
|
const dialog = newDialog.querySelector('.custom-dialog');
|
if (dialog) {
|
dialog.style.background = 'white';
|
dialog.style.borderRadius = '4px';
|
dialog.style.maxWidth = '90vw';
|
dialog.style.maxHeight = '90vh';
|
dialog.style.boxShadow = '0 2px 12px 0 rgba(0, 0, 0, 0.1)';
|
dialog.style.overflow = 'auto';
|
}
|
|
// 弹窗头部样式
|
const header = newDialog.querySelector('.custom-dialog-header');
|
if (header) {
|
header.style.display = 'flex';
|
header.style.justifyContent = 'space-between';
|
header.style.alignItems = 'center';
|
header.style.padding = '20px 20px 10px';
|
header.style.borderBottom = '1px solid #ebeef5';
|
}
|
|
document.body.appendChild(newDialog);
|
console.log('紧急弹窗已创建');
|
|
// 绑定事件
|
this.bindManualDialogEvents(dialogId);
|
|
// 自动聚焦到条码输入框
|
setTimeout(() => {
|
const barcodeInput = document.getElementById(`${dialogId}-barcode`);
|
if (barcodeInput) {
|
barcodeInput.focus();
|
// 添加回车键监听
|
barcodeInput.addEventListener('keyup', (event) => {
|
if (event.key === 'Enter') {
|
this.onManualSplitBarcodeScan(dialogId);
|
}
|
});
|
}
|
}, 100);
|
},
|
|
// 绑定手动弹窗事件
|
bindManualDialogEvents(dialogId) {
|
const vm = this; // 保存Vue实例引用
|
|
// 查看拆包链按钮
|
const viewChainBtn = document.getElementById(`${dialogId}-viewChain`);
|
if (viewChainBtn) {
|
viewChainBtn.onclick = () => {
|
const barcodeInput = document.getElementById(`${dialogId}-barcode`);
|
if (barcodeInput && barcodeInput.value.trim()) {
|
vm.viewSplitChainFromManualDialog(barcodeInput.value.trim(), dialogId);
|
} else {
|
ElMessage.warning('请先输入条码');
|
}
|
};
|
}
|
|
// 取消按钮
|
const cancelBtn = document.getElementById(`${dialogId}-cancel`);
|
if (cancelBtn) {
|
cancelBtn.onclick = () => {
|
const dialog = document.getElementById(dialogId);
|
if (dialog && dialog.parentNode) {
|
dialog.parentNode.removeChild(dialog);
|
}
|
};
|
}
|
|
// 确认拆包按钮
|
const confirmBtn = document.getElementById(`${dialogId}-confirm`);
|
if (confirmBtn) {
|
confirmBtn.onclick = () => {
|
vm.handleManualSplitPackage(dialogId);
|
};
|
}
|
|
// 条码输入框变化事件
|
const barcodeInput = document.getElementById(`${dialogId}-barcode`);
|
if (barcodeInput) {
|
// 防抖处理
|
let timeout;
|
barcodeInput.addEventListener('input', () => {
|
clearTimeout(timeout);
|
timeout = setTimeout(() => {
|
if (barcodeInput.value.trim()) {
|
vm.onManualSplitBarcodeScan(dialogId);
|
}
|
}, 500);
|
});
|
}
|
},
|
|
// 手动弹窗的条码扫描处理
|
async onManualSplitBarcodeScan(dialogId) {
|
const barcodeInput = document.getElementById(`${dialogId}-barcode`);
|
if (!barcodeInput || !barcodeInput.value.trim()) return;
|
|
const barcode = barcodeInput.value.trim();
|
|
try {
|
const res = await http.post('/api/OutboundBatchPicking/split-package-info', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode,
|
barcode: barcode
|
});
|
|
if (res.status) {
|
// 更新物料编码
|
const materielInput = document.getElementById(`${dialogId}-materiel`);
|
if (materielInput) {
|
materielInput.value = res.data.materielCode || '';
|
}
|
|
// 更新剩余数量
|
const remainInput = document.getElementById(`${dialogId}-remain`);
|
if (remainInput) {
|
remainInput.value = res.data.remainQuantity || 0;
|
}
|
|
// 更新拆包数量(默认为1,不超过剩余数量)
|
const splitQtyInput = document.getElementById(`${dialogId}-splitQty`);
|
if (splitQtyInput) {
|
const maxQty = res.data.remainQuantity || 0;
|
splitQtyInput.max = maxQty;
|
const currentVal = parseFloat(splitQtyInput.value) || 1;
|
if (currentVal > maxQty) {
|
splitQtyInput.value = Math.min(1, maxQty);
|
}
|
}
|
} else {
|
ElMessage.error(res.message || '获取拆包信息失败');
|
}
|
} catch (error) {
|
console.error('获取拆包信息失败:', error);
|
ElMessage.error('获取拆包信息失败');
|
}
|
},
|
|
// 从手动弹窗查看拆包链
|
viewSplitChainFromManualDialog(barcode, dialogId) {
|
// 先关闭手动弹窗
|
const dialog = document.getElementById(dialogId);
|
if (dialog && dialog.parentNode) {
|
dialog.parentNode.removeChild(dialog);
|
}
|
|
// 延迟一下,然后打开Vue的拆包链弹窗
|
setTimeout(() => {
|
this.viewSplitChain(barcode);
|
}, 50);
|
},
|
|
// 处理手动弹窗的拆包操作
|
async handleManualSplitPackage(dialogId) {
|
const barcodeInput = document.getElementById(`${dialogId}-barcode`);
|
const splitQtyInput = document.getElementById(`${dialogId}-splitQty`);
|
|
if (!barcodeInput || !barcodeInput.value.trim()) {
|
ElMessage.warning('请输入原条码');
|
return;
|
}
|
|
if (!splitQtyInput || !splitQtyInput.value || parseFloat(splitQtyInput.value) <= 0) {
|
ElMessage.warning('请输入有效的拆包数量');
|
return;
|
}
|
|
const originalBarcode = barcodeInput.value.trim();
|
const splitQuantity = parseFloat(splitQtyInput.value);
|
|
try {
|
// 显示加载状态
|
const confirmBtn = document.getElementById(`${dialogId}-confirm`);
|
if (confirmBtn) {
|
confirmBtn.disabled = true;
|
confirmBtn.textContent = '处理中...';
|
}
|
|
const res = await http.post('/api/OutboundBatchPicking/split-package', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode,
|
originalBarcode: originalBarcode,
|
splitQuantity: splitQuantity
|
});
|
|
if (res.status) {
|
ElMessage.success('拆包成功');
|
|
// 关闭手动弹窗
|
const dialog = document.getElementById(dialogId);
|
if (dialog && dialog.parentNode) {
|
dialog.parentNode.removeChild(dialog);
|
}
|
|
// 重新加载数据
|
await this.loadPalletData();
|
} else {
|
ElMessage.error(res.message || '拆包失败');
|
|
// 恢复按钮状态
|
if (confirmBtn) {
|
confirmBtn.disabled = false;
|
confirmBtn.textContent = '确认拆包';
|
}
|
}
|
} catch (error) {
|
console.error('拆包失败:', error);
|
ElMessage.error('拆包失败');
|
|
// 恢复按钮状态
|
const confirmBtn = document.getElementById(`${dialogId}-confirm`);
|
if (confirmBtn) {
|
confirmBtn.disabled = false;
|
confirmBtn.textContent = '确认拆包';
|
}
|
}
|
},
|
closeAllDialogsImmediately() {
|
console.log('立即关闭所有弹窗');
|
|
// 直接设置为 false,不等待任何异步操作
|
this.showCustomSplitDialog = false;
|
this.showRevertSplitDialog = false;
|
this.showBatchReturnDialog = false;
|
this.showEmptyPalletDialog = false;
|
this.showSplitChainDialog = false;
|
|
// 强制DOM更新
|
this.$forceUpdate();
|
},
|
|
async onSplitBarcodeScan() {
|
if (!this.splitForm.originalBarcode) return;
|
this.splitForm.originalBarcode = this.splitForm.originalBarcode.replace(/\n/g, '').trim();
|
|
try {
|
const res = await http.post('/api/OutboundBatchPicking/split-package-info', {
|
orderNo: this.splitForm.orderNo,
|
palletCode: this.splitForm.palletCode,
|
barcode: this.splitForm.originalBarcode
|
});
|
|
if (res.status) {
|
if(res.data && res.data.length>0){
|
this.$refs.childs.open(res.data);
|
}
|
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/OutboundBatchPicking/split-package', {
|
orderNo: this.splitForm.orderNo,
|
palletCode: this.splitForm.palletCode,
|
originalBarcode: this.splitForm.originalBarcode,
|
splitQuantity: this.splitForm.splitQuantity
|
});
|
if (res.status) {
|
this.$message.success('拆包成功');
|
this.closeAllDialogs();
|
await this.loadPalletData();
|
} else {
|
this.$message.error(res.message || '拆包失败');
|
}
|
} catch (error) {
|
this.$message.error('拆包失败');
|
} finally {
|
this.splitLoading = false;
|
}
|
}
|
});
|
}
|
},
|
|
async viewSplitChainFromSplit(barcode) {
|
if (!barcode) {
|
this.$message.warning('请先输入条码');
|
return;
|
}
|
|
this.closeAllDialogs();
|
|
setTimeout(() => {
|
this.viewSplitChain(barcode);
|
}, 50);
|
},
|
|
// 撤销拆包
|
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/OutboundBatchPicking/cancel-split', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode,
|
newBarcode: this.revertSplitForm.newBarcode
|
});
|
if (res.status) {
|
this.$message.success('撤销拆包成功');
|
this.closeAllDialogs();
|
await this.loadPalletData();
|
} else {
|
this.$message.error(res.message || '撤销拆包失败');
|
}
|
} catch (error) {
|
this.$message.error('撤销拆包失败');
|
} finally {
|
this.revertSplitLoading = false;
|
}
|
}
|
});
|
}
|
},
|
|
async findRootChain(currentBarcode) {
|
this.splitChainLoading = true;
|
try {
|
const res = await http.post('/api/OutboundBatchPicking/find-root-split-chain', {
|
orderNo: this.scanData.orderNo,
|
barcode: currentBarcode
|
});
|
|
if (res.status) {
|
this.splitChainInfo = res.data;
|
this.$message.success('已加载完整拆包链');
|
} else {
|
this.$message.error(res.message || '查找完整拆包链失败');
|
}
|
} catch (error) {
|
this.$message.error('查找完整拆包链失败');
|
} finally {
|
this.splitChainLoading = false;
|
}
|
},
|
|
// 查看拆包链信息
|
async viewSplitChain(barcode) {
|
if (!barcode) {
|
this.$message.warning('请先输入条码');
|
return;
|
}
|
|
this.splitChainLoading = true;
|
try {
|
const res = await http.post('/api/OutboundBatchPicking/split-package-chain-info', {
|
orderNo: this.scanData.orderNo,
|
barcode: barcode
|
});
|
|
if (res.status) {
|
this.splitChainInfo = res.data;
|
this.activeDialog = 'splitChain';
|
} else {
|
this.$message.error(res.message || '获取拆包链信息失败');
|
}
|
} catch (error) {
|
this.$message.error('获取拆包链信息失败');
|
} finally {
|
this.splitChainLoading = false;
|
}
|
},
|
|
// 关闭拆包链信息弹窗
|
closeSplitChainDialog() {
|
this.showSplitChainDialog = false;
|
},
|
|
// 取消单个拆包记录
|
async cancelSingleSplit(newBarcode) {
|
const originalBarcode = this.splitChainInfo.originalBarcode;
|
|
try {
|
await this.$confirm(
|
`确定要取消条码 ${newBarcode} 的拆包操作吗?`,
|
'取消单个拆包',
|
{
|
confirmButtonText: '确定取消',
|
cancelButtonText: '再想想',
|
type: 'warning'
|
}
|
);
|
|
this.revertSplitLoading = true;
|
|
const res = await http.post('/api/OutboundBatchPicking/cancel-split', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode,
|
newBarcode: newBarcode
|
});
|
|
if (res.status) {
|
this.$message.success('取消拆包成功');
|
await this.loadPalletData();
|
this.closeAllDialogs();
|
setTimeout(() => {
|
this.viewSplitChain(originalBarcode);
|
}, 50);
|
} else {
|
this.$message.error(res.message || '取消拆包失败');
|
}
|
} catch (error) {
|
if (error !== 'cancel') {
|
this.$message.error('取消拆包失败');
|
}
|
} finally {
|
this.revertSplitLoading = false;
|
}
|
},
|
|
// 取消整个拆包链
|
async cancelWholeSplitChain() {
|
try {
|
await this.$confirm(
|
`确定要取消整个拆包链吗?\n这将取消从条码 ${this.splitChainInfo.originalBarcode} 开始的所有拆包操作。`,
|
'取消拆包链确认',
|
{
|
confirmButtonText: '确定取消',
|
cancelButtonText: '再想想',
|
type: 'warning',
|
center: true,
|
closeOnClickModal: false
|
}
|
);
|
|
this.revertSplitLoading = true;
|
|
const res = await http.post('/api/OutboundBatchPicking/cancel-split-chain', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode,
|
startBarcode: this.splitChainInfo.originalBarcode
|
});
|
|
if (res.status) {
|
this.$message.success('取消拆包链成功');
|
this.closeAllDialogs();
|
await this.loadPalletData();
|
} else {
|
this.$message.error(res.message || '取消拆包链失败');
|
}
|
} catch (error) {
|
if (error !== 'cancel') {
|
this.$message.error('取消拆包链失败: ' + error.message);
|
}
|
} finally {
|
this.revertSplitLoading = false;
|
}
|
},
|
|
// 检查条码是否已被分拣
|
hasPicked(barcode) {
|
return this.pickedList.some(item => item.currentBarcode === barcode);
|
},
|
|
// 格式化日期时间
|
formatDateTime(dateTime) {
|
if (!dateTime) return '';
|
const date = new Date(dateTime);
|
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`;
|
},
|
|
// 关键修复:新增关闭所有弹窗的方法
|
closeAllDialogs() {
|
this.activeDialog = null;
|
// 确保所有弹窗状态都被重置
|
// 关闭Vue弹窗
|
this.showCustomSplitDialog = false;
|
this.showRevertSplitDialog = false;
|
this.showBatchReturnDialog = false;
|
this.showEmptyPalletDialog = false;
|
this.showSplitChainDialog = false;
|
|
// 关闭手动弹窗
|
if (this.manualDialog && this.manualDialog.parentNode) {
|
this.manualDialog.parentNode.removeChild(this.manualDialog);
|
this.manualDialog = null;
|
}
|
|
// 移除所有紧急弹窗
|
const emergencyDialogs = document.querySelectorAll('.emergency-fix');
|
emergencyDialogs.forEach(dialog => {
|
if (dialog.parentNode) {
|
dialog.parentNode.removeChild(dialog);
|
}
|
});
|
},
|
|
// 回库相关方法
|
openBatchReturnDialog() {
|
if (!this.scanData.palletCode) {
|
this.$message.warning('请先扫描托盘码');
|
return;
|
}
|
|
if (this.isOpeningDialog) return;
|
|
this.isOpeningDialog = true;
|
|
setTimeout(() => {
|
this.closeAllDialogsImmediately();
|
|
requestAnimationFrame(() => {
|
this.showBatchReturnDialog = true;
|
this.batchReturnForm.orderNo = this.scanData.orderNo;
|
this.batchReturnForm.palletCode = this.scanData.palletCode;
|
this.batchReturnForm.unpickedCount = this.summary.unpickedCount;
|
this.batchReturnForm.unpickedQuantity = this.summary.unpickedQuantity;
|
|
this.$nextTick(() => {
|
this.isOpeningDialog = false;
|
});
|
});
|
}, 0);
|
},
|
|
async handleBatchReturnConfirm() {
|
this.batchReturnLoading = true;
|
try {
|
const res = await http.post('/api/OutboundBatchPicking/return-stock', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode
|
});
|
if (res.status) {
|
this.$message.success('回库成功');
|
this.closeAllDialogs();
|
await this.loadPalletData();
|
} else {
|
this.$message.error(res.message || '回库失败');
|
}
|
} catch (error) {
|
this.$message.error('回库失败');
|
} finally {
|
this.batchReturnLoading = false;
|
}
|
},
|
|
// 取空箱方法
|
handleEmptyPallet() {
|
if (this.isOpeningDialog) return;
|
|
this.isOpeningDialog = true;
|
|
setTimeout(() => {
|
this.closeAllDialogsImmediately();
|
|
requestAnimationFrame(() => {
|
this.showEmptyPalletDialog = true;
|
this.emptypalletOutForm.orderNo = this.scanData.orderNo;
|
this.emptypalletOutForm.palletCode = '';
|
|
this.$nextTick(() => {
|
this.isOpeningDialog = false;
|
});
|
});
|
}, 0);
|
},
|
|
async handleEmptyPalletConfirm() {
|
this.emptypalletOutLoading = true;
|
try {
|
const res = await http.post('/api/OutboundBatchPicking/remove-empty-pallet', {
|
orderNo: this.emptypalletOutForm.orderNo,
|
palletCode: this.emptypalletOutForm.palletCode
|
});
|
if (res.status) {
|
this.$message.success('取走空箱成功');
|
this.closeAllDialogs();
|
await this.loadPalletData();
|
} else {
|
this.$message.error(res.message || '取走空箱失败');
|
}
|
} catch (error) {
|
this.$message.error('取走空箱失败');
|
} finally {
|
this.emptypalletOutLoading = false;
|
}
|
},
|
|
// 数据加载方法
|
async loadPalletData() {
|
if (!this.scanData.orderNo || !this.scanData.palletCode) return;
|
|
try {
|
await this.loadUnpickedList();
|
await this.loadPickedList();
|
await this.loadPalletStatus();
|
} catch (error) {
|
console.error('加载托盘数据失败:', error);
|
}
|
},
|
|
async loadUnpickedList() {
|
try {
|
const res = await http.post('/api/OutboundBatchPicking/pallet-locks', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode
|
});
|
if (res.status) {
|
this.unpickedList = (res.data || []).filter(item => item.canPick === true);
|
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/OutboundBatchPicking/pallet-picked-list', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode
|
});
|
if (res.status) {
|
this.pickedList = res.data.map(item => ({
|
...item,
|
currentBarcode: item.barcode
|
}));
|
this.summary.pickedCount = this.pickedList.length;
|
}
|
} catch (error) {
|
this.$message.error('加载已拣选列表失败');
|
}
|
},
|
|
async loadPalletStatus() {
|
try {
|
const res = await http.post('/api/OutboundBatchPicking/pallet-status', {
|
orderNo: this.scanData.orderNo,
|
palletCode: this.scanData.palletCode
|
});
|
if (res.status) {
|
this.palletStatus = res.data.statusText || '未知';
|
}
|
} catch (error) {
|
this.palletStatus = '未知';
|
}
|
},
|
|
// 扫码相关方法
|
onPalletScan() {
|
this.scanData.palletCode = this.scanData.palletCode.replace(/\n/g, '').trim();
|
if (!this.scanData.palletCode) return;
|
|
this.loadPalletData();
|
this.$nextTick(() => {
|
this.$refs.barcodeInput.focus();
|
});
|
},
|
|
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/OutboundBatchPicking/cancel-picking', {
|
orderNo: this.scanData.orderNo,
|
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.loadPalletData();
|
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() {
|
if (this.isOpeningDialog) return;
|
|
this.isOpeningDialog = true;
|
|
setTimeout(() => {
|
this.closeAllDialogsImmediately();
|
|
requestAnimationFrame(() => {
|
this.showRevertSplitDialog = true;
|
this.revertSplitForm.newBarcode = '';
|
|
this.$nextTick(() => {
|
this.isOpeningDialog = false;
|
});
|
});
|
}, 0);
|
},
|
|
closeRevertSplitDialog() {
|
this.showRevertSplitDialog = false;
|
this.revertSplitForm.newBarcode = '';
|
},
|
|
closeBatchReturnDialog() {
|
this.showBatchReturnDialog = false;
|
},
|
|
onEmptyPalletScan() {
|
if (!this.emptypalletOutForm.palletCode) return;
|
this.emptypalletOutForm.palletCode = this.emptypalletOutForm.palletCode.replace(/\n/g, '').trim();
|
},
|
|
closeEmptyPalletDialog() {
|
this.showEmptyPalletDialog = false;
|
this.emptypalletOutForm.palletCode = '';
|
},
|
|
parentcall() {
|
// 打印回调
|
}
|
}
|
})
|
</script>
|
|
<style scoped>
|
.OutboundPicking-container {
|
padding: 20px;
|
}
|
|
.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; /* 提高z-index确保在最上层 */
|
}
|
|
.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%;
|
}
|
}
|
|
/* 原有的样式保持不变 */
|
.OutboundPicking-container {
|
padding: 20px;
|
}
|
|
.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: 2000;
|
}
|
|
.custom-dialog-wrapper {
|
position: relative;
|
z-index: 2001;
|
}
|
|
.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%;
|
}
|
}
|
|
/* 新增:手动弹窗的按钮悬停效果 */
|
:deep(button) {
|
transition: all 0.3s;
|
}
|
|
:deep(button:hover) {
|
opacity: 0.8;
|
}
|
|
:deep(button:active) {
|
opacity: 0.6;
|
}
|
</style>
|