Files
wms-ui/src/views/barcodePrint/index.vue
2026-06-10 18:25:14 +08:00

690 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="gi_page">
<div class="container">
<h2 class="page-title">标签打印</h2>
<!-- 标签参数设置 -->
<div class="form-section">
<a-form :model="formData" layout="vertical">
<div class="form-grid">
<div class="form-grid-item">
<a-form-item label="物料名称">
<a-input v-model="formData.materialName" placeholder="未获取到物料名称" :disabled="true" />
</a-form-item>
</div>
<div class="form-grid-item">
<a-form-item label="打印编码(国产替代)">
<a-input v-model="formData.encodingPrint" placeholder="未获取到打印编码" :disabled="true" />
</a-form-item>
</div>
<div class="form-grid-item">
<a-form-item label="工单编号">
<a-input v-model="formData.orderNo" placeholder="未获取到工单编号" :disabled="true" />
</a-form-item>
</div>
<div class="form-grid-item">
<a-form-item label="生产批次">
<a-input v-model="formData.batch" placeholder="未获取到生产批次" :disabled="true" />
</a-form-item>
</div>
</div>
</a-form>
<div class="form-actions">
<a-button type="primary" @click="generateDetailLabel">明细标签</a-button>
<a-button type="primary" @click="generateOverallLabel">整体标签</a-button>
</div>
</div>
<!-- 标签预览 -->
<div v-if="labelDataList.length > 0 || labelData.partName" class="label-preview-section">
<h3>标签预览</h3>
<div class="label-container" ref="labelContainer">
<!-- 明细标签显示多个标签 -->
<template v-if="labelDataList.length > 0">
<div class="label" v-for="(item, index) in labelDataList" :key="index">
<table class="label-table">
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">零件名称</div>
<div class="label-value">{{ item.partName }}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">生产日期</div>
<div class="label-value">{{ item.productionDate }}</div>
</div>
</td>
<td class="label-cell qr-cell" rowspan="4">
<div class="qr-code">
<img v-if="item.qrCodeImage" :src="item.qrCodeImage" alt="QR Code" />
<div class="mark-number">{{ item.mark || '' }}</div>
</div>
</td>
</tr>
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">零件号</div>
<div class="label-value">{{ item.partNumber }}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">数量</div>
<div class="label-value">{{ item.totalCount }}</div>
</div>
</td>
</tr>
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">标重(g)</div>
<div class="label-value">{{ item.totalCalculatedWeight }}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">包装签字</div>
<div class="label-value">{{ item.packingSignature || '' }}</div>
</div>
</td>
</tr>
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">实重(g)</div>
<div class="label-value">{{ item.totalWeight || '' }}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">检验签字</div>
<div class="label-value">{{ item.inspectionSignature || '' }}</div>
</div>
</td>
</tr>
</table>
</div>
</template>
<!-- 整体标签显示单个标签 -->
<template v-else>
<div class="label">
<table class="label-table">
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">零件名称</div>
<div class="label-value">{{ labelData.partName }}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">生产日期</div>
<div class="label-value">{{ labelData.productionDate }}</div>
</div>
</td>
<td class="label-cell qr-cell" rowspan="4">
<div class="qr-code">
<img v-if="labelData.qrCodeImage" :src="labelData.qrCodeImage" alt="QR Code" />
<div class="mark-number">{{ labelData.mark || '' }}</div>
</div>
</td>
</tr>
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">零件号</div>
<div class="label-value">{{ labelData.partNumber }}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">数量</div>
<div class="label-value">{{ labelData.totalCount }}</div>
</div>
</td>
</tr>
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">标重(g)</div>
<div class="label-value">{{ labelData.totalCalculatedWeight }}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">包装签字</div>
<div class="label-value">{{ labelData.packingSignature || '' }}</div>
</div>
</td>
</tr>
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">实重(g)</div>
<div class="label-value">{{ labelData.totalWeight || '' }}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">检验签字</div>
<div class="label-value">{{ labelData.inspectionSignature || '' }}</div>
</div>
</td>
</tr>
</table>
</div>
</template>
</div>
<div class="label-actions">
<a-button type="primary" @click="printLabel">打印标签</a-button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import { useRoute } from 'vue-router'
import {getWorkOrder, type WorkOrderInfoResp} from "@/apis/workOrder/workOrder"
import QRCode from 'qrcode';
const route = useRoute()
// 表单数据
const formData = reactive({
workerOrderId: '',
encoding: '',
encodingPrint: '',
materialName: '',
orderNo: '',
totalCalculatedWeight: '',
totalWeight: '',
totalCount: '',
batch: '',
mark: '',
qrCodeData: '',
workOrderInfos: Array<WorkOrderInfoResp>(),
})
// 标签数据数组
const labelDataList = reactive<Array<{
partName: string
partNumber: string
totalCalculatedWeight: string
totalWeight: string
productionDate: string
totalCount: string
packingSignature: string
inspectionSignature: string
qrCodeData: string
qrCodeImage: string
mark: string
}>>([])
// 标签数据(用于整体标签)
const labelData = reactive({
partName: '',
partNumber: '',
totalCalculatedWeight: '',
totalWeight: '',
productionDate: '',
totalCount: '',
packingSignature: '',
inspectionSignature: '',
qrCodeData: '',
qrCodeImage: '',
mark: '',
})
// 标签容器引用
const labelContainer = ref<HTMLElement | null>(null)
// 生成二维码
const generateQRCode = async (data: string) => {
try {
return await QRCode.toDataURL(data, {
width: 120,
margin: 1,
color: {
dark: '#000000',
light: '#FFFFFF'
}
})
} catch (error) {
console.error('生成二维码失败:', error)
return ''
}
}
// 生成明细标签
const generateDetailLabel = async () => {
if (!formData.workOrderInfos || formData.workOrderInfos.length === 0) {
Message.error('未获取到工单明细信息')
return
}
if (!formData.batch) {
console.log("11111", formData.batch);
Message.error('未获取到批次信息')
return
}
if (!formData.materialName) {
Message.error('未获取到物料信息')
return
}
try {
// 清空之前的标签数据
labelDataList.length = 0
// 格式化生产日期为 yyyyMMddHHmm 格式
const now = new Date()
const formattedDate = now.getFullYear().toString() +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0') +
String(now.getHours()).padStart(2, '0') +
String(now.getMinutes()).padStart(2, '0')
const formattedDate2 = now.getFullYear().toString() +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0')
// 为每个工单明细生成一个标签
for (const workOrderInfo of formData.workOrderInfos) {
// 计算二维码数据
const orderNo = formData.orderNo + workOrderInfo.id;
const qrCodeData = `10#${formData.encodingPrint}$11#9DP$12#${formData.batch}$17#${workOrderInfo.quantity}$20#${formattedDate2}$31#${orderNo}$DY`
// 生成二维码图片
const qrCodeImage = await generateQRCode(qrCodeData)
// 添加标签数据
labelDataList.push({
partName: formData.materialName || '',
partNumber: formData.encodingPrint || '',
totalCalculatedWeight: workOrderInfo.calculatedWeight || '',
totalWeight: workOrderInfo.weight || '',
productionDate: formattedDate,
totalCount: workOrderInfo.quantity || '',
packingSignature: '',
inspectionSignature: '',
qrCodeData: qrCodeData,
qrCodeImage: qrCodeImage,
mark: workOrderInfo.mark || '',
})
}
Message.success(`成功生成 ${labelDataList.length} 个明细标签`)
} catch (error) {
console.error('生成明细标签失败:', error)
Message.error('生成明细标签失败')
}
}
// 生成整体标签
const generateOverallLabel = async () => {
if (!formData.batch) {
Message.error('未获取到批次信息')
return
}
if (!formData.materialName) {
Message.error('未获取到物料信息')
return
}
try {
// 清空明细标签数据
labelDataList.length = 0
// 格式化生产日期为 yyyyMMddHHmm 格式
const now = new Date()
const formattedDate = now.getFullYear().toString() +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0') +
String(now.getHours()).padStart(2, '0') +
String(now.getMinutes()).padStart(2, '0')
const formattedDate2 = now.getFullYear().toString() +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0')
// 计算二维码数据
const qrCodeData = `10#${formData.encodingPrint}$11#9DP$12#${formData.batch}$17#${formData.totalCount}$20#${formattedDate2}$31#${formData.orderNo}$DY`
// 生成二维码图片
const qrCodeImage = await generateQRCode(qrCodeData)
// 直接从 formData 中获取数据
Object.assign(labelData, {
partName: formData.materialName || '',
partNumber: formData.encodingPrint || '',
totalCalculatedWeight: formData.totalCalculatedWeight || '',
totalWeight: formData.totalWeight || '',
productionDate: formattedDate,
totalCount: formData.totalCount || '',
packingSignature: '',
inspectionSignature: '',
qrCodeData: qrCodeData,
qrCodeImage: qrCodeImage,
mark: formData.mark || '',
})
Message.success('标签生成成功')
} catch (error) {
console.error('生成标签失败:', error)
Message.error('生成标签失败')
}
}
// 组件挂载时,检查是否有参数传递
onMounted(() => {
// 从路由参数中获取数据
const workerOrderId = route.query.workerOrderId as string
// 如果有参数传递,设置表单数据并标记为不可修改
if (workerOrderId) {
formData.workerOrderId = workerOrderId
getWorkOrder(workerOrderId).then(res => {
if (res.code == '0') {
formData.encoding = res.data.encoding
formData.encodingPrint = res.data.encodingPrint
formData.materialName = res.data.materialName
formData.orderNo = res.data.orderNo
formData.batch = res.data.batch
formData.totalCalculatedWeight = res.data.totalCalculatedWeight
formData.totalWeight = res.data.totalWeight
formData.totalCount = res.data.totalCount
formData.workOrderInfos = res.data.workOrderInfos
formData.mark = res.data.mark
} else {
Message.error('获取详情失败')
}
});
}
})
// 打印标签
const printLabel = async () => {
await nextTick()
if (labelContainer.value) {
// 创建打印窗口
const printWindow = window.open('', '_blank')
if (printWindow) {
// 判断是打印明细标签还是整体标签
const isDetailLabels = labelDataList.length > 0
const labelsToPrint = isDetailLabels ? labelDataList : [labelData]
// 生成标签HTML
const labelsHTML = labelsToPrint.map(item => `
<div class="label">
<table class="label-table">
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">零件名称</div>
<div class="label-value">${item.partName}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">生产日期</div>
<div class="label-value">${item.productionDate}</div>
</div>
</td>
<td class="label-cell qr-cell" rowspan="4">
<div class="qr-code">
<img src="${item.qrCodeImage}" alt="QR Code" />
<div class="mark-number">${item.mark || ''}</div>
</div>
</td>
</tr>
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">零件号</div>
<div class="label-value">${item.partNumber}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">数量</div>
<div class="label-value">${item.totalCount}</div>
</div>
</td>
</tr>
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">标重(g)</div>
<div class="label-value">${item.totalCalculatedWeight}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">包装签字</div>
<div class="label-value">${item.packingSignature || ''}</div>
</div>
</td>
</tr>
<tr>
<td class="label-cell">
<div class="label-row">
<div class="label-field">实重(g)</div>
<div class="label-value">${item.totalWeight || ''}</div>
</div>
</td>
<td class="label-cell">
<div class="label-row">
<div class="label-field">检验签字</div>
<div class="label-value">${item.inspectionSignature || ''}</div>
</div>
</td>
</tr>
</table>
</div>
`).join('')
// 构建打印内容
const printHTML = `
<html>
<head>
<title>标签打印</title>
<style>
@page { size: 80mm 50mm; margin: 0; }
body { margin: 0; padding: 1mm; font-family: Arial, sans-serif; }
.label-container { display: flex; flex-wrap: wrap; gap: 1mm; justify-content: center; align-items: center; transform-origin: center; }
.label { width: 75mm; height: 45mm; border: 1px solid #000; padding: 0; box-sizing: border-box; page-break-inside: avoid; overflow: hidden; margin: 0 auto; }
.label-table { width: 100%; height: 100%; border-collapse: collapse; table-layout: fixed; }
.label-cell { border: 1px solid #000; padding: 0.8mm; vertical-align: top; }
.qr-cell { width: 22mm; text-align: center; vertical-align: middle; border: 1px solid #000; }
.label-row { display: flex; align-items: center; }
.label-field { font-size: 7.5pt; font-weight: bold; margin-right: 1mm; min-width: 22pt; white-space: nowrap; }
.label-value { font-size: 7.5pt; font-weight: bold; flex: 1; overflow-wrap: break-word; word-break: break-word; }
.qr-code img { width: 22mm; height: 22mm; margin: 0.5mm 0; }
.mark-number { font-size: 7pt; font-weight: bold; margin-top: 0.5mm; text-align: center; }
.serial-number { font-size: 7.5pt; font-weight: bold; margin-top: 0.5mm; }
</style>
</head>
<body>
<div class="label-container">
${labelsHTML}
</div>
</body>
</html>
`
printWindow.document.write(printHTML)
printWindow.document.close()
// 等待图片加载完成后再打印
printWindow.onload = () => {
setTimeout(() => {
printWindow.focus()
printWindow.print()
printWindow.close()
}, 500)
}
}
}
}
defineOptions({ name: 'print' })
</script>
<style scoped lang="scss">
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.page-title {
margin-bottom: 30px;
text-align: center;
color: #333;
}
/* 表单区域 */
.form-section {
background-color: var(--color-bg-2);
padding: 20px;
border-radius: 4px;
margin-bottom: 30px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.form-grid-item {
width: 100%;
}
.form-actions {
margin-top: 20px;
display: flex;
justify-content: center;
gap: 20px;
}
/* 标签预览 */
.label-preview-section {
margin-top: 30px;
padding: 20px;
background-color: var(--color-bg-2);
border-radius: 4px;
}
.label-preview-section h3 {
margin: 0 0 20px 0;
font-size: 16px;
font-weight: bold;
text-align: center;
}
.label-container {
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
margin-bottom: 20px;
}
.label {
width: 450px;
height: 180px;
border: 1px solid #000;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
.label-table {
width: 100%;
height: 100%;
border-collapse: collapse;
}
.label-cell {
border: 1px solid #000;
padding: 5px;
vertical-align: top;
}
.qr-cell {
width: 120px;
text-align: center;
vertical-align: middle;
}
.label-row {
display: flex;
align-items: center;
}
.label-field {
font-size: 12px;
font-weight: bold;
margin-right: 10px;
min-width: 60px;
}
.label-value {
font-size: 12px;
font-weight: bold;
flex: 1;
word-break: break-word;
}
.qr-code img {
width: 115px;
height: 120px;
margin: 0 0;
}
.qr-code .loading {
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
color: #666;
}
.mark-number {
font-size: 10px;
margin-top: 5px;
text-align: center;
font-weight: bold;
}
.serial-number {
font-size: 12px;
margin-top: 5px;
}
.label-actions {
display: flex;
justify-content: center;
margin-top: 20px;
}
/* 确保输入框宽度一致 */
:deep(.arco-input-wrapper) {
width: 100%;
height: 35px;
}
:deep(.arco-input) {
font-size: 16px;
}
</style>