Merge remote-tracking branch 'origin/0.8.X' into 0.8.X

This commit is contained in:
2026-01-10 08:12:21 +08:00
9 changed files with 831 additions and 220 deletions

View File

@@ -163,4 +163,15 @@ export function listCoilOperation({coilIds, planId}) {
pageSize: 1000 pageSize: 1000
} }
}) })
}
// 钢卷导出
export function exportCoilData(coilIds) {
return request({
url: '/wms/materialCoil/export',
method: 'post',
data: {
coilIds
}
})
} }

View File

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

View File

@@ -1,46 +1,63 @@
<template> <template>
<div class="label-container" :style="{ '--print-scale': printScale }"> <div class="label-container" :style="{ '--print-scale': printScale }">
<table class="material-label-table"> <table class="material-label-table">
<!-- 调整第一行结构使label和value各占2列等宽 --> <!-- 调整第一行结构左侧内容扩大右侧二维码减小边距 -->
<tr> <tr>
<td colspan="3" style="padding: 0;"> <td colspan="2" style="padding: 0; width: 50%;">
<div <div
style=" style="
display: flex; display: flex;
height: 100%; height: 100%;
align-items: space-evenly; align-items: space-evenly;
text-align: center; text-align: center;
justify-content: space-evenly; justify-content: space-evenly;
flex-direction: column; flex-direction: column;
font-size: 13px;
"> ">
<div style="display: flex; flex: 1; align-items: center;"> <div style="display: flex; flex: 1; align-items: center;">
<div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box;" class="label-cell"> <div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box; padding: 3px; word-break: break-all; overflow-wrap: break-word;" class="label-cell">
料卷号 料卷号
</div> </div>
<div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box;" class="value-cell"> <div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box; padding: 3px; word-break: break-all; overflow-wrap: break-word;" class="value-cell">
{{ content.enterCoilNo || '' }} {{ content.enterCoilNo || '' }}
</div> </div>
</div> </div>
<div style="display: flex; flex: 1; align-items: center;"> <div style="display: flex; flex: 1; align-items: center;">
<div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box;" class="label-cell"> <div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box; padding: 3px; word-break: break-all; overflow-wrap: break-word;" class="label-cell">
逻辑库区 逻辑库区
</div> </div>
<div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box;" class="value-cell"> <div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box; padding: 3px; word-break: break-all; overflow-wrap: break-word;" class="value-cell">
<input type="text" class="nob" :value="content.warehouseName || ''" /> <input type="text" class="nob" :value="content.warehouseName || ''" />
</div> </div>
</div> </div>
<div style="display: flex; flex: 1; align-items: center;"> <div style="display: flex; flex: 1; align-items: center;">
<div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box;" class="label-cell"> <div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box; padding: 3px; word-break: break-all; overflow-wrap: break-word;" class="label-cell">
实际库区 实际库区
</div> </div>
<div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box;" class="value-cell"> <div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box; padding: 3px; word-break: break-all; overflow-wrap: break-word;" class="value-cell">
<input type="text" class="nob" :value="content.actualWarehouseName || ''" /> <input type="text" class="nob" :value="content.actualWarehouseName || ''" />
</div> </div>
</div> </div>
<div style="display: flex; flex: 1; align-items: center;">
<div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box; padding: 3px; word-break: break-all; overflow-wrap: break-word;" class="label-cell">
规格(mm)
</div>
<div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box; padding: 3px; word-break: break-all; overflow-wrap: break-word;" class="value-cell">
<input type="text" class="nob" :value="content.specification || ''" />
</div>
</div>
<div style="display: flex; flex: 1; align-items: center;">
<div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box; padding: 3px; word-break: break-all; overflow-wrap: break-word;" class="label-cell">
生产班组
</div>
<div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #333; box-sizing: border-box; padding: 3px; word-break: break-all; overflow-wrap: break-word;" class="value-cell">
<input type="text" class="nob" :value="content.team || ''" />
</div>
</div>
</div> </div>
</td> </td>
<td class="value-cell" colspan="1"> <td class="value-cell" colspan="4" style="width: 50%; padding: 2px; text-align: center; vertical-align: middle;">
<QRCode :content="content.qrcodeRecordId || ' '" :size="60" /> <QRCode :content="content.qrcodeRecordId || ' '" :size="130" />
</td> </td>
</tr> </tr>
<!-- <tr> <!-- <tr>
@@ -56,34 +73,25 @@
</td> </td>
</tr> --> </tr> -->
<tr> <tr>
<td class="label-cell">规格(mm)</td> <td class="label-cell" style="width: 16.67%; padding: 4px;">净重</td>
<td class="value-cell"> <td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<input type="text" class="nob" :value="content.specification || ''" />
</td>
<td class="label-cell">净重</td>
<td class="value-cell">
<input type="text" class="nob" :value="content.netWeight || ''" /> <input type="text" class="nob" :value="content.netWeight || ''" />
</td> </td>
</tr> <td class="label-cell" style="width: 16.67%; padding: 4px;">材质</td>
<tr> <td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<td class="label-cell">材质</td>
<td class="value-cell">
<input type="text" class="nob" :value="content.material || ''" /> <input type="text" class="nob" :value="content.material || ''" />
</td> </td>
<td class="label-cell">下道</td>
<td class="value-cell">
<input type="text" class="nob" :value="content.nextProcess || ''" />
</td>
</tr> </tr>
<tr> <tr>
<td class="label-cell">生产班组</td> <td class="label-cell" style="width: 16.67%; padding: 4px;">时间</td>
<td class="value-cell"> <td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<input type="text" class="nob" :value="content.team || ''" />
</td>
<td class="label-cell">时间</td>
<td class="value-cell">
<input type="text" class="nob" :value="content.updateTime || ''" /> <input type="text" class="nob" :value="content.updateTime || ''" />
</td> </td>
<td class="label-cell" style="width: 16.67%; padding: 4px;">下道</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<input type="text" class="nob" :value="content.nextProcess || ''" />
</td>
</tr> </tr>
</table> </table>
</div> </div>
@@ -129,11 +137,11 @@ export default {
// 使用 matchMedia 监听打印状态(更可靠) // 使用 matchMedia 监听打印状态(更可靠)
this.printMediaQuery = window.matchMedia('print'); this.printMediaQuery = window.matchMedia('print');
this.printMediaQuery.addListener(this.handlePrintMediaChange); this.printMediaQuery.addListener(this.handlePrintMediaChange);
// 监听打印事件作为备用 // 监听打印事件作为备用
window.addEventListener('beforeprint', this.handleBeforePrint); window.addEventListener('beforeprint', this.handleBeforePrint);
window.addEventListener('afterprint', this.handleAfterPrint); window.addEventListener('afterprint', this.handleAfterPrint);
// 计算初始缩放比例 // 计算初始缩放比例
this.$nextTick(() => { this.$nextTick(() => {
this.calculatePrintScale(); this.calculatePrintScale();
@@ -169,41 +177,41 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
const container = this.$el; const container = this.$el;
if (!container) return; if (!container) return;
// 纸张尺寸180mm x 100mm // 纸张尺寸180mm x 100mm
const dpi = 96; // 标准 DPI const dpi = 96; // 标准 DPI
const mmToPx = dpi / 25.4; // 1mm = 3.779527559px const mmToPx = dpi / 25.4; // 1mm = 3.779527559px
const paperWidthMm = this.paperWidthMm || 100; const paperWidthMm = this.paperWidthMm || 100;
const paperHeightMm = this.paperHeightMm || 80; const paperHeightMm = this.paperHeightMm || 80;
const marginMm = 2; const marginMm = 2;
const paperWidthPx = paperWidthMm * mmToPx; const paperWidthPx = paperWidthMm * mmToPx;
const paperHeightPx = paperHeightMm * mmToPx; const paperHeightPx = paperHeightMm * mmToPx;
const marginPx = marginMm * mmToPx; const marginPx = marginMm * mmToPx;
// 获取内容实际尺寸 // 获取内容实际尺寸
const rect = container.getBoundingClientRect(); const rect = container.getBoundingClientRect();
let contentWidth = rect.width; let contentWidth = rect.width;
let contentHeight = rect.height; let contentHeight = rect.height;
// 如果尺寸为0或无效尝试使用 scrollWidth/scrollHeight // 如果尺寸为0或无效尝试使用 scrollWidth/scrollHeight
if (contentWidth === 0 || contentHeight === 0) { if (contentWidth === 0 || contentHeight === 0) {
contentWidth = container.scrollWidth || contentWidth; contentWidth = container.scrollWidth || contentWidth;
contentHeight = container.scrollHeight || contentHeight; contentHeight = container.scrollHeight || contentHeight;
} }
// 计算可用空间(减去边距) // 计算可用空间(减去边距)
const availableWidth = paperWidthPx - marginPx * 2; const availableWidth = paperWidthPx - marginPx * 2;
const availableHeight = paperHeightPx - marginPx * 2; const availableHeight = paperHeightPx - marginPx * 2;
// 计算缩放比例 // 计算缩放比例
const scaleX = contentWidth > 0 ? availableWidth / contentWidth : 1; const scaleX = contentWidth > 0 ? availableWidth / contentWidth : 1;
const scaleY = contentHeight > 0 ? availableHeight / contentHeight : 1; const scaleY = contentHeight > 0 ? availableHeight / contentHeight : 1;
// 取较小的缩放比例确保内容完全适配不超过1不放大 // 取较小的缩放比例确保内容完全适配不超过1不放大
this.printScale = Math.min(scaleX, scaleY, 1); this.printScale = Math.min(scaleX, scaleY, 1);
// 设置CSS变量和内联样式 // 设置CSS变量和内联样式
container.style.setProperty('--print-scale', this.printScale); container.style.setProperty('--print-scale', this.printScale);
container.style.setProperty('--paper-width', `${paperWidthMm}mm`); container.style.setProperty('--paper-width', `${paperWidthMm}mm`);
@@ -239,6 +247,7 @@ export default {
table-layout: fixed; table-layout: fixed;
flex-grow: 1; /* 让表格拉伸,占满剩余垂直空间 */ flex-grow: 1; /* 让表格拉伸,占满剩余垂直空间 */
height: 0; /* 配合flex-grow自动计算高度 */ height: 0; /* 配合flex-grow自动计算高度 */
border: 1px solid #333; /* 确保表格有最外层边框 */
} }
/* 四列均分宽度每列占25% */ /* 四列均分宽度每列占25% */
@@ -249,6 +258,10 @@ export default {
/* width: 25%; */ /* width: 25%; */
box-sizing: border-box; box-sizing: border-box;
text-align: center; text-align: center;
word-break: break-all;
overflow-wrap: break-word;
white-space: normal;
overflow: hidden;
} }
.label-cell { .label-cell {
@@ -267,6 +280,10 @@ export default {
outline: none; outline: none;
background: transparent; background: transparent;
text-align: center; text-align: center;
font-size: inherit;
word-break: break-all;
overflow-wrap: break-word;
white-space: normal;
} }
/* 打印样式 - 强制单页适配180mm x 100mm纸张保持原有样式不变 */ /* 打印样式 - 强制单页适配180mm x 100mm纸张保持原有样式不变 */
@@ -303,15 +320,15 @@ export default {
break-before: avoid !important; break-before: avoid !important;
orphans: 999 !important; orphans: 999 !important;
widows: 999 !important; widows: 999 !important;
/* 应用动态缩放,保持原有样式 */ /* 应用动态缩放,保持原有样式 */
transform: scale(var(--print-scale, 1)) !important; transform: scale(var(--print-scale, 1)) !important;
transform-origin: top left !important; transform-origin: top left !important;
/* 确保缩放后不超出纸张 */ /* 确保缩放后不超出纸张 */
max-width: var(--paper-width, 100mm) !important; max-width: var(--paper-width, 100mm) !important;
max-height: var(--paper-height, 80mm) !important; max-height: var(--paper-height, 80mm) !important;
overflow: hidden !important; overflow: hidden !important;
} }
} }
</style> </style>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="label-render-container"> <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 <ProductionTagPreview
v-if="labelType === '2'" v-if="labelType === '2'"
:content="content" :content="content"
@@ -18,7 +18,7 @@
<ForgeTagPreview v-if="labelType === '5'" :content="content" /> <ForgeTagPreview v-if="labelType === '5'" :content="content" />
<SaltSprayTagPreview v-if="labelType === '6'" :content="content" /> <SaltSprayTagPreview v-if="labelType === '6'" :content="content" />
</div> </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="downloadLabelAsImage">下载标签图片</el-button>
<el-button type="primary" @click="printLabel" style="margin-left: 10px;">打印标签</el-button> <el-button type="primary" @click="printLabel" style="margin-left: 10px;">打印标签</el-button>
</div> </div>
@@ -27,9 +27,9 @@
<script> <script>
import domToImage from 'dom-to-image'; 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 { Message } from 'element-ui';
import { PDFDocument } from 'pdf-lib';
import ProductionTagPreview from './ProductionTagPreview.vue'; import ProductionTagPreview from './ProductionTagPreview.vue';
import OuterTagPreview from './OuterTagPreview.vue'; import OuterTagPreview from './OuterTagPreview.vue';
@@ -55,11 +55,15 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
hideActions: {
type: Boolean,
default: false,
},
}, },
methods: { methods: {
// -------- 图片下载方法保留可按需替换为html2canvas -------- // -------- 图片下载方法保留可按需替换为html2canvas --------
async downloadLabelAsImage() { async downloadLabelAsImage() {
const labelContainer = document.getElementById('label-preview-container'); const labelContainer = this.$refs.labelRef;
if (!labelContainer) { if (!labelContainer) {
Message.error('未找到标签容器,无法下载'); Message.error('未找到标签容器,无法下载');
return; return;
@@ -83,11 +87,12 @@ export default {
} }
}, },
// -------- 重构后的打印方法(核心优化) -------- // -------- 单张打印:先生成 PDF再让浏览器打印 --------
async printLabel() { async printLabel() {
// 1. 获取标签容器DOM // 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) { if (!labelContainer) {
Message.error('未找到标签容器,无法打印'); Message.error('未找到标签容器,无法打印');
return; return;
@@ -96,137 +101,128 @@ export default {
try { try {
Message.info('正在准备打印内容,请稍等...'); Message.info('正在准备打印内容,请稍等...');
// 2. 等待二维码/字体等资源加载完成(复用之前的等待逻辑 // 2. 计算纸张尺寸(与批量导出保持一致
await this.waitForAllResources(labelContainer); // 根据 labelType 判断:'2' 是材料标签100x80'3' 是外标180x100
const isMaterial = this.labelType === '2';
// 3. 计算纸张尺寸和缩放比例
// 根据标签类型设置纸张尺寸:
// 产品码100mm x 180mm横向原先180x100
// 原料码80mm x 100mm竖向
const isMaterial = labelContainer.classList.contains('material-label-container');
const paperWidthMm = isMaterial ? 100 : 180; const paperWidthMm = isMaterial ? 100 : 180;
const paperHeightMm = isMaterial ? 80 : 100; 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但不超过纸张尺寸 // 使用合适的scale值生成高清Canvas但不超过纸张尺寸
const canvasScale = Math.min(2, printScale * 2); // 适当提高清晰度 const canvasScale = 3; // 提高清晰度(单张打印)
// 4. 用html2canvas生成高清Canvas解决文字模糊+二维码丢失) // 3. 为单张打印临时创建一个与批量打印完全一致的隐藏容器
// 注意:不再强制指定 width/height避免裁掉最右/最下边框 // 完全模拟批量打印的 .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, { const canvas = await html2canvas(labelContainer, {
scale: canvasScale, backgroundColor: '#ffffff',
backgroundColor: '#ffffff', // 强制白色背景,避免打印时背景透明 scale: 3,
useCORS: true, // 支持跨域图片 useCORS: true,
allowTaint: true, // 允许渲染canvas二维码 // 让 html2canvas 为频繁读回优化 Canvas与批量导出一致
taintTest: false, // 关闭canvas污染检测 willReadFrequently: true,
logging: false, // 确保按元素尺寸截图(与批量导出完全一致)
width: labelContainer.offsetWidth,
height: labelContainer.offsetHeight,
windowWidth: labelContainer.scrollWidth,
windowHeight: labelContainer.scrollHeight,
}); });
// 5. 如果Canvas尺寸超出纸张需要缩放Canvas // 5. 使用 pdf-lib 生成单页 PDF与批量导出完全一致
let finalCanvas = canvas; const mmToPt = 72 / 25.4;
if (canvas.width > paperWidthPx || canvas.height > paperHeightPx) { const pageWidthPt = paperWidthMm * mmToPt;
// 创建新的Canvas尺寸适配纸张 const pageHeightPt = paperHeightMm * mmToPt;
const scaledCanvas = document.createElement('canvas');
scaledCanvas.width = Math.min(canvas.width, paperWidthPx); // 边距:与批量导出保持一致
scaledCanvas.height = Math.min(canvas.height, paperHeightPx); // 材料标签100x80左右2mm上下0.5mm
const ctx = scaledCanvas.getContext('2d'); // 成品标签180x100左右4mm上下0.5mm
ctx.drawImage(canvas, 0, 0, scaledCanvas.width, scaledCanvas.height); const marginXmm = isMaterial ? 2 : 4;
finalCanvas = scaledCanvas; 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 模式(单图),并减少额外样式以避免隐藏内容 // 还原 DOM 结构
const dataUrl = finalCanvas.toDataURL('image/png'); // 恢复 labelContainer 的原始样式(如果之前修改过)
if (isMaterial) {
// 6. 创建隐藏 iframe 进行打印,避免 printJS 的分页行为 labelContainer.style.width = '';
const iframe = document.createElement('iframe'); labelContainer.style.height = '';
iframe.style.position = 'fixed'; }
iframe.style.right = '0'; // 成品标签不需要恢复,因为没有修改样式
iframe.style.bottom = '0'; if (originalParent) {
iframe.style.width = '0'; if (originalNext) {
iframe.style.height = '0'; originalParent.insertBefore(labelContainer, originalNext);
iframe.style.border = '0'; } else {
document.body.appendChild(iframe); originalParent.appendChild(labelContainer);
}
const doc = iframe.contentWindow.document; }
doc.open(); document.body.removeChild(wrapper);
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);
};
} catch (error) { } catch (error) {
console.error('打印准备失败:', error); console.error('打印准备失败:', error);
Message.error('打印内容准备失败,请重试'); Message.error('打印内容准备失败,请重试');
@@ -285,6 +281,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.label-render-container { .label-render-container {
width: 100%; width: 100%;
display: flex; display: flex;

View File

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

View File

@@ -199,6 +199,9 @@
<ActualWarehouseSelect v-model="item.actualWarehouseId" placeholder="请选择真实库区" block <ActualWarehouseSelect v-model="item.actualWarehouseId" placeholder="请选择真实库区" block
:disabled="readonly" /> :disabled="readonly" />
</el-form-item> </el-form-item>
<el-form-item label="备注">
<el-input v-model="item.remark" placeholder="请输入备注" :disabled="readonly"/>
</el-form-item>
</el-form> </el-form>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,164 @@
<template>
<div class="app-container" v-loading="loading">
<el-row>
<el-form label-width="80px" inline>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker style="width: 200px;" v-model="queryParams.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择开始时间"></el-date-picker>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker style="width: 200px;" v-model="queryParams.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择结束时间"></el-date-picker>
</el-form-item>
<el-form-item label="入场钢卷号" prop="endTime">
<el-input style="width: 200px; display: inline-block;" v-model="queryParams.enterCoilNo"
placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="当前钢卷号" prop="endTime">
<el-input style="width: 200px;" v-model="queryParams.currentCoilNo" placeholder="请输入当前钢卷号" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="逻辑库位" prop="endTime">
<warehouse-select v-model="queryParams.warehouseId" placeholder="请选择仓库/库区/库位"
style="width: 100%; display: inline-block; width: 200px;" clearable />
</el-form-item>
<el-form-item label="产品名称" prop="endTime">
<el-input style="width: 200px;" v-model="queryParams.itemName" placeholder="请输入产品名称" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="规格" prop="endTime">
<memo-input style="width: 200px;" v-model="queryParams.itemSpecification" storageKey="coilSpec"
placeholder="请选择规格" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="材质" prop="endTime">
<muti-select style="width: 200px;" v-model="queryParams.itemMaterial" :options="dict.type.coil_material"
placeholder="请选择材质" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="厂家" prop="endTime">
<muti-select style="width: 200px;" v-model="queryParams.itemManufacturer"
:options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item prop="endTime">
<el-button type="primary" @click="getList">查询</el-button>
<el-button type="primary" @click="exportData">导出</el-button>
</el-form-item>
</el-form>
</el-row>
<el-descriptions title="统计信息" :column="3" border>
<el-descriptions-item label="总钢卷数量">{{ summary.totalCount }}</el-descriptions-item>
<el-descriptions-item label="总重">{{ summary.totalWeight }}t</el-descriptions-item>
<el-descriptions-item label="均重">{{ summary.avgWeight }}t</el-descriptions-item>
</el-descriptions>
<el-descriptions title="明细信息" :column="3" border>
</el-descriptions>
<el-table :data="list" border height="calc(100vh - 320px)">
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo">
<template slot-scope="scope">
<coil-no :coil-no="scope.row.enterCoilNo"></coil-no>
</template>
</el-table-column>
<el-table-column label="当前钢卷号" align="center" prop="currentCoilNo">
<template slot-scope="scope">
<coil-no :coil-no="scope.row.currentCoilNo"></coil-no>
</template>
</el-table-column>
<el-table-column label="逻辑库位" align="center" prop="warehouseName" />
<el-table-column label="实际库区" align="center" prop="actualWarehouseName" />
<el-table-column label="产品类型" align="center" width="250">
<template slot-scope="scope">
<ProductInfo v-if="scope.row.itemType == 'product'" :product="scope.row.product" />
<RawMaterialInfo v-else-if="scope.row.itemType === 'raw_material'" :material="scope.row.rawMaterial" />
</template>
</el-table-column>
<el-table-column label="重量 (吨)" align="center" prop="netWeight" />
<el-table-column label="长度 (米)" align="center" prop="length" />
<el-table-column label="发货时间" align="center" prop="exportTime" />
<el-table-column label="更新人" align="center" prop="updateByName" />
</el-table>
</div>
</template>
<script>
import { listMaterialCoil } from "@/api/wms/coil";
import ProductInfo from "@/components/KLPService/Renderer/ProductInfo";
import RawMaterialInfo from "@/components/KLPService/Renderer/RawMaterialInfo";
import CoilNo from "@/components/KLPService/Renderer/CoilNo.vue";
import MemoInput from "@/components/MemoInput";
import MutiSelect from "@/components/MutiSelect";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
export default {
components: {
ProductInfo,
RawMaterialInfo,
CoilNo,
MemoInput,
MutiSelect,
WarehouseSelect
},
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer'],
data() {
// 获取昨天0点到今天0点的时间范围
const now = new Date()
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
const startTime = now.toISOString().slice(0, 10) + ' 00:00:00'
const endTime = now.toISOString().slice(0, 10) + ' 23:59:59'
return {
list: [],
queryParams: {
pageNum: 1,
pageSize: 9999,
status: 1,
startTime: startTime,
endTime: endTime,
selectType: 'product',
enterCoilNo: '',
currentCoilNo: '',
warehouseId: '',
productName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: '',
},
loading: false,
}
},
computed: {
summary() {
// 总钢卷数量、总重、均重
const totalCount = this.list.length
const totalWeight = this.list.reduce((acc, cur) => acc + parseFloat(cur.netWeight), 0)
const avgWeight = totalCount > 0 ? (totalWeight / totalCount).toFixed(2) : 0
return {
totalCount,
totalWeight: totalWeight.toFixed(2),
avgWeight,
}
}
},
methods: {
getList() {
this.loading = true
listMaterialCoil({
...this.queryParams
}).then(res => {
this.list = res.rows
this.loading = false
})
},
// 导出
exportData() {
this.download('wms/materialCoil/export', {
coilIds: this.list.map(item => item.coilId).join(',')
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
},
mounted() {
this.getList()
}
}
</script>
<style scoped></style>

View File

@@ -0,0 +1,187 @@
<template>
<div class="app-container" v-loading="loading">
<el-row>
<el-form label-width="80px" inline>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker style="width: 200px;" v-model="queryParams.startTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择开始时间"></el-date-picker>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker style="width: 200px;" v-model="queryParams.endTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择结束时间"></el-date-picker>
</el-form-item>
<el-form-item label="入场钢卷号" prop="endTime">
<el-input style="width: 200px; display: inline-block;" v-model="queryParams.enterCoilNo"
placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="当前钢卷号" prop="endTime">
<el-input style="width: 200px;" v-model="queryParams.currentCoilNo" placeholder="请输入当前钢卷号" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="逻辑库位" prop="endTime">
<warehouse-select v-model="queryParams.warehouseId" placeholder="请选择仓库/库区/库位"
style="width: 100%; display: inline-block; width: 200px;" clearable />
</el-form-item>
<el-form-item label="产品名称" prop="endTime">
<el-input style="width: 200px;" v-model="queryParams.itemName" placeholder="请输入产品名称" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="规格" prop="endTime">
<memo-input style="width: 200px;" v-model="queryParams.itemSpecification" storageKey="coilSpec"
placeholder="请选择规格" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="材质" prop="endTime">
<muti-select style="width: 200px;" v-model="queryParams.itemMaterial" :options="dict.type.coil_material"
placeholder="请选择材质" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="厂家" prop="endTime">
<muti-select style="width: 200px;" v-model="queryParams.itemManufacturer"
:options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item prop="endTime">
<el-button type="primary" @click="getList">查询</el-button>
<el-button type="primary" @click="exportData">导出</el-button>
</el-form-item>
</el-form>
</el-row>
<el-descriptions title="统计信息" :column="3" border>
<el-descriptions-item label="总钢卷数量">{{ summary.totalCount }}</el-descriptions-item>
<el-descriptions-item label="总重">{{ summary.totalWeight }}t</el-descriptions-item>
<el-descriptions-item label="均重">{{ summary.avgWeight }}t</el-descriptions-item>
</el-descriptions>
<el-descriptions title="明细信息" :column="3" border>
</el-descriptions>
<el-table :data="list" border height="calc(100vh - 320px)">
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo">
<template slot-scope="scope">
<coil-no :coil-no="scope.row.enterCoilNo"></coil-no>
</template>
</el-table-column>
<el-table-column label="当前钢卷号" align="center" prop="currentCoilNo">
<template slot-scope="scope">
<coil-no :coil-no="scope.row.currentCoilNo"></coil-no>
</template>
</el-table-column>
<el-table-column label="逻辑库位" align="center" prop="warehouseName" />
<el-table-column label="实际库区" align="center" prop="actualWarehouseName" />
<el-table-column label="产品类型" align="center" width="250">
<template slot-scope="scope">
<ProductInfo v-if="scope.row.itemType == 'product'" :product="scope.row.product" />
<RawMaterialInfo v-else-if="scope.row.itemType === 'raw_material'" :material="scope.row.rawMaterial" />
</template>
</el-table-column>
<el-table-column label="重量 (吨)" align="center" prop="netWeight" />
<el-table-column label="长度 (米)" align="center" prop="length" />
<el-table-column label="更新人" align="center" prop="updateByName" />
</el-table>
</div>
</template>
<script>
import { listMaterialCoil } from "@/api/wms/coil";
import {
listPendingAction,
} from '@/api/wms/pendingAction';
import ProductInfo from "@/components/KLPService/Renderer/ProductInfo";
import RawMaterialInfo from "@/components/KLPService/Renderer/RawMaterialInfo";
import CoilNo from "@/components/KLPService/Renderer/CoilNo.vue";
import MemoInput from "@/components/MemoInput";
import MutiSelect from "@/components/MutiSelect";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
export default {
components: {
ProductInfo,
RawMaterialInfo,
CoilNo,
MemoInput,
MutiSelect,
WarehouseSelect,
},
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer'],
data() {
// 获取昨天0点到今天0点的时间范围
const now = new Date()
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
const startTime = now.toISOString().slice(0, 10) + ' 00:00:00'
const endTime = now.toISOString().slice(0, 10) + ' 23:59:59'
return {
list: [],
queryParams: {
pageNum: 1,
pageSize: 9999,
status: 1,
startTime: startTime,
endTime: endTime,
selectType: 'product',
enterCoilNo: '',
currentCoilNo: '',
warehouseId: '',
productName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: '',
},
loading: false,
}
},
computed: {
summary() {
// 总钢卷数量、总重、均重
const totalCount = this.list.length
const totalWeight = this.list.reduce((acc, cur) => acc + parseFloat(cur.netWeight), 0)
const avgWeight = totalCount > 0 ? (totalWeight / totalCount).toFixed(2) : 0
return {
totalCount,
totalWeight: totalWeight.toFixed(2),
avgWeight,
}
}
},
methods: {
getList() {
this.loading = true
listPendingAction({
// actionStatus: 2,
actionType: 401,
pageSize: 999,
pageNum: 1,
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime,
}).then(res => {
const actions = res.rows
const coilIds = actions.map(item => item.coilId).join(',')
console.log(coilIds)
if (!coilIds) {
this.$message({
message: '暂无数据',
type: 'warning',
})
this.loading = false
return
}
listMaterialCoil({
...this.queryParams,
coilIds: coilIds,
}).then(res => {
this.list = res.rows
this.loading = false
})
})
},
// 导出
exportData() {
this.download('wms/materialCoil/export', {
coilIds: this.list.map(item => item.coilId).join(',')
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
},
mounted() {
this.getList()
}
}
</script>
<style scoped></style>

View File

@@ -0,0 +1,225 @@
<template>
<div class="app-container" v-loading="loading">
<el-row>
<el-form label-width="80px" inline>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker style="width: 200px;" v-model="queryParams.startTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择开始时间"></el-date-picker>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker style="width: 200px;" v-model="queryParams.endTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择结束时间"></el-date-picker>
</el-form-item>
<el-form-item label="入场钢卷号" prop="endTime">
<el-input style="width: 200px; display: inline-block;" v-model="queryParams.enterCoilNo"
placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="当前钢卷号" prop="endTime">
<el-input style="width: 200px;" v-model="queryParams.currentCoilNo" placeholder="请输入当前钢卷号" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="逻辑库位" prop="endTime">
<warehouse-select v-model="queryParams.warehouseId" placeholder="请选择仓库/库区/库位"
style="width: 100%; display: inline-block; width: 200px;" clearable />
</el-form-item>
<el-form-item label="产品名称" prop="endTime">
<el-input style="width: 200px;" v-model="queryParams.itemName" placeholder="请输入产品名称" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="规格" prop="endTime">
<memo-input style="width: 200px;" v-model="queryParams.itemSpecification" storageKey="coilSpec"
placeholder="请选择规格" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="材质" prop="endTime">
<muti-select style="width: 200px;" v-model="queryParams.itemMaterial" :options="dict.type.coil_material"
placeholder="请选择材质" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="厂家" prop="endTime">
<muti-select style="width: 200px;" v-model="queryParams.itemManufacturer"
:options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item prop="endTime">
<el-button type="primary" @click="getList">查询</el-button>
<el-button type="primary" @click="exportData">导出</el-button>
</el-form-item>
</el-form>
</el-row>
<el-descriptions title="统计信息" :column="3" border>
<el-descriptions-item label="总钢卷数量">{{ summary.totalCount }}</el-descriptions-item>
<el-descriptions-item label="总重">{{ summary.totalWeight }}t</el-descriptions-item>
<el-descriptions-item label="均重">{{ summary.avgWeight }}t</el-descriptions-item>
</el-descriptions>
<el-descriptions title="明细信息" :column="3" border>
</el-descriptions>
<el-table :data="list" border height="calc(100vh - 320px)">
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo">
<template slot-scope="scope">
<coil-no :coil-no="scope.row.enterCoilNo"></coil-no>
</template>
</el-table-column>
<el-table-column label="当前钢卷号" align="center" prop="currentCoilNo">
<template slot-scope="scope">
<coil-no :coil-no="scope.row.currentCoilNo"></coil-no>
</template>
</el-table-column>
<el-table-column label="逻辑库位" align="center" prop="warehouseName" />
<el-table-column label="实际库区" align="center" prop="actualWarehouseName" />
<el-table-column label="产品类型" align="center" width="250">
<template slot-scope="scope">
<ProductInfo v-if="scope.row.itemType == 'product'" :product="scope.row.product" />
<RawMaterialInfo v-else-if="scope.row.itemType === 'raw_material'" :material="scope.row.rawMaterial" />
</template>
</el-table-column>
<el-table-column label="长度 (米)" align="center" prop="length" />
<el-table-column label="更新人" align="center" prop="updateByName" />
</el-table>
</div>
</template>
<script>
import { listMaterialCoil } from "@/api/wms/coil";
import {
listPendingAction,
} from '@/api/wms/pendingAction';
import ProductInfo from "@/components/KLPService/Renderer/ProductInfo";
import RawMaterialInfo from "@/components/KLPService/Renderer/RawMaterialInfo";
import CoilNo from "@/components/KLPService/Renderer/CoilNo.vue";
import MemoInput from "@/components/MemoInput";
import MutiSelect from "@/components/MutiSelect";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
export default {
components: {
ProductInfo,
RawMaterialInfo,
CoilNo,
MemoInput,
MutiSelect,
WarehouseSelect,
},
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer'],
data() {
// 获取昨天0点到今天0点的时间范围
const now = new Date()
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
const startTime = now.toISOString().slice(0, 10) + ' 00:00:00'
const endTime = now.toISOString().slice(0, 10) + ' 23:59:59'
return {
list: [],
queryParams: {
pageNum: 1,
pageSize: 9999,
status: 1,
startTime: startTime,
endTime: endTime,
selectType: 'product',
enterCoilNo: '',
currentCoilNo: '',
warehouseId: '',
productName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: '',
},
loading: false,
}
},
computed: {
summary() {
// 总钢卷数量、总重、均重
const totalCount = this.list.length
const totalWeight = this.list.reduce((acc, cur) => acc + parseFloat(cur.netWeight), 0)
const avgWeight = totalCount > 0 ? (totalWeight / totalCount).toFixed(2) : 0
return {
totalCount,
totalWeight: totalWeight.toFixed(2),
avgWeight,
}
}
},
methods: {
getList() {
this.loading = true
Promise.all([
// 酸连轧成品库
listMaterialCoil({
...this.queryParams,
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime,
pageSize: 9999,
pageNum: 1,
dataType: 1,
createBy: 'suanzhakuguan',
warehouseId: '1988150099140866050'
}),
// 镀锌原料库
listMaterialCoil({
...this.queryParams,
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime,
pageSize: 9999,
pageNum: 1,
dataType: 1,
createBy: 'suanzhakuguan',
warehouseId: '1988150263284953089'
}),
// 脱脂原料库
listMaterialCoil({
...this.queryParams,
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime,
pageSize: 9999,
pageNum: 1,
dataType: 1,
createBy: 'suanzhakuguan',
warehouseId: '1988150545175736322'
}),
]).then(([res1, res2, res3]) => {
console.log(res1, res2, res3)
this.list = [...res1.rows, ...res2.rows, ...res3.rows]
this.loading = false
})
// listPendingAction({
// // actionStatus: 2,
// actionType: 11,
// pageSize: 999,
// pageNum: 1,
// startTime: this.queryParams.startTime,
// endTime: this.queryParams.endTime,
// }).then(res => {
// const actions = res.rows
// const coilIds = actions.map(item => item.coilId).join(',')
// console.log(coilIds)
// if (!coilIds) {
// this.$message({
// message: '暂无数据',
// type: 'warning',
// })
// this.loading = false
// return
// }
// listMaterialCoil({
// coilIds: coilIds,
// }).then(res => {
// this.list = res.rows
// this.loading = false
// })
// })
},
// 导出
exportData() {
this.download('wms/materialCoil/export', {
coilIds: this.list.map(item => item.coilId).join(',')
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
},
mounted() {
this.getList()
}
}
</script>
<style scoped></style>