Files
klp-oa/klp-ui/src/views/wms/coil/panels/LabelRender/index.vue
砂糖 8efb46289c feat(wms): 新增钢卷去向标签和异常信息查看功能
- 新增钢卷去向标签(WhereTag)及相关配置
- 在base.vue中添加钢卷去向选择功能
- 新增异常信息查看弹窗组件(abnormal.vue)
- 新增多个仓库视图页面(mini.vue, scrap.vue等)
- 在发货单组件中新增Excel导出功能
- 优化标签打印功能,支持多种标签类型
- 修复报表查询时间参数问题
2026-02-07 14:36:56 +08:00

394 lines
13 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="label-render-container">
<!-- 标签预览容器 -->
<div class="preview-container" :id="hideActions ? undefined : 'label-preview-container'" ref="labelRef">
<MaterialTag
v-if="tagType === '2'"
:content="content"
:paperWidthMm="100"
:paperHeightMm="80"
/>
<OuterTagPreview
v-if="tagType === '3'"
:content="content"
:paperWidthMm="180"
:paperHeightMm="100"
/>
<GalvanizedTag
v-if="tagType === '4'"
:content="content"
:paperWidthMm="180"
:paperHeightMm="100"
/>
<WhereTag
v-if="tagType === 'where'"
:content="content"
:paperWidthMm="100"
:paperHeightMm="80"
/>
<!-- <SampleTagPreview v-if="labelType === '4'" :content="content" />
<ForgeTagPreview v-if="labelType === '5'" :content="content" />
<SaltSprayTagPreview v-if="labelType === '6'" :content="content" /> -->
</div>
<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>
</div>
</template>
<script>
import domToImage from 'dom-to-image';
import html2canvas from 'html2canvas'; // 高清渲染
import { Message } from 'element-ui';
import { PDFDocument } from 'pdf-lib';
import MaterialTag from './MaterialTag.vue';
import OuterTagPreview from './OuterTagPreview.vue';
import GalvanizedTag from './GalvanizedTag.vue';
import WhereTag from './WhereTag.vue';
// import SampleTagPreview from './SampleTagPreview.vue';
// import ForgeTagPreview from './ForgeTagPreview.vue';
// import SaltSprayTagPreview from './SaltSprayTagPreview.vue';
export default {
name: 'LabelRender',
components: {
MaterialTag,
OuterTagPreview,
GalvanizedTag,
WhereTag,
// SampleTagPreview,
// ForgeTagPreview,
// SaltSprayTagPreview,
},
data() {
return {
labelType: '2',
tagSizeMap: {
'2': {
width: 100,
height: 80,
},
'3': {
width: 180,
height: 100,
},
'4': {
width: 180,
height: 100,
},
'where': {
width: 100,
height: 80,
},
}
}
},
computed: {
tagType() {
if (this.forceSpecialTag) {
return this.forceSpecialTag;
}
return this.labelType;
}
},
props: {
// labelType: {
// type: String,
// required: true,
// },
forceSpecialTag: {
type: String,
required: false,
},
content: {
type: Object,
required: true,
},
hideActions: {
type: Boolean,
default: false,
},
},
watch: {
content: {
handler(newVal) {
const { itemName, itemType } = newVal;
if (itemType == 'raw_material') {
this.labelType = '2';
} else if (itemType == 'product' && itemName == '冷硬卷') {
this.labelType = '3';
} else if (itemType == 'product' && itemName == '热轧卷板') {
this.labelType = '3';
} else if (itemType == 'product' && itemName == '镀锌卷') {
this.labelType = '4';
} else if (itemType == 'product' && itemName == '冷轧卷') {
this.labelType = '3';
} else {
this.labelType = '3';
}
},
deep: true,
immediate: true,
}
},
methods: {
// -------- 图片下载方法保留可按需替换为html2canvas --------
async downloadLabelAsImage() {
const labelContainer = this.$refs.labelRef;
if (!labelContainer) {
Message.error('未找到标签容器,无法下载');
return;
}
try {
// 可选也替换为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);
const downloadLink = document.createElement('a');
downloadLink.href = dataUrl;
downloadLink.download = `标签_${new Date().getTime()}.png`;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
} catch (error) {
console.error('标签图片下载失败:', error);
Message.error('标签图片下载失败,请重试');
}
},
// -------- 单张打印:先生成 PDF再让浏览器打印 --------
async printLabel() {
// 1. 获取标签容器DOM
// 只在当前组件作用域内查找,避免页面上存在多个标签时选错
const labelContainer = this.$refs.labelRef?.querySelector('.label-container') ||
this.$refs.labelRef?.querySelector('.material-label-container');
if (!labelContainer) {
Message.error('未找到标签容器,无法打印');
return;
}
try {
Message.info('正在准备打印内容,请稍等...');
// 2. 计算纸张尺寸(与批量导出保持一致)
// 根据 labelType 判断:'2' 是材料标签100x80宽100高80'3' 是外标180x100
const isMaterial = this.labelType === '2';
const paperWidthMm = this.tagSizeMap[this.tagType].width || 100;
const paperHeightMm = this.tagSizeMap[this.tagType].height || 80;
// 使用合适的scale值生成高清Canvas但不超过纸张尺寸
const canvasScale = 3; // 提高清晰度(单张打印)
// 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) {
// 材料标签设置固定尺寸确保100mm x 80mm的比例
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
// 直接设置labelContainer的尺寸为mm单位确保正确的物理尺寸比例
labelContainer.style.width = `${paperWidthMm}mm`;
labelContainer.style.height = `${paperHeightMm}mm`;
} 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与批量打印完全一致
// 确保canvas的宽高与labelContainer的实际尺寸一致
const containerWidth = labelContainer.offsetWidth || labelContainer.scrollWidth;
const containerHeight = labelContainer.offsetHeight || labelContainer.scrollHeight;
const canvas = await html2canvas(labelContainer, {
backgroundColor: '#ffffff',
scale: 3,
useCORS: true,
// 让 html2canvas 为频繁读回优化 Canvas与批量导出一致
willReadFrequently: true,
// 确保按元素尺寸截图,明确指定宽高
width: containerWidth,
height: containerHeight,
windowWidth: containerWidth,
windowHeight: containerHeight,
});
// 5. 使用 pdf-lib 生成单页 PDF占满整张纸无边距
const mmToPt = 72 / 25.4;
const pageWidthPt = paperWidthMm * mmToPt;
const pageHeightPt = paperHeightMm * mmToPt;
const pdfDoc = await PDFDocument.create();
const imgPng = await pdfDoc.embedPng(canvas.toDataURL('image/png'));
// 确保页面尺寸正确宽100mm高80mm横向
const page = pdfDoc.addPage([pageWidthPt, pageHeightPt]);
// 直接拉伸填充整个PDF页面不留边距确保占满整张纸
// 从(0,0)开始,直接填充整个页面尺寸
page.drawImage(imgPng, {
x: 0,
y: 0,
width: pageWidthPt,
height: pageHeightPt
});
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);
}
// 还原 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('打印内容准备失败,请重试');
}
},
// -------- 辅助方法:等待所有资源加载(图片+字体+二维码) --------
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);
}
},
};
</script>
<style scoped>
.label-render-container {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
/* padding: 1rem; */
box-sizing: border-box;
}
.action-buttons {
margin-top: 1rem;
}
.preview-container {
background-color: #fff;
color: #000;
/* padding: 1.5rem; */
width: fit-content;
/* min-width: 200px; */
box-shadow: 0 0 60px rgba(255, 255, 255, 0.2);
/* border: 1px solid rgba(255, 255, 255, 0.1); */
overflow: hidden;
transition: all 0.3s ease;
}
.preview-container:hover {
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>