You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

771 lines
20 KiB

<template>
<div class="pda-container">
<!-- 头部栏 -->
<div class="header-bar">
<div class="header-left" @click="$router.back()">
<i class="el-icon-arrow-left"></i>
<span>拆合组托</span>
</div>
<div class="header-right" @click="$router.push({ path: '/' })">
首页
</div>
</div>
<!-- 搜索框 -->
<div class="search-container">
<el-input
v-model="scanCode"
placeholder="请扫描标签条码"
prefix-icon="el-icon-search"
@keyup.enter.native="handleScan"
ref="scanInput"
clearable
/>
</div>
<!-- 标签信息卡片 -->
<div class="label-info-card" v-if="currentLabel.labelCode">
<div class="info-row">
<span class="info-label">标签条码</span>
<span class="info-value">{{ currentLabel.labelCode || ''}}</span>
</div>
<div class="info-row">
<span class="info-label">标签类型</span>
<span class="info-value">{{ currentLabel.labelType || '' }}</span>
</div>
<div class="info-row">
<span class="info-label">标签张数</span>
<span class="info-value">{{ currentLabel.labelQty || ''}}</span>
</div>
<div class="info-row">
<span class="info-label">标签数量</span>
<span class="info-value">{{ currentLabel.qtyOnHand || ''}}</span>
</div>
<div class="info-row">
<span class="info-label">所在仓库</span>
<span class="info-value">{{ currentLabel.warehouseName || ''}}</span>
</div>
<div class="info-row">
<span class="info-label">所在库位</span>
<span class="info-value">{{ currentLabel.locationId || ''}}</span>
</div>
<div class="info-row">
<span class="info-label">标签状态</span>
<span class="info-value" :class="currentLabel.status === '冻结' ? 'status-frozen' : 'status-normal'">{{ currentLabel.status || ''}}</span>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons" v-if="currentLabel.labelCode">
<button class="action-btn split-btn" @click="showSplitDialog">
拆分
</button>
<button class="action-btn merge-btn" @click="showMergeDialog">
合并
</button>
</div>
<!-- 拆分对话框 -->
<div v-if="splitDialogVisible" class="dialog-overlay">
<div class="dialog-modal">
<div class="dialog-header">
<span class="dialog-title">标签的拆分</span>
</div>
<div class="dialog-body">
<div class="split-input-section">
<el-input v-model="splitQuantity" placeholder="请输入拆分数量" type="number" class="split-input inlineNumber numInput"/>
</div>
</div>
<div class="dialog-footer">
<button class="btn-split" @click="confirmSplit" :disabled="!splitQuantity || splitQuantity <= 0">
拆分
</button>
<button class="btn-cancel" @click="closeSplitDialog">
取消
</button>
</div>
</div>
</div>
<!-- 合并对话框 -->
<div v-if="mergeDialogVisible" class="dialog-overlay">
<div class="dialog-modal">
<div class="dialog-header">
<span class="dialog-title">标签的合并</span>
</div>
<div class="dialog-body">
<div class="merge-input-section">
<el-input
v-model="mergeTargetCode"
placeholder="请扫描合并标签"
prefix-icon="el-icon-search"
class="merge-input"
ref="mergeInput"
/>
</div>
</div>
<div class="dialog-footer">
<button class="btn-merge" @click="confirmMerge" :disabled="!mergeTargetCode.trim()">
合并
</button>
<button class="btn-cancel" @click="closeMergeDialog">
取消
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { getStockInfoByLabelCode, splitLabel, mergeLabel, getUserDefaultPrinter } from "@/api/label-split-merge/label-split-merge.js";
import { getCurrentWarehouse } from '@/utils'
import getLodop from '@/utils/LodopFuncs.js';
import labelPrintTemplates from '@/mixins/labelPrintTemplates.js';
export default {
mixins: [labelPrintTemplates],
data() {
return {
scanCode: '',
currentLabel: {},
splitDialogVisible: false,
mergeDialogVisible: false,
splitQuantity: '',
mergeTargetCode: '',
mergeTargetLabel: {}
};
},
methods: {
// 处理扫描
handleScan() {
if (!this.scanCode.trim()) {
return;
}
this.getStockInfo(this.scanCode.trim());
this.scanCode = '';
},
// 获取库存信息
getStockInfo(labelCode) {
const params = {
labelCode: labelCode,
site: localStorage.getItem('site'),
warehouseId: getCurrentWarehouse()
};
getStockInfoByLabelCode(params).then(({ data }) => {
if (data && data.code === 0) {
this.currentLabel = data.data;
// this.$message.success('获取标签信息成功');
} else {
this.$message.error(data.msg || '未找到该标签的库存信息');
this.currentLabel = {};
}
}).catch(error => {
console.error('获取库存信息失败:', error);
this.$message.error('获取库存信息失败');
this.currentLabel = {};
});
},
// 显示拆分对话框
showSplitDialog() {
if (this.currentLabel.qtyOnHand <= 1) {
this.$message.warning('标签数量必须大于1才能拆分');
return;
}
this.splitDialogVisible = true;
this.splitQuantity = '';
},
// 关闭拆分对话框
closeSplitDialog() {
this.splitDialogVisible = false;
this.splitQuantity = '';
},
// 确认拆分
confirmSplit() {
const splitQty = parseFloat(this.splitQuantity);
const currentQty = parseFloat(this.currentLabel.qtyOnHand);
if (!splitQty || splitQty <= 0) {
this.$message.warning('请输入有效的拆分数量');
return;
}
if (splitQty >= currentQty) {
this.$message.warning('拆分数量必须小于当前数量');
return;
}
const params = {
site: localStorage.getItem('site'),
buNo: this.currentLabel.buNo,
warehouseId: getCurrentWarehouse(),
originalLabelCode: this.currentLabel.labelCode,
wdr: this.currentLabel.wdr,
partNo: this.currentLabel.partNo,
batchNo: this.currentLabel.batchNo,
locationId: this.currentLabel.locationId,
originalQuantity: currentQty,
splitQuantity: splitQty,
labelTypeTb: this.currentLabel.labelTypeTb,
labelType: this.currentLabel.labelType,
freezeFlag: this.currentLabel.freezeFlag,
manufactureDate: this.currentLabel.productionDate,
expiredDate: this.currentLabel.expiryDate,
orderref1: this.currentLabel.orderref1,
orderref2: this.currentLabel.orderref2,
orderref3: this.currentLabel.orderref3,
status: this.currentLabel.status,
statusTb: this.currentLabel.statusTb
};
splitLabel(params).then(async ({ data }) => {
if (data && data.code === 0) {
this.$message.success(`拆分成功!新标签: ${data.data.newLabelCode}`);
this.closeSplitDialog();
// 自动打印标签(拆分打印两张:原标签和新标签)
const printList = data.data.printList || [];
if (printList.length > 0) {
await this.printLabelsWithTemplate(printList);
}
// 刷新当前标签信息
this.getStockInfo(this.currentLabel.labelCode);
} else {
this.$message.error(data.msg || '拆分失败');
}
}).catch(error => {
console.error('拆分失败:', error);
this.$message.error('拆分失败');
});
},
// 显示合并对话框
showMergeDialog() {
this.mergeDialogVisible = true;
this.mergeTargetCode = '';
this.mergeTargetLabel = {};
this.$nextTick(() => {
if (this.$refs.mergeInput) {
this.$refs.mergeInput.focus();
}
});
},
// 关闭合并对话框
closeMergeDialog() {
this.mergeDialogVisible = false;
this.mergeTargetCode = '';
this.mergeTargetLabel = {};
},
// 确认合并
confirmMerge() {
if (!this.mergeTargetCode.trim()) {
this.$message.warning('请扫描目标标签');
return;
}
if (this.mergeTargetCode.trim() === this.currentLabel.labelCode) {
this.$message.warning('不能合并到自己');
return;
}
// 在合并前再次验证目标标签
const params = {
labelCode: this.mergeTargetCode.trim(),
site: localStorage.getItem('site'),
warehouseId: getCurrentWarehouse()
};
getStockInfoByLabelCode(params).then(({ data }) => {
if (data && data.code === 0) {
const targetLabel = data.data;
// 检查是否可以合并(同物料、同批次)
if (targetLabel.partNo !== this.currentLabel.partNo) {
this.$message.error('不同物料不能合并');
return;
}
if (targetLabel.batchNo !== this.currentLabel.batchNo) {
this.$message.error('不同批次不能合并');
return;
}
// 验证通过,执行合并
const mergeParams = {
site: localStorage.getItem('site'),
buNo: this.currentLabel.buNo,
warehouseId: getCurrentWarehouse(),
targetLabelCode: targetLabel.labelCode,
sourceLabelCode: this.currentLabel.labelCode,
partNo: this.currentLabel.partNo,
batchNo: this.currentLabel.batchNo,
locationId: this.currentLabel.locationId,
targetQuantity: parseFloat(targetLabel.qtyOnHand),
sourceQuantity: parseFloat(this.currentLabel.qtyOnHand),
status: this.currentLabel.status,
statusTb: this.currentLabel.statusTb
};
mergeLabel(mergeParams).then(async ({ data }) => {
if (data && data.code === 0) {
this.$message.success('合并成功!');
this.closeMergeDialog();
// 自动打印标签(合并只打印一张:源标签/合并后的标签)
const printList = data.data.printList || [];
if (printList.length > 0) {
await this.printLabelsWithTemplate(printList);
}
// 清空当前标签信息,因为已经合并出库
this.currentLabel = {};
// 聚焦扫描框
this.$nextTick(() => {
if (this.$refs.scanInput) {
this.$refs.scanInput.focus();
}
});
} else {
this.$message.error(data.msg || '合并失败');
}
}).catch(error => {
console.error('合并失败:', error);
this.$message.error('合并失败');
});
} else {
this.$message.error(data.msg || '未找到目标标签的库存信息');
}
}).catch(error => {
console.error('获取目标标签信息失败:', error);
this.$message.error('获取目标标签信息失败');
});
},
/**
* 获取用户默认打印机配置
*/
async fetchUserDefaultPrinter(labelNo) {
try {
const params = {
userName: localStorage.getItem('userName'),
labelNo: labelNo || ''
};
const { data } = await getUserDefaultPrinter(params);
if (data && data.code === 0 && data.printerName) {
return {
printerName: data.printerName,
printerIp: data.printerIp,
labelNo: data.labelNo
};
}
return null;
} catch (error) {
console.error('获取用户打印机配置失败:', error);
return null;
}
},
/**
* 使用模板打印标签
* @param {Array} printList - 打印数据列表(存储过程UspPartLabelTemplate返回)
*/
async printLabelsWithTemplate(printList) {
try {
// 1. 获取 LODOP 打印控件
const LODOP = getLodop();
if (!LODOP) {
console.warn('无法连接到打印控件,跳过打印');
this.$message.warning('无法连接到打印控件,请确保已安装并启动打印服务');
return;
}
// 2. 检测打印机数量
const printerCount = LODOP.GET_PRINTER_COUNT();
if (printerCount === 0) {
console.warn('未检测到打印机,跳过打印');
this.$message.warning('未检测到打印机');
return;
}
// 3. 获取用户配置的打印机
const firstLabel = printList[0] || {};
const printerConfig = await this.fetchUserDefaultPrinter(firstLabel.labelNo);
let printerName = null;
if (printerConfig && printerConfig.printerName) {
printerName = printerConfig.printerName;
console.log('使用用户配置的打印机:', printerName);
} else {
console.warn('未找到用户打印机配置,跳过打印');
this.$message.warning('未配置用户打印机,请在系统中配置默认打印机后再打印');
return;
}
// 4. 执行打印
await this.executePrintWithTemplate(LODOP, printList, printerName);
this.$message.success('标签打印任务已发送!');
} catch (error) {
console.error('模板打印失败:', error);
this.$message.warning('标签打印失败,请手动打印');
}
},
/**
* 执行模板打印
* @param {Object} LODOP - 打印控件对象
* @param {Array} printDataList - 打印数据列表
* @param {String} printerName - 用户配置的打印机名称(可选)
*/
async executePrintWithTemplate(LODOP, printDataList, printerName) {
console.log('开始打印,标签数量:', printDataList.length, '打印机:', printerName || '默认', '标签数据:', printDataList);
// 循环打印每个标签(每个标签单独打印一次)
for (let i = 0; i < printDataList.length; i++) {
const printData = printDataList[i];
// 获取标签模板编号(存储过程返回)
const labelNo = printData.labelNo;
// 每个标签单独初始化一个打印任务
LODOP.PRINT_INIT('拆合组托标签打印_' + (i + 1));
// 设置用户配置的打印机(如果有)
if (printerName) {
LODOP.SET_PRINTER_INDEX(printerName);
}
// 设置打印模式
LODOP.SET_PRINT_MODE("PRINT_NOCOLLATE", true);
// 根据标签模板编号调用对应的打印方法
if (labelNo === 'A001') {
await this.printLabelA001(LODOP, printData, false);
} else if (labelNo === 'A002') {
this.printLabelA002(LODOP, printData, false);
} else if (labelNo === 'A003') {
this.printLabelA003(LODOP, printData, false);
} else {
// 默认使用 A001 模板
console.warn('未知标签模板:', labelNo, ',使用默认模板 A001');
await this.printLabelA001(LODOP, printData, false);
}
// 执行打印
LODOP.PRINT();
}
}
},
mounted() {
// 聚焦扫描框
this.$nextTick(() => {
if (this.$refs.scanInput) {
this.$refs.scanInput.focus();
}
});
}
};
</script>
<style scoped>
.pda-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
background: #f5f5f5;
}
/* 头部栏 */
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
background: #17B3A3;
color: white;
height: 40px;
min-height: 40px;
}
.header-left {
display: flex;
align-items: center;
cursor: pointer;
font-size: 16px;
font-weight: 500;
}
.header-left i {
margin-right: 8px;
font-size: 18px;
}
.header-right {
cursor: pointer;
font-size: 16px;
font-weight: 500;
}
/* 搜索容器 */
.search-container {
padding: 12px 16px;
background: white;
}
.search-container .el-input {
width: 100%;
}
/* 标签信息卡片 */
.label-info-card {
background: white;
margin: 8px 16px;
padding: 16px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
flex: 1;
overflow-y: auto;
}
.info-row {
display: flex;
align-items: center;
margin-bottom: 16px;
min-height: 40px;
}
.info-label {
width: 80px;
font-size: 14px;
color: #333;
font-weight: 500;
flex-shrink: 0;
}
.info-value {
flex: 1;
font-size: 14px;
color: #666;
margin-left: 12px;
}
.status-frozen {
color: #ff4949;
font-weight: 500;
}
.status-normal {
color: #17B3A3;
font-weight: 500;
}
/* 操作按钮 */
.action-buttons {
display: flex;
padding: 16px;
gap: 12px;
background: white;
margin-top: auto;
}
.action-btn {
flex: 1;
padding: 12px;
border-radius: 20px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
border: none;
}
.split-btn {
background: #17B3A3;
color: white;
}
.split-btn:hover {
background: #0d8f7f;
}
.merge-btn {
background: white;
color: #17B3A3;
border: 1px solid #17B3A3;
}
.merge-btn:hover {
background: #17B3A3;
color: white;
}
.action-btn:active {
transform: scale(0.98);
}
/* 对话框样式 */
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.dialog-modal {
background: white;
border-radius: 12px;
width: 100%;
max-width: 400px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.dialog-header {
background: #17B3A3;
color: white;
padding: 16px 20px;
text-align: center;
}
.dialog-title {
font-size: 16px;
font-weight: 500;
}
.dialog-body {
padding: 20px;
}
.split-input-section,
.merge-input-section {
margin-bottom: 20px;
}
.split-input,
.merge-input {
width: 100%;
}
.split-input ::v-deep .el-input__inner,
.numInput /deep/ .el-input__inner{
text-align: right;
}
/deep/ .inlineNumber input::-webkit-outer-spin-button,
/deep/ .inlineNumber input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
/deep/ .inlineNumber input[type="number"]{
-moz-appearance: textfield;
padding-right: 5px !important;
}
.merge-input ::v-deep .el-input__inner {
height: 48px;
border: 1px solid #17B3A3;
border-radius: 8px;
font-size: 16px;
text-align: center;
}
.dialog-footer {
padding: 16px 20px;
display: flex;
justify-content: center;
gap: 12px;
border-top: 1px solid #f0f0f0;
}
.btn-split,
.btn-merge,
.btn-cancel {
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
border: none;
outline: none;
}
.btn-split,
.btn-merge {
background: #17B3A3;
color: white;
}
.btn-split:hover:not(:disabled),
.btn-merge:hover:not(:disabled) {
background: #0d8f7f;
}
.btn-split:disabled,
.btn-merge:disabled {
background: #c0c4cc;
cursor: not-allowed;
}
.btn-cancel {
background: #f5f5f5;
color: #666;
}
.btn-cancel:hover {
background: #e6e6e6;
}
/* 响应式设计 */
@media (max-width: 360px) {
.header-bar {
padding: 8px 12px;
}
.search-container {
padding: 8px 12px;
}
.label-info-card {
margin: 6px 12px;
padding: 12px;
}
.info-label {
width: 70px;
font-size: 13px;
}
.info-value {
font-size: 13px;
}
.action-buttons {
padding: 12px;
}
}
</style>