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.
812 lines
18 KiB
812 lines
18 KiB
<template>
|
|
<div>
|
|
<div class="pda-container">
|
|
<!-- 头部栏 -->
|
|
<div class="header-bar">
|
|
<div class="header-left" @click="handleBack">
|
|
<i class="el-icon-arrow-left"></i>
|
|
<span>标签查询</span>
|
|
</div>
|
|
<div class="header-right" @click="$router.push({ path: '/' })">
|
|
首页
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-body" style="max-height: 500px; overflow-y: auto;">
|
|
<div class="main-content form-section">
|
|
<!-- 标签扫描输入框 -->
|
|
<div class="input-group">
|
|
<el-input
|
|
v-model="labelCode"
|
|
placeholder="请扫描标签编码"
|
|
class="form-input"
|
|
clearable
|
|
inputmode="none"
|
|
autocomplete="off"
|
|
autocorrect="off"
|
|
spellcheck="false"
|
|
@keyup.enter.native="handleLabelScan"
|
|
ref="labelInput"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 标签信息显示 (扫描后显示) -->
|
|
<div v-if="labelInfo" class="info-section">
|
|
<div class="info-title">
|
|
<i class="el-icon-document">标签信息</i>
|
|
<button
|
|
class="print-btn"
|
|
@click="handlePrint"
|
|
:disabled="printLoading"
|
|
>
|
|
<i :class="printLoading ? 'el-icon-loading' : 'el-icon-printer'"></i>
|
|
{{ printLoading ? '打印中...' : '打印' }}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">标签编码:</span>
|
|
<span class="info-value">{{ labelInfo.unitId || '-' }}</span>
|
|
</div>
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">物料编码:</span>
|
|
<span class="info-value">{{ labelInfo.partNo || '-' }}</span>
|
|
</div>
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">数量:</span>
|
|
<span class="info-value">{{ labelInfo.qty || '0' }}</span>
|
|
</div>
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">批次号:</span>
|
|
<span class="info-value">{{ labelInfo.batchNo || '-' }}</span>
|
|
</div>
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">仓库:</span>
|
|
<span class="info-value">{{ labelInfo.warehouseId || '-' }}</span>
|
|
</div>
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">库位:</span>
|
|
<span class="info-value">{{ labelInfo.locationId || '-' }}</span>
|
|
</div>
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">WDR:</span>
|
|
<span class="info-value">{{ labelInfo.wdr || '-' }}</span>
|
|
</div>
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">engChgLevel:</span>
|
|
<span class="info-value">{{ labelInfo.engChgLevel }}</span>
|
|
</div>
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">expiredDate:</span>
|
|
<span class="info-value">{{ formatDate(labelInfo.expiredDate) }}</span>
|
|
</div>
|
|
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">创建时间:</span>
|
|
<span class="info-value">{{ formatDate(labelInfo.createdDate) }}</span>
|
|
</div>
|
|
|
|
<!-- 预留申请单号 - rqrq -->
|
|
<div class="info-row" v-if="labelInfo.reserveOrderRef1">
|
|
<span class="info-label">预留申请单号:</span>
|
|
<span class="info-value">{{ labelInfo.reserveOrderRef1 || '-' }}</span>
|
|
</div>
|
|
|
|
<!-- 预留订单号 - rqrq -->
|
|
<div class="info-row" v-if="labelInfo.reserveOrderRef3">
|
|
<span class="info-label">预留订单号:</span>
|
|
<span class="info-value">{{ labelInfo.reserveOrderRef3 || '-' }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 底部操作按钮 -->
|
|
<div v-if="labelInfo" class="bottom-actions">
|
|
<button
|
|
class="action-btn primary"
|
|
@click="showSplitDialog"
|
|
>
|
|
拆分
|
|
</button>
|
|
<button
|
|
class="action-btn secondary"
|
|
@click="clearData"
|
|
>
|
|
清空
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 拆分对话框 -->
|
|
<el-dialog
|
|
title="标签拆分"
|
|
:visible.sync="splitDialogVisible"
|
|
width="90%"
|
|
:close-on-click-modal="false"
|
|
:append-to-body="true"
|
|
:modal-append-to-body="true"
|
|
>
|
|
<div class="split-dialog-content">
|
|
<div class="split-info-row">
|
|
<span class="split-label">原标签编码:</span>
|
|
<span class="split-value">{{ labelInfo ? labelInfo.unitId : '' }}</span>
|
|
</div>
|
|
|
|
<div class="split-info-row">
|
|
<span class="split-label">当前数量:</span>
|
|
<span class="split-value">{{ labelInfo ? labelInfo.qty : 0 }}</span>
|
|
</div>
|
|
|
|
<div class="split-input-group">
|
|
<label class="split-label required">拆分数量:</label>
|
|
<el-input
|
|
v-model="splitQty"
|
|
type="number"
|
|
placeholder="请输入拆分数量(必须小于100)"
|
|
class="split-input"
|
|
ref="splitInput"
|
|
/>
|
|
<div class="split-hint" v-if="splitQty && !isValidSplitQty">
|
|
<i class="el-icon-warning"></i>
|
|
拆分数量必须大于0且小于当前数量
|
|
</div>
|
|
</div>
|
|
|
|
<div class="split-info-row">
|
|
<span class="split-label">拆分后原标签剩余:</span>
|
|
<span class="split-value split-remain">{{ calculateRemainQty }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div slot="footer" class="dialog-footer">
|
|
<button class="dialog-btn cancel" @click="splitDialogVisible = false">
|
|
取消
|
|
</button>
|
|
<button
|
|
class="dialog-btn confirm"
|
|
@click="confirmSplit"
|
|
:disabled="splitLoading || !isValidSplitQty"
|
|
>
|
|
{{ splitLoading ? '处理中...' : '确定拆分' }}
|
|
</button>
|
|
</div>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { queryLabelInfo, splitLabel } from '@/api/inventory/label'
|
|
import { printLabelCommon } from '@/api/production/production-inbound.js'
|
|
|
|
export default {
|
|
name: 'LabelQuery',
|
|
|
|
data() {
|
|
return {
|
|
site: localStorage.getItem('site'),
|
|
labelCode: '',
|
|
labelInfo: null,
|
|
loading: false,
|
|
printLoading: false,
|
|
splitDialogVisible: false,
|
|
splitQty: '',
|
|
splitLoading: false
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
/**
|
|
* 计算拆分后原标签剩余数量
|
|
*/
|
|
calculateRemainQty() {
|
|
if (!this.labelInfo || !this.splitQty) {
|
|
return this.labelInfo ? this.labelInfo.qty : 0;
|
|
}
|
|
const currentQty = parseFloat(this.labelInfo.qty) || 0;
|
|
const split = parseFloat(this.splitQty) || 0;
|
|
const remain = currentQty - split;
|
|
return remain >= 0 ? remain : 0;
|
|
},
|
|
|
|
/**
|
|
* 验证拆分数量是否有效
|
|
*/
|
|
isValidSplitQty() {
|
|
if (!this.splitQty || !this.labelInfo) {
|
|
return false;
|
|
}
|
|
const split = parseFloat(this.splitQty);
|
|
const current = parseFloat(this.labelInfo.qty) || 0;
|
|
|
|
return split > 0 && split < current;
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
/**
|
|
* 返回上一页
|
|
*/
|
|
handleBack() {
|
|
this.$router.back();
|
|
},
|
|
|
|
/**
|
|
* 处理标签扫描
|
|
*/
|
|
handleLabelScan() {
|
|
if (!this.labelCode.trim()) {
|
|
this.$message.error('请扫描有效的标签编码');
|
|
return;
|
|
}
|
|
|
|
this.loading = true;
|
|
|
|
queryLabelInfo({
|
|
site: this.site,
|
|
labelCode: this.labelCode.trim()
|
|
}).then(({ data }) => {
|
|
this.loading = false;
|
|
|
|
if (data && data.code === 0) {
|
|
this.labelInfo = data.data;
|
|
this.$message.success('查询成功');
|
|
} else {
|
|
this.$message.error(data.msg || '标签不存在');
|
|
this.labelCode = '';
|
|
this.labelInfo = null;
|
|
this.focusLabelInput();
|
|
}
|
|
}).catch(error => {
|
|
this.loading = false;
|
|
console.error('查询标签失败:', error);
|
|
this.$message.error('查询异常');
|
|
this.labelCode = '';
|
|
this.labelInfo = null;
|
|
this.focusLabelInput();
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 清空数据
|
|
*/
|
|
clearData() {
|
|
this.labelCode = '';
|
|
this.labelInfo = null;
|
|
this.focusLabelInput();
|
|
},
|
|
|
|
/**
|
|
* 聚焦标签输入框
|
|
*/
|
|
focusLabelInput() {
|
|
this.$nextTick(() => {
|
|
if (this.$refs.labelInput) {
|
|
this.$refs.labelInput.focus();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 格式化日期
|
|
*/
|
|
formatDate(date) {
|
|
if (!date) return '-';
|
|
|
|
const d = new Date(date);
|
|
const year = d.getFullYear();
|
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|
const hours = String(d.getHours()).padStart(2, '0');
|
|
const minutes = String(d.getMinutes()).padStart(2, '0');
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
},
|
|
|
|
/**
|
|
* 获取状态标签类型
|
|
*/
|
|
getStatusType(inStockFlag) {
|
|
return inStockFlag === 'Y' ? 'success' : 'danger';
|
|
},
|
|
|
|
/**
|
|
* 处理打印
|
|
*/
|
|
handlePrint() {
|
|
if (!this.labelInfo || !this.labelInfo.unitId) {
|
|
this.$message.error('没有可打印的标签信息');
|
|
return;
|
|
}
|
|
let printLabelType;
|
|
if (this.labelInfo.partNo && this.labelInfo.partNo.startsWith("80")) {
|
|
printLabelType = '库存成品标签';
|
|
} else {
|
|
printLabelType = 'BIL标签';
|
|
}
|
|
// 调用打印方法,传入unitId数组和标签类型
|
|
this.printViaServer([this.labelInfo.unitId], printLabelType);
|
|
},
|
|
|
|
/**
|
|
* 通过服务器打印
|
|
* @param {Array} unitIds - HU unitId列表
|
|
* @param {String} printLabelType - 标签类型
|
|
*/
|
|
async printViaServer(unitIds, printLabelType) {
|
|
if (!unitIds || unitIds.length === 0) {
|
|
console.warn('没有可打印的标签');
|
|
return;
|
|
}
|
|
|
|
this.printLoading = true;
|
|
|
|
try {
|
|
const printRequest = {
|
|
userId: localStorage.getItem('userName'),
|
|
username: localStorage.getItem('userName'),
|
|
site: localStorage.getItem('site'),
|
|
unitIds: unitIds,
|
|
labelType: printLabelType
|
|
};
|
|
console.log('打印请求:', printRequest);
|
|
|
|
const { data } = await printLabelCommon(printRequest);
|
|
|
|
if (data.code === 200 || data.code === 0) {
|
|
this.$message.success(`打印任务已发送!`);
|
|
} else {
|
|
this.$message.error(data.msg || '打印失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('服务器打印失败:', error);
|
|
this.$message.error(`打印失败: ${error.message || error}`);
|
|
} finally {
|
|
this.printLoading = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 显示拆分对话框
|
|
*/
|
|
showSplitDialog() {
|
|
if (!this.labelInfo) {
|
|
this.$message.error('没有可拆分的标签');
|
|
return;
|
|
}
|
|
|
|
this.splitQty = '';
|
|
this.splitDialogVisible = true;
|
|
|
|
// 聚焦到拆分数量输入框
|
|
this.$nextTick(() => {
|
|
if (this.$refs.splitInput) {
|
|
this.$refs.splitInput.focus();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 确认拆分
|
|
*/
|
|
async confirmSplit() {
|
|
// 验证拆分数量
|
|
if (!this.splitQty) {
|
|
this.$message.error('请输入拆分数量');
|
|
return;
|
|
}
|
|
|
|
const splitQty = parseFloat(this.splitQty);
|
|
const currentQty = parseFloat(this.labelInfo.qty);
|
|
|
|
if (splitQty <= 0) {
|
|
this.$message.error('拆分数量必须大于0');
|
|
return;
|
|
}
|
|
|
|
if (splitQty >= currentQty) {
|
|
this.$message.error('拆分数量必须小于当前数量');
|
|
return;
|
|
}
|
|
|
|
this.splitLoading = true;
|
|
|
|
try {
|
|
const { data } = await splitLabel({
|
|
site: this.site,
|
|
unitId: this.labelInfo.unitId,
|
|
splitQty: splitQty,
|
|
operatorName: localStorage.getItem('userName')
|
|
});
|
|
|
|
if (data && data.code === 0) {
|
|
const result = data.data;
|
|
|
|
this.$message.success('拆分成功!');
|
|
|
|
// 关闭对话框
|
|
this.splitDialogVisible = false;
|
|
|
|
// 确定标签类型
|
|
let printLabelType;
|
|
if (this.labelInfo.partNo && this.labelInfo.partNo.startsWith("80")) {
|
|
printLabelType = '库存成品标签';
|
|
} else {
|
|
printLabelType = 'BIL标签';
|
|
}
|
|
|
|
// 打印原标签和新标签
|
|
console.log('打印标签:', [result.originalUnitId, result.newUnitId]);
|
|
await this.printViaServer([result.originalUnitId, result.newUnitId], printLabelType);
|
|
|
|
// 刷新标签信息(显示更新后的数量)
|
|
this.labelCode = this.labelInfo.unitId;
|
|
this.handleLabelScan();
|
|
|
|
} else {
|
|
this.$message.error(data.msg || '拆分失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('拆分标签失败:', error);
|
|
this.$message.error('拆分异常: ' + (error.message || error));
|
|
} finally {
|
|
this.splitLoading = false;
|
|
}
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
// 页面加载后自动聚焦标签输入框
|
|
this.focusLabelInput();
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.input-group {
|
|
margin-bottom: 1px !important;
|
|
}
|
|
|
|
/* PDA容器样式 */
|
|
.pda-container {
|
|
width: 100vw;
|
|
height: 120vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #f5f5f5;
|
|
font-family: 'Arial', sans-serif;
|
|
}
|
|
|
|
/* 头部栏样式 */
|
|
.header-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 8px 16px;
|
|
background: #17B3A3;
|
|
color: white;
|
|
height: 40px;
|
|
min-height: 40px;
|
|
max-height: 40px;
|
|
}
|
|
|
|
.header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.header-left i {
|
|
margin-right: 8px;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.header-left span {
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.header-right {
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* 主要内容区 */
|
|
.table-body {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.main-content {
|
|
padding: 16px;
|
|
}
|
|
|
|
/* 输入组样式 */
|
|
|
|
.input-label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: #333;
|
|
}
|
|
|
|
.form-input {
|
|
width: 100%;
|
|
height: 44px;
|
|
}
|
|
|
|
/* 信息展示区 */
|
|
.info-section {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.info-title {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
font-size: 16px;
|
|
font-weight: bold;
|
|
color: #17B3A3;
|
|
margin-bottom: 6px;
|
|
padding-bottom: 5px;
|
|
border-bottom: 2px solid #17B3A3;
|
|
}
|
|
|
|
.info-title i {
|
|
margin-right: 8px;
|
|
font-size: 18px;
|
|
}
|
|
|
|
/* 打印按钮 */
|
|
.print-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 6px 12px;
|
|
background: #17B3A3;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.print-btn:hover:not(:disabled) {
|
|
background: #15a394;
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 2px 8px rgba(23, 179, 163, 0.3);
|
|
}
|
|
|
|
.print-btn:active:not(:disabled) {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.print-btn:disabled {
|
|
background: #ccc;
|
|
cursor: not-allowed;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.print-btn i {
|
|
margin-right: 0;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
padding: 10px 0;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.info-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: 14px;
|
|
color: #666;
|
|
min-width: 90px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.info-value {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: #333;
|
|
flex: 1;
|
|
text-align: right;
|
|
word-break: break-all;
|
|
}
|
|
|
|
/* 底部按钮区 */
|
|
.bottom-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
padding-top: 16px;
|
|
}
|
|
|
|
.action-btn {
|
|
flex: 1;
|
|
padding: 12px 24px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
min-height: 44px;
|
|
}
|
|
|
|
.action-btn.primary {
|
|
background: #17B3A3;
|
|
color: white;
|
|
}
|
|
|
|
.action-btn.primary:hover {
|
|
background: #15a394;
|
|
}
|
|
|
|
.action-btn.primary:disabled {
|
|
background: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.action-btn.secondary {
|
|
background: #f5f5f5;
|
|
color: #333;
|
|
border: 1px solid #ddd;
|
|
}
|
|
|
|
.action-btn.secondary:hover {
|
|
background: #e8e8e8;
|
|
}
|
|
|
|
/* 拆分对话框样式 */
|
|
.split-dialog-content {
|
|
padding: 16px 0;
|
|
}
|
|
|
|
.split-info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 0;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.split-label {
|
|
font-size: 14px;
|
|
color: #666;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.split-value {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.split-value.split-remain {
|
|
color: #17B3A3;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.split-input-group {
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.split-input-group .split-label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.split-input {
|
|
width: 100%;
|
|
}
|
|
|
|
/* 对话框底部按钮 */
|
|
.dialog-footer {
|
|
display: flex;
|
|
gap: 12px;
|
|
padding-top: 16px;
|
|
}
|
|
|
|
.dialog-btn {
|
|
flex: 1;
|
|
padding: 12px 24px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
min-height: 44px;
|
|
}
|
|
|
|
.dialog-btn.confirm {
|
|
background: #17B3A3;
|
|
color: white;
|
|
}
|
|
|
|
.dialog-btn.confirm:hover:not(:disabled) {
|
|
background: #15a394;
|
|
}
|
|
|
|
.dialog-btn.confirm:disabled {
|
|
background: #ccc;
|
|
cursor: not-allowed;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.dialog-btn.cancel {
|
|
background: #f5f5f5;
|
|
color: #333;
|
|
border: 1px solid #ddd;
|
|
}
|
|
|
|
.dialog-btn.cancel:hover {
|
|
background: #e8e8e8;
|
|
}
|
|
|
|
/* 强制对话框显示在最上层 */
|
|
::v-deep .el-dialog__wrapper {
|
|
z-index: 9999 !important;
|
|
}
|
|
|
|
::v-deep .el-dialog {
|
|
z-index: 10000 !important;
|
|
}
|
|
|
|
::v-deep .v-modal {
|
|
z-index: 9998 !important;
|
|
}
|
|
|
|
/* 响应式设计 */
|
|
@media screen and (max-width: 480px) {
|
|
.header-bar {
|
|
padding: 8px 12px;
|
|
}
|
|
|
|
.main-content {
|
|
padding: 12px;
|
|
}
|
|
|
|
.info-section {
|
|
padding: 2px;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: 13px;
|
|
min-width: 80px;
|
|
}
|
|
|
|
.info-value {
|
|
font-size: 13px;
|
|
}
|
|
|
|
.action-btn {
|
|
padding: 10px 16px;
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
</style>
|
|
|