Merge branch '0.8.X' of https://gitee.com/hdka/klp-oa into 0.8.X

This commit is contained in:
砂糖
2026-01-09 19:06:36 +08:00
4 changed files with 179 additions and 172 deletions

View File

@@ -103,7 +103,8 @@
</span>
</div>
<div class="contact-timestamp">
<QRCode :content="content.qrcodeRecordId || ''" :size="60" />
<!-- 放大二维码占用更多下方空间弱化底部留白感 -->
<QRCode :content="content.qrcodeRecordId || ''" :size="90" />
</div>
</div>
</div>
@@ -275,7 +276,8 @@ export default {
/* height: 377.953px; */
width: fit-content;
height: fit-content;
padding: 0.5em;
/* 减少内边距避免生成PDF时上下留白不一致 */
padding: 0.1em;
font-size: 12px;
border: 1px solid #000;
box-sizing: border-box;
@@ -287,31 +289,36 @@ export default {
display: flex;
align-items: center;
margin-bottom: 0.3em;
position: relative; /* 为绝对定位的 title 提供定位上下文 */
}
.company-logo {
width: 4em;
height: 4em;
width: 5em;
height: 5em;
margin-right: 0.5em;
}
.company-name {
font-size: 1em;
font-size: 1.2em;
font-weight: bold;
line-height: 1.1;
}
.title {
flex: 1;
position: absolute; /* 绝对定位,脱离 flex 流 */
left: 50%; /* 从左边50%开始 */
transform: translateX(-50%); /* 向左移动自身宽度的50%,实现居中 */
font-size: 2em;
font-weight: bold;
text-align: center;
white-space: nowrap; /* 防止文字换行 */
}
.english-name {
width: 100%;
font-size: 0.6em;
opacity: 0.8;
font-size: 1em;
opacity: 0.9;
letter-spacing: 0.08em;
}
.product-title {
@@ -354,23 +361,27 @@ export default {
display: flex;
justify-content: space-between;
align-items: flex-start;
font-size: 0.7em;
margin-top: 0.3em;
font-size: 0.85em;
margin-top: 0.2em;
}
.address {
line-height: 1.2;
line-height: 1.25;
width: 65%;
font-size: 1.3em;
}
.english-address {
opacity: 0.8;
font-size: 0.9em;
opacity: 0.9;
font-size: 1.1em;
line-height: 1.15;
word-break: normal; /* 正常换行,只在单词边界(空格)处换行 */
overflow-wrap: normal; /* 不在单词内部断行,保持单词完整 */
}
.contact-timestamp {
text-align: right;
line-height: 1.4;
line-height: 1.2;
}
.nob {

View File

@@ -170,7 +170,7 @@ export default {
const container = this.$el;
if (!container) return;
// 纸张尺寸180mm x 100mm
// 纸张尺寸100mm x 80mm
const dpi = 96; // 标准 DPI
const mmToPx = dpi / 25.4; // 1mm = 3.779527559px
@@ -231,6 +231,7 @@ export default {
flex-direction: column; /* 子元素垂直排列 */
font-family: "SimSun", serif;
box-sizing: border-box; /* 确保内边距/边框不影响总尺寸 */
overflow: visible; /* 确保所有内容可见,不被裁剪 */
}
.material-label-table {
@@ -269,10 +270,10 @@ export default {
text-align: center;
}
/* 打印样式 - 强制单页适配180mm x 100mm纸张保持原有样式不变 */
/* 打印样式 - 强制单页适配100mm x 80mm纸张保持原有样式不变 */
@media print {
@page {
size: 180mm 100mm;
size: 100mm 80mm;
margin: 0 !important;
padding: 0 !important;
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="label-render-container">
<!-- 标签预览容器 -->
<div class="preview-container" id="label-preview-container" ref="labelRef">
<div class="preview-container" :id="hideActions ? undefined : 'label-preview-container'" ref="labelRef">
<ProductionTagPreview
v-if="labelType === '2'"
:content="content"
@@ -18,7 +18,7 @@
<ForgeTagPreview v-if="labelType === '5'" :content="content" />
<SaltSprayTagPreview v-if="labelType === '6'" :content="content" />
</div>
<div class="action-buttons">
<div class="action-buttons" v-if="!hideActions">
<el-button type="primary" @click="downloadLabelAsImage">下载标签图片</el-button>
<el-button type="primary" @click="printLabel" style="margin-left: 10px;">打印标签</el-button>
</div>
@@ -27,9 +27,9 @@
<script>
import domToImage from 'dom-to-image';
// import printJS from 'print-js'; // 改为自建 iframe 打印,避免多余空白页
import html2canvas from 'html2canvas'; // 新增:引入高清渲染库
import html2canvas from 'html2canvas'; // 高清渲染
import { Message } from 'element-ui';
import { PDFDocument } from 'pdf-lib';
import ProductionTagPreview from './ProductionTagPreview.vue';
import OuterTagPreview from './OuterTagPreview.vue';
@@ -55,11 +55,15 @@ export default {
type: Object,
required: true,
},
hideActions: {
type: Boolean,
default: false,
},
},
methods: {
// -------- 图片下载方法保留可按需替换为html2canvas --------
async downloadLabelAsImage() {
const labelContainer = document.getElementById('label-preview-container');
const labelContainer = this.$refs.labelRef;
if (!labelContainer) {
Message.error('未找到标签容器,无法下载');
return;
@@ -83,11 +87,12 @@ export default {
}
},
// -------- 重构后的打印方法(核心优化) --------
// -------- 单张打印:先生成 PDF再让浏览器打印 --------
async printLabel() {
// 1. 获取标签容器DOM
const labelContainer = document.querySelector('.label-container') ||
document.querySelector('.material-label-container');
// 只在当前组件作用域内查找,避免页面上存在多个标签时选错
const labelContainer = this.$refs.labelRef?.querySelector('.label-container') ||
this.$refs.labelRef?.querySelector('.material-label-container');
if (!labelContainer) {
Message.error('未找到标签容器,无法打印');
return;
@@ -96,137 +101,128 @@ export default {
try {
Message.info('正在准备打印内容,请稍等...');
// 2. 等待二维码/字体等资源加载完成(复用之前的等待逻辑
await this.waitForAllResources(labelContainer);
// 3. 计算纸张尺寸和缩放比例
// 根据标签类型设置纸张尺寸:
// 产品码100mm x 180mm横向原先180x100
// 原料码80mm x 100mm竖向
const isMaterial = labelContainer.classList.contains('material-label-container');
// 2. 计算纸张尺寸(与批量导出保持一致
// 根据 labelType 判断:'2' 是材料标签100x80'3' 是外标180x100
const isMaterial = this.labelType === '2';
const paperWidthMm = isMaterial ? 100 : 180;
const paperHeightMm = isMaterial ? 80 : 100;
const dpi = 96;
const mmToPx = dpi / 25.4;
const paperWidthPx = paperWidthMm * mmToPx;
const paperHeightPx = paperHeightMm * mmToPx;
// 获取标签容器的实际尺寸
const containerRect = labelContainer.getBoundingClientRect();
const containerWidth = containerRect.width;
const containerHeight = containerRect.height;
// 计算缩放比例确保内容适配到纸张留出2mm边距
const marginMm = 2;
const marginPx = marginMm * mmToPx;
const availableWidth = paperWidthPx - marginPx * 2;
const availableHeight = paperHeightPx - marginPx * 2;
const scaleX = containerWidth > 0 ? availableWidth / containerWidth : 1;
const scaleY = containerHeight > 0 ? availableHeight / containerHeight : 1;
const printScale = Math.min(scaleX, scaleY, 1); // 不超过1不放大
// 计算最终Canvas尺寸适配纸张
const finalCanvasWidth = containerWidth * printScale;
const finalCanvasHeight = containerHeight * printScale;
// 使用合适的scale值生成高清Canvas但不超过纸张尺寸
const canvasScale = Math.min(2, printScale * 2); // 适当提高清晰度
const canvasScale = 3; // 提高清晰度(单张打印)
// 4. 用html2canvas生成高清Canvas解决文字模糊+二维码丢失)
// 注意:不再强制指定 width/height避免裁掉最右/最下边框
// 3. 为单张打印临时创建一个与批量打印完全一致的隐藏容器
// 完全模拟批量打印的 .batch-pdf-page 容器
const originalParent = labelContainer.parentNode;
const originalNext = labelContainer.nextSibling;
const wrapper = document.createElement('div');
wrapper.style.position = 'fixed';
wrapper.style.left = '-100000px'; // 与批量打印一致
wrapper.style.top = '0';
wrapper.style.width = `${paperWidthMm}mm`; // 固定尺寸,与批量打印的 .batch-pdf-page 一致
wrapper.style.height = `${paperHeightMm}mm`;
wrapper.style.boxSizing = 'border-box';
wrapper.style.backgroundColor = '#ffffff';
wrapper.style.overflow = 'hidden'; // 与批量打印的 .batch-pdf-page 一致
// 根据标签类型设置不同的布局方式
if (isMaterial) {
// 材料标签:填充 wrapper
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
labelContainer.style.width = '100%';
labelContainer.style.height = '100%';
} else {
// 成品标签:直接放在 wrapper 中,保持原始布局(与批量打印一致)
wrapper.style.display = 'block';
wrapper.style.padding = '0';
// 不修改 labelContainer 的样式,保持 fit-content与批量打印一致
}
wrapper.appendChild(labelContainer);
document.body.appendChild(wrapper);
// 4. 等待二维码/字体等资源加载完成(与批量打印一致)
await this.waitForAllResources(labelContainer);
// 等待布局稳定(与批量打印一致)
await new Promise(resolve => setTimeout(resolve, 100));
// 5. 用html2canvas生成高清Canvas与批量导出完全一致
// 关键:直接截图 labelContainer与批量打印完全一致
const canvas = await html2canvas(labelContainer, {
scale: canvasScale,
backgroundColor: '#ffffff', // 强制白色背景,避免打印时背景透明
useCORS: true, // 支持跨域图片
allowTaint: true, // 允许渲染canvas二维码
taintTest: false, // 关闭canvas污染检测
logging: false,
backgroundColor: '#ffffff',
scale: 3,
useCORS: true,
// 让 html2canvas 为频繁读回优化 Canvas与批量导出一致
willReadFrequently: true,
// 确保按元素尺寸截图(与批量导出完全一致)
width: labelContainer.offsetWidth,
height: labelContainer.offsetHeight,
windowWidth: labelContainer.scrollWidth,
windowHeight: labelContainer.scrollHeight,
});
// 5. 如果Canvas尺寸超出纸张需要缩放Canvas
let finalCanvas = canvas;
if (canvas.width > paperWidthPx || canvas.height > paperHeightPx) {
// 创建新的Canvas尺寸适配纸张
const scaledCanvas = document.createElement('canvas');
scaledCanvas.width = Math.min(canvas.width, paperWidthPx);
scaledCanvas.height = Math.min(canvas.height, paperHeightPx);
const ctx = scaledCanvas.getContext('2d');
ctx.drawImage(canvas, 0, 0, scaledCanvas.width, scaledCanvas.height);
finalCanvas = scaledCanvas;
// 5. 使用 pdf-lib 生成单页 PDF与批量导出完全一致
const mmToPt = 72 / 25.4;
const pageWidthPt = paperWidthMm * mmToPt;
const pageHeightPt = paperHeightMm * mmToPt;
// 边距:与批量导出保持一致
// 材料标签100x80左右2mm上下0.5mm
// 成品标签180x100左右4mm上下0.5mm
const marginXmm = isMaterial ? 2 : 4;
const marginYmm = 0.5; // 上下对称边距(不裁切前提下尽量贴边)
const marginXPt = marginXmm * mmToPt;
const marginYPt = marginYmm * mmToPt;
const contentWidthPt = pageWidthPt - marginXPt * 2;
const contentHeightPt = pageHeightPt - marginYPt * 2;
const pdfDoc = await PDFDocument.create();
const imgPng = await pdfDoc.embedPng(canvas.toDataURL('image/png'));
const page = pdfDoc.addPage([pageWidthPt, pageHeightPt]);
const imgW = imgPng.width;
const imgH = imgPng.height;
// 标准适配:确保完整显示且不变形(在内容区域内等比缩放,与批量导出完全一致)
const scale = Math.min(contentWidthPt / imgW, contentHeightPt / imgH);
const drawW = imgW * scale;
const drawH = imgH * scale;
// 内容区域内居中 + 外层边距(上下对称,与批量导出完全一致)
const x = marginXPt + (contentWidthPt - drawW) / 2;
const y = marginYPt + (contentHeightPt - drawH) / 2;
page.drawImage(imgPng, { x, y, width: drawW, height: drawH });
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const win = window.open(url, '_blank');
if (!win) {
const a = document.createElement('a');
a.href = url;
a.download = `标签_${new Date().getTime()}.pdf`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
// 6. 生成 DataURL使用 printJS 的 image 模式(单图),并减少额外样式以避免隐藏内容
const dataUrl = finalCanvas.toDataURL('image/png');
// 6. 创建隐藏 iframe 进行打印,避免 printJS 的分页行为
const iframe = document.createElement('iframe');
iframe.style.position = 'fixed';
iframe.style.right = '0';
iframe.style.bottom = '0';
iframe.style.width = '0';
iframe.style.height = '0';
iframe.style.border = '0';
document.body.appendChild(iframe);
const doc = iframe.contentWindow.document;
doc.open();
doc.write(`
<!doctype html>
<html>
<head>
<style>
@page {
size: ${paperWidthMm}mm ${paperHeightMm}mm;
margin: 2mm;
}
* {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
body {
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
}
img {
/* 稍微缩小一点,避免被打印机物理不可打印区域裁掉边框 */
width: 94%;
height: auto;
max-height: 94%;
object-fit: contain;
display: block;
page-break-after: avoid;
page-break-before: avoid;
page-break-inside: avoid;
}
</style>
</head>
<body>
<img src="${dataUrl}" />
</body>
</html>
`);
doc.close();
iframe.onload = () => {
setTimeout(() => {
iframe.contentWindow.focus();
iframe.contentWindow.print();
setTimeout(() => {
document.body.removeChild(iframe);
}, 1000);
}, 300);
};
// 还原 DOM 结构
// 恢复 labelContainer 的原始样式(如果之前修改过)
if (isMaterial) {
labelContainer.style.width = '';
labelContainer.style.height = '';
}
// 成品标签不需要恢复,因为没有修改样式
if (originalParent) {
if (originalNext) {
originalParent.insertBefore(labelContainer, originalNext);
} else {
originalParent.appendChild(labelContainer);
}
}
document.body.removeChild(wrapper);
} catch (error) {
console.error('打印准备失败:', error);
Message.error('打印内容准备失败,请重试');
@@ -285,6 +281,7 @@ export default {
</script>
<style scoped>
.label-render-container {
width: 100%;
display: flex;

View File

@@ -63,7 +63,7 @@
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExportAll">导出</el-button>
</el-col>
<el-col :span="2" v-if="canExportAll">
<el-col :span="2">
<el-button type="info" plain icon="el-icon-printer" size="mini" :disabled="multiple" @click="handleBatchPrintLabel">批量打印标签</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -246,7 +246,7 @@
<!-- 渲染容器屏幕隐藏仅用于截图生成PDF -->
<div ref="batchPdfContainer" class="batch-pdf-root" aria-hidden="true">
<div v-for="(item, idx) in batchPrint.list" :key="item.coilId || idx" class="batch-pdf-page">
<OuterTagPreview :content="item" :paperWidthMm="180" :paperHeightMm="100" />
<label-render :content="item" :labelType="labelType" :hideActions="true" />
</div>
</div>
</el-dialog>
@@ -343,10 +343,6 @@ export default {
type: Boolean,
default: false,
},
canExportAll: {
type: Boolean,
default: false,
}
},
data() {
return {
@@ -822,40 +818,44 @@ export default {
// 纸张尺寸
const paperWidthMm = 180;
const paperHeightMm = 100;
// 留白:左右 4mm上下更小一些,避免底部空得太多
// 边距:左右 4mm上下对称 2mm确保垂直居中
const marginXmm = 4;
const marginTopMm = 0;
const marginBottomMm = 0;
const marginYmm = 0.5; // 上下对称边距(不裁切前提下尽量贴边)
const pageWidthPt = paperWidthMm * mmToPt;
const pageHeightPt = paperHeightMm * mmToPt;
const marginXPt = marginXmm * mmToPt;
const marginTopPt = marginTopMm * mmToPt;
const marginBottomPt = marginBottomMm * mmToPt;
const marginYPt = marginYmm * mmToPt;
const contentWidthPt = pageWidthPt - marginXPt * 2;
const contentHeightPt = pageHeightPt - marginTopPt - marginBottomPt;
const contentHeightPt = pageHeightPt - marginYPt * 2;
const pdfDoc = await PDFDocument.create();
// 关键:只截取标签身(OuterTagPreview不要把页面菜单/弹窗带进去
// 关键:只截取标签身(.label-container不要把外层容器/按钮高度算进去
const pageEls = container.querySelectorAll('.batch-pdf-page');
for (let i = 0; i < pageEls.length; i++) {
const el = pageEls[i];
// 强制用标签的mm尺寸作为截图基准避免被外层布局影响
const canvas = await html2canvas(el, {
// 在每一页内部优先查找标签根节点
const labelEl =
el.querySelector('.label-container') ||
el.querySelector('.material-label-container') ||
el; // 兜底:找不到时退回整页
// 强制用标签的实际尺寸作为截图基准,避免被外层布局影响
const canvas = await html2canvas(labelEl, {
backgroundColor: '#ffffff',
scale: 2,
scale: 3,
useCORS: true,
// 让 html2canvas 为频繁读回优化 Canvas浏览器会提示 willReadFrequently
willReadFrequently: true,
// 确保按元素尺寸截图
width: el.offsetWidth,
height: el.offsetHeight,
windowWidth: el.scrollWidth,
windowHeight: el.scrollHeight,
width: labelEl.offsetWidth,
height: labelEl.offsetHeight,
windowWidth: labelEl.scrollWidth,
windowHeight: labelEl.scrollHeight,
});
const imgDataUrl = canvas.toDataURL('image/png');
@@ -866,18 +866,16 @@ export default {
// 图片铺满页面(保持比例,居中)
const imgW = pngImage.width;
const imgH = pngImage.height;
// 按“内容区域(去掉边距)”计算缩放,让四周留下约 4mm 空白
// 标准适配:确保完整显示且不变形(在内容区域内等比缩放)
const scale = Math.min(contentWidthPt / imgW, contentHeightPt / imgH);
const drawW = imgW * scale;
const drawH = imgH * scale;
// 内容区域内居中 + 外层边距
// 内容区域内居中 + 外层边距(上下对称)
const x = marginXPt + (contentWidthPt - drawW) / 2;
const y = marginBottomPt + (contentHeightPt - drawH) / 2;
const y = marginYPt + (contentHeightPt - drawH) / 2;
console.log(pngImage)
page.drawImage(pngImage, { x, y: y - 20, width: drawW, height: drawH + 20 });
page.drawImage(pngImage, { x, y, width: drawW, height: drawH });
}
const pdfBytes = await pdfDoc.save();