feat(标签打印): 优化标签打印功能并新增批量导出

- 新增html2canvas依赖以支持高清打印
- 重构标签打印逻辑,解决二维码丢失和文字模糊问题
- 优化外标签样式布局和公司名称显示
- 新增标签预览功能,可在物料列表中直接查看
- 实现批量导出功能,支持任务规划和进度展示
- 添加导出配置选项,可调整清晰度和单次导出数量
This commit is contained in:
砂糖
2026-01-08 16:29:47 +08:00
parent 624482a610
commit 0bf61a3dd6
6 changed files with 788 additions and 209 deletions

View File

@@ -4,9 +4,10 @@
<div class="company-header">
<img :src="logo" alt="Company Logo" class="company-logo" />
<div class="company-name">
嘉祥科伦普重工有限公司<br />
<span class="english-name">Jiaxiang KLP Heavy Industry Co., Ltd.</span>
科伦普<br />
<span class="english-name">KE LUN PU</span>
</div>
<div class="title">嘉祥科伦普重工有限公司</div>
</div>
<!-- 核心信息网格布局替代原表格 -->
@@ -102,7 +103,7 @@
</span>
</div>
<div class="contact-timestamp">
<QRCode :content="content.qrcodeRecordId || ''" :size="10" />
<QRCode :content="content.qrcodeRecordId || ''" :size="60" />
</div>
</div>
</div>
@@ -161,11 +162,11 @@ export default {
<style scoped>
.label-container {
width: 45em;
height: 25em;
height: 26em;
padding: 0.5em;
box-sizing: border-box;
font-family: 'Arial', sans-serif;
border: 1px solid #000;
/* border: 1px solid #000; */
}
.company-header {
@@ -175,17 +176,24 @@ export default {
}
.company-logo {
width: 2.8em;
height: 2.8em;
width: 4em;
height: 4em;
margin-right: 0.5em;
}
.company-name {
font-size: 0.95em;
width: 100%;
font-size: 1em;
font-weight: bold;
line-height: 1.1;
}
.title {
flex: 1;
font-size: 2em;
font-weight: bold;
text-align: center;
}
.english-name {
width: 100%;
font-size: 0.6em;
@@ -211,8 +219,8 @@ export default {
.info-grid-item {
border: 1px solid #000;
padding: 0.25em;
font-size: 0.78em;
padding: 0.1em;
font-size: 1em;
height: 2.1em;
text-align: center;
vertical-align: middle;
@@ -220,10 +228,11 @@ export default {
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.label-cell {
font-weight: bold;
/* font-weight: bold; */
}
/* 生产日期跨3列 */

View File

@@ -5,7 +5,7 @@
<tr>
<td class="label-cell" colspan="1">料卷号</td>
<td class="value-cell" colspan="2">
<input type="text" class="nob" :value="content.enterCoilNo || ''" />
<input type="text" class="nob" style="line-height: 95px;" :value="content.enterCoilNo || ''" />
</td>
<td class="value-cell" colspan="1">
<QRCode :content="content.qrcodeRecordId || ' '" :size="5" />

View File

@@ -1,7 +1,5 @@
<template>
<div class="label-render-container">
<!-- 操作按钮 -->
<!-- 标签预览容器 -->
<div class="preview-container" id="label-preview-container" ref="labelRef">
<ProductionTagPreview v-if="labelType === '2'" :content="content" />
@@ -20,7 +18,8 @@
<script>
import domToImage from 'dom-to-image';
import printJS from 'print-js';
import { Message } from 'element-ui'; // 若使用Element UI引入消息提示
import html2canvas from 'html2canvas'; // 新增:引入高清渲染库
import { Message } from 'element-ui';
import ProductionTagPreview from './ProductionTagPreview.vue';
import OuterTagPreview from './OuterTagPreview.vue';
@@ -48,9 +47,8 @@ export default {
},
},
methods: {
// -------- 图片下载方法 --------
// -------- 图片下载方法保留可按需替换为html2canvas --------
async downloadLabelAsImage() {
// 1. 获取要转换的DOM容器
const labelContainer = document.getElementById('label-preview-container');
if (!labelContainer) {
Message.error('未找到标签容器,无法下载');
@@ -58,24 +56,25 @@ export default {
}
try {
// 2. 用dom-to-image生成PNG图片的DataURL
// 可选也替换为html2canvas提升下载清晰度
// const canvas = await html2canvas(labelContainer, { scale: 3, backgroundColor: '#ffffff', allowTaint: true, taintTest: false });
// const dataUrl = canvas.toDataURL('image/png', 1.0);
const dataUrl = await domToImage.toPng(labelContainer);
// 3. 创建临时a标签触发下载
const downloadLink = document.createElement('a');
downloadLink.href = dataUrl;
downloadLink.download = `标签_${new Date().getTime()}.png`; // 自定义文件名(带时间戳防重复)
downloadLink.download = `标签_${new Date().getTime()}.png`;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink); // 下载后移除临时标签
document.body.removeChild(downloadLink);
} catch (error) {
console.error('标签图片下载失败:', error);
Message.error('标签图片下载失败,请重试');
}
},
// 打印方法
printLabel() {
// -------- 重构后的打印方法(核心优化) --------
async printLabel() {
// 1. 获取标签容器DOM
const labelContainer = document.getElementById('label-preview-container');
if (!labelContainer) {
@@ -83,21 +82,137 @@ export default {
return;
}
// 4. 调用print-js打印
printJS({
printable: 'label-preview-container', // 要打印的元素ID
type: 'html', // 打印类型为HTML
header: null, // 不显示页眉
footer: null, // 不显示页脚
// style: printStyles, // 注入打印样式
scanStyles: true, // 禁用自动扫描页面样式(避免冲突)
targetStyles: ['*'], // 允许所有样式生效
printContainer: true, // 打印指定容器
onError: (error) => { // 错误处理
console.error('打印失败:', error);
Message.error('打印失败,请重试');
}
try {
Message.info('正在准备打印内容,请稍等...');
// 2. 等待二维码/字体等资源加载完成(复用之前的等待逻辑)
await this.waitForAllResources(labelContainer);
// 3. 用html2canvas生成高清Canvas解决文字模糊+二维码丢失)
const canvas = await html2canvas(labelContainer, {
scale: 3, // 3倍高清渲染核心
backgroundColor: '#ffffff', // 强制白色背景,避免打印时背景透明
useCORS: true, // 支持跨域图片
allowTaint: true, // 允许渲染canvas二维码
taintTest: false, // 关闭canvas污染检测
windowWidth: labelContainer.offsetWidth * 3, // 适配缩放后的宽度
windowHeight: labelContainer.offsetHeight * 3, // 适配缩放后的高度
logging: false,
});
// 4. 创建临时打印容器避免影响原DOM
const printContainerId = 'temp-print-container-' + new Date().getTime();
const tempPrintContainer = document.createElement('div');
tempPrintContainer.id = printContainerId;
// 样式:让打印容器居中、适配纸张
tempPrintContainer.style.cssText = `
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
box-sizing: border-box;
background: #fff;
`;
// 将高清Canvas插入临时容器
tempPrintContainer.appendChild(canvas);
// document.body.appendChild(tempPrintContainer);
// 5. 调用printJS打印高清Canvas而非原HTML
printJS({
printable: tempPrintContainer, // 打印临时容器
type: 'html',
header: null,
footer: null,
scanStyles: false, // 禁用自动扫描样式,手动控制
// 自定义打印样式确保Canvas适配纸张、无多余边距
style: `
#${printContainerId} {
width: 100%;
height: auto;
margin: 0;
padding: 0;
}
#${printContainerId} canvas {
max-width: 100%;
height: auto;
border: none;
}
@media print {
body * { visibility: hidden; }
#${printContainerId}, #${printContainerId} * { visibility: visible; }
#${printContainerId} { position: absolute; top: 0; left: 0; }
@page { margin: 10mm; } /* 打印纸张边距 */
}
`,
printContainer: true,
onAfterPrint: () => {
// 6. 打印完成后清理临时容器
document.body.removeChild(tempPrintContainer);
Message.success('打印准备完成,请在打印预览中确认打印');
},
onError: (error) => {
// 异常时也清理临时容器
document.body.removeChild(tempPrintContainer);
console.error('打印失败:', error);
Message.error('打印失败,请重试');
}
});
// document.removeChild(tempPrintContainer);
} catch (error) {
console.error('打印准备失败:', error);
Message.error('打印内容准备失败,请重试');
}
},
// -------- 辅助方法:等待所有资源加载(图片+字体+二维码) --------
async waitForAllResources(element) {
// 等待图片加载
const images = element.querySelectorAll('img');
const imgPromises = Array.from(images).map(img =>
img.complete ? Promise.resolve() : new Promise(resolve => {
img.onload = resolve;
img.onerror = resolve;
})
);
await Promise.all(imgPromises);
// 等待字体加载
await document.fonts.ready;
// 等待二维码Canvas渲染
await this.waitForQRCodeRender(element);
// 最终等待样式稳定
await new Promise(resolve => setTimeout(resolve, 300));
},
// -------- 辅助方法等待二维码Canvas渲染完成 --------
async waitForQRCodeRender(element) {
const qrCanvasList = element.querySelectorAll('canvas');
if (qrCanvasList.length === 0) return;
const qrLoadPromises = Array.from(qrCanvasList).map(canvas => {
return new Promise(resolve => {
const checkInterval = setInterval(() => {
const ctx = canvas.getContext('2d');
if (!ctx) {
clearInterval(checkInterval);
resolve();
return;
}
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const hasContent = imageData.data.some(value => value !== 0);
if (hasContent || Date.now() - (ctx.startTime || 0) > 2000) {
clearInterval(checkInterval);
resolve();
}
}, 100);
});
});
await Promise.all(qrLoadPromises);
}
},
};
@@ -108,7 +223,6 @@ export default {
width: 100%;
display: flex;
flex-direction: column;
/* 按钮与预览垂直排列 */
justify-content: flex-start;
align-items: center;
padding: 1rem;
@@ -117,7 +231,6 @@ export default {
.action-buttons {
margin-top: 1rem;
/* 按钮与预览区的间距 */
}
.preview-container {
@@ -137,4 +250,18 @@ export default {
box-shadow: 0 0 80px rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
/* 强制二维码Canvas可见避免渲染丢失 */
:deep(canvas) {
display: block;
visibility: visible;
opacity: 1;
}
/* 字体平滑,提升文字清晰度 */
:deep(.preview-container) {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-smooth: always;
}
</style>