fix(wms): 修复标签打印容器选择器和PDF导出空白问题
重构标签容器选择器从ID改为class选择器,解决打印功能失效问题 优化PDF导出逻辑,动态设置页面尺寸并裁剪空白区域,彻底解决多页导出时的空白页问题 统一重量和长度输入框为el-input-number组件,提升表单交互体验
This commit is contained in:
@@ -175,22 +175,22 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="毛重(t)">
|
||||
<el-input v-model="targetCoil.grossWeight" placeholder="请输入毛重" type="number" step="0.01"
|
||||
<el-input-number :controls="false" v-model="targetCoil.grossWeight" placeholder="请输入毛重" type="number" step="0.01"
|
||||
:disabled="readonly">
|
||||
<template slot="append">吨</template>
|
||||
</el-input>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="净重(t)">
|
||||
<el-input v-model="targetCoil.netWeight" placeholder="请输入净重" type="number" step="0.01"
|
||||
<el-input-number :controls="false" v-model="targetCoil.netWeight" placeholder="请输入净重" type="number" step="0.01"
|
||||
:disabled="readonly">
|
||||
<template slot="append">吨</template>
|
||||
</el-input>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="长度(m)">
|
||||
<el-input v-model="targetCoil.length" placeholder="请输入长度" type="number" step="0.01"
|
||||
<el-input-number :controls="false" v-model="targetCoil.length" placeholder="请输入长度" type="number" step="0.01"
|
||||
:disabled="readonly">
|
||||
<template slot="append">米</template>
|
||||
</el-input>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="逻辑库区">
|
||||
<WarehouseSelect
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="material-label-container" :style="{ '--print-scale': printScale }">
|
||||
<div class="label-container" :style="{ '--print-scale': printScale }">
|
||||
<table class="material-label-table">
|
||||
<!-- 调整第一行结构,使label和value各占2列(等宽) -->
|
||||
<tr>
|
||||
@@ -144,7 +144,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.material-label-container {
|
||||
.label-container {
|
||||
width: 25em;
|
||||
height: 15em;
|
||||
padding: 0px;
|
||||
@@ -212,11 +212,11 @@ export default {
|
||||
}
|
||||
|
||||
/* 隐藏所有其他内容,只显示标签 */
|
||||
body > *:not(.material-label-container) {
|
||||
body > *:not(.label-container) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.material-label-container {
|
||||
.label-container {
|
||||
/* 使用固定纸张尺寸 */
|
||||
width: 180mm !important;
|
||||
height: 100mm !important;
|
||||
|
||||
@@ -76,7 +76,7 @@ export default {
|
||||
// -------- 重构后的打印方法(核心优化) --------
|
||||
async printLabel() {
|
||||
// 1. 获取标签容器DOM
|
||||
const labelContainer = document.getElementById('label-preview-container');
|
||||
const labelContainer = document.querySelector('.label-container');
|
||||
if (!labelContainer) {
|
||||
Message.error('未找到标签容器,无法打印');
|
||||
return;
|
||||
|
||||
@@ -3,22 +3,15 @@
|
||||
<!-- 左侧:原有导出功能区域 -->
|
||||
<div style="flex: 1; max-width: 700px;">
|
||||
<!-- 导出配置区域 -->
|
||||
<div class="config-container" style="margin-bottom: 20px; padding: 16px; border: 1px solid #e6e6e6; border-radius: 8px;">
|
||||
<div class="config-container"
|
||||
style="margin-bottom: 20px; padding: 16px; border: 1px solid #e6e6e6; border-radius: 8px;">
|
||||
<div style="font-size: 16px; font-weight: 600; margin-bottom: 12px;">导出配置</div>
|
||||
|
||||
|
||||
<!-- 清晰度配置 -->
|
||||
<div class="config-item" style="margin-bottom: 10px; display: flex; align-items: center;">
|
||||
<label style="width: 120px; font-size: 14px; color: #333;">导出清晰度:</label>
|
||||
<input
|
||||
v-model.number="config.renderScale"
|
||||
type="number"
|
||||
min="1.0"
|
||||
max="4.0"
|
||||
step="0.1"
|
||||
style="width: 120px;"
|
||||
placeholder="如:1.5/2.0/3.0"
|
||||
@blur="validateRenderScale"
|
||||
/>
|
||||
<input v-model.number="config.renderScale" type="number" min="1.0" max="4.0" step="0.1" style="width: 120px;"
|
||||
placeholder="如:1.5/2.0/3.0" @blur="validateRenderScale" />
|
||||
<span style="margin-left: 8px; font-size: 12px; color: #666;">
|
||||
范围:1.0-4.0(值越高越清晰,导出越慢)
|
||||
</span>
|
||||
@@ -27,16 +20,8 @@
|
||||
<!-- 单次导出数量配置 -->
|
||||
<div class="config-item" style="display: flex; align-items: center;">
|
||||
<label style="width: 120px; font-size: 14px; color: #333;">单次导出数量:</label>
|
||||
<input
|
||||
v-model.number="config.pageSize"
|
||||
type="number"
|
||||
min="1"
|
||||
max="200"
|
||||
style="width: 120px;"
|
||||
placeholder="如:10/50/100"
|
||||
@blur="validatePageSize"
|
||||
@change="replanTask"
|
||||
/>
|
||||
<input v-model.number="config.pageSize" type="number" min="1" max="200" style="width: 120px;"
|
||||
placeholder="如:10/50/100" @blur="validatePageSize" @change="replanTask" />
|
||||
<span style="margin-left: 8px; font-size: 12px; color: #666;">
|
||||
范围:1-200(建议不超过100,避免卡顿)
|
||||
</span>
|
||||
@@ -44,11 +29,8 @@
|
||||
</div>
|
||||
|
||||
<!-- 导出按钮:批量导出所有未完成任务 -->
|
||||
<button
|
||||
@click="handleExportAll"
|
||||
:disabled="exportLoading || taskPlan.totalTask === 0 || hasRunningTask"
|
||||
style="padding: 8px 16px; cursor: pointer; margin-bottom: 20px;"
|
||||
>
|
||||
<button @click="handleExportAll" :disabled="exportLoading || taskPlan.totalTask === 0 || hasRunningTask"
|
||||
style="padding: 8px 16px; cursor: pointer; margin-bottom: 20px;">
|
||||
{{ exportLoading ? '导出中...' : '批量导出所有未完成任务' }}
|
||||
</button>
|
||||
|
||||
@@ -57,22 +39,19 @@
|
||||
<div class="progress-text" style="margin-bottom: 8px; font-size: 14px; color: #666;">
|
||||
当前批次进度:{{ currentIndex }}/{{ totalCount }} ({{ progress }}%)
|
||||
</div>
|
||||
<div class="progress-bar-bg" style="width: 100%; height: 8px; background: #f5f5f5; border-radius: 4px; overflow: hidden;">
|
||||
<div
|
||||
class="progress-bar"
|
||||
style="height: 100%; background: #409eff; transition: width 0.3s ease;"
|
||||
:style="{ width: `${progress}%` }"
|
||||
></div>
|
||||
<div class="progress-bar-bg"
|
||||
style="width: 100%; height: 8px; background: #f5f5f5; border-radius: 4px; overflow: hidden;">
|
||||
<div class="progress-bar" style="height: 100%; background: #409eff; transition: width 0.3s ease;"
|
||||
:style="{ width: `${progress}%` }"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览组件 -->
|
||||
<OuterTagPreview ref="outerTagPreview" :content="current" />
|
||||
<!-- 临时渲染容器 -->
|
||||
<div
|
||||
ref="tempTagContainer"
|
||||
style="position: fixed; top: -9999px; left: -9999px; z-index: -9999; width: 794px; padding: 10px;"
|
||||
></div>
|
||||
<!-- 临时渲染容器【已修复:解决留白核心样式】 -->
|
||||
<div ref="tempTagContainer"
|
||||
style="position: fixed; top: -9999px; left: -9999px; z-index: -9999; width: 794px; padding: 10px; box-sizing: border-box; overflow: hidden !important;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:任务规划+进度展示区域 -->
|
||||
@@ -105,10 +84,8 @@
|
||||
<span>{{ taskPlan.totalProgress }}%</span>
|
||||
</div>
|
||||
<div style="width: 100%; height: 8px; background: #f5f5f5; border-radius: 4px; overflow: hidden;">
|
||||
<div
|
||||
style="height: 100%; background: #67c23a; transition: width 0.3s ease;"
|
||||
:style="{ width: `${taskPlan.totalProgress}%` }"
|
||||
></div>
|
||||
<div style="height: 100%; background: #67c23a; transition: width 0.3s ease;"
|
||||
:style="{ width: `${taskPlan.totalProgress}%` }"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -126,15 +103,11 @@
|
||||
|
||||
<!-- 任务列表 -->
|
||||
<div style="max-height: 400px; overflow-y: auto;">
|
||||
<div
|
||||
v-for="(task, idx) in taskList"
|
||||
:key="idx"
|
||||
style="padding: 10px; border: 1px solid #e6e6e6; border-radius: 6px; margin-bottom: 8px;"
|
||||
:style="{
|
||||
<div v-for="(task, idx) in taskList" :key="idx"
|
||||
style="padding: 10px; border: 1px solid #e6e6e6; border-radius: 6px; margin-bottom: 8px;" :style="{
|
||||
borderColor: task.status === 'running' ? '#409eff' : task.status === 'completed' ? '#67c23a' : '#e6e6e6',
|
||||
background: task.status === 'running' ? '#f0f9ff' : 'transparent'
|
||||
}"
|
||||
>
|
||||
}">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
||||
<span style="font-size: 14px; font-weight: 500;">任务 {{ idx + 1 }}</span>
|
||||
<span :style="{ color: task.statusColor }">{{ task.statusText }}</span>
|
||||
@@ -142,21 +115,17 @@
|
||||
<div style="font-size: 12px; color: #666; margin-bottom: 6px;">
|
||||
处理范围:{{ task.startNum }} - {{ task.endNum }} 条
|
||||
</div>
|
||||
<div style="width: 100%; height: 6px; background: #f5f5f5; border-radius: 3px; overflow: hidden; margin-bottom: 8px;">
|
||||
<div
|
||||
style="height: 100%; transition: width 0.3s ease;"
|
||||
:style="{
|
||||
width: `${task.progress}%`,
|
||||
background: task.status === 'running' ? '#409eff' : task.status === 'completed' ? '#67c23a' : '#ccc'
|
||||
}"
|
||||
></div>
|
||||
<div
|
||||
style="width: 100%; height: 6px; background: #f5f5f5; border-radius: 3px; overflow: hidden; margin-bottom: 8px;">
|
||||
<div style="height: 100%; transition: width 0.3s ease;" :style="{
|
||||
width: `${task.progress}%`,
|
||||
background: task.status === 'running' ? '#409eff' : task.status === 'completed' ? '#67c23a' : '#ccc'
|
||||
}"></div>
|
||||
</div>
|
||||
<!-- 单个任务执行按钮 -->
|
||||
<button
|
||||
@click="handleRunSingleTask(task.idx)"
|
||||
<button @click="handleRunSingleTask(task.idx)"
|
||||
:disabled="exportLoading || task.status === 'running' || task.status === 'completed'"
|
||||
style="padding: 4px 8px; font-size: 12px; border: none; border-radius: 4px; cursor: pointer; background: #409eff; color: white;"
|
||||
>
|
||||
style="padding: 4px 8px; font-size: 12px; border: none; border-radius: 4px; cursor: pointer; background: #409eff; color: white;">
|
||||
{{ task.status === 'failed' ? '重新执行' : '执行任务' }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -211,7 +180,7 @@ export default {
|
||||
statusText: '未开始',
|
||||
statusColor: '#909399'
|
||||
},
|
||||
pdfDoc: null // 全局PDF实例,支持追加内容
|
||||
pdfDoc: null, // 全局PDF实例,支持追加内容
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -247,7 +216,6 @@ export default {
|
||||
},
|
||||
|
||||
// ========== 任务规划核心逻辑 ==========
|
||||
// 1. 获取总数据量,初始化任务规划
|
||||
async fetchTotalData() {
|
||||
try {
|
||||
const res = await listMaterialCoil({
|
||||
@@ -270,7 +238,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// 2. 初始化任务列表
|
||||
initTaskList() {
|
||||
this.taskList = [];
|
||||
for (let i = 0; i < this.taskPlan.totalTask; i++) {
|
||||
@@ -286,40 +253,30 @@ export default {
|
||||
statusColor: '#909399'
|
||||
});
|
||||
}
|
||||
// 重置任务统计
|
||||
this.taskPlan.completedTask = 0;
|
||||
this.taskPlan.runningTask = 0;
|
||||
this.taskPlan.totalProgress = 0;
|
||||
// 重置当前任务
|
||||
this.currentTask = { idx: 0, startNum: 0, endNum: 0, progress: 0, status: 'pending', statusText: '未开始', statusColor: '#909399' };
|
||||
// 重置PDF实例
|
||||
this.pdfDoc = null;
|
||||
},
|
||||
|
||||
// 3. 修改pageSize后重新规划任务
|
||||
replanTask() {
|
||||
// 先校验参数
|
||||
this.validatePageSize();
|
||||
// 重新计算总任务数
|
||||
this.taskPlan.totalTask = Math.ceil(this.taskPlan.totalCount / this.config.pageSize);
|
||||
// 重新初始化任务列表
|
||||
this.initTaskList();
|
||||
Message.success(`已重新规划任务!单次导出${this.config.pageSize}条,总计${this.taskPlan.totalTask}个任务`);
|
||||
},
|
||||
|
||||
// 4. 更新任务状态
|
||||
updateTaskStatus(taskIdx, status, progress = 0) {
|
||||
const task = this.taskList[taskIdx - 1];
|
||||
if (!task) return;
|
||||
|
||||
// 更新任务状态
|
||||
task.status = status;
|
||||
task.progress = progress;
|
||||
switch (status) {
|
||||
case 'running':
|
||||
task.statusText = '进行中';
|
||||
task.statusColor = '#409eff';
|
||||
// 更新当前任务
|
||||
this.currentTask = { ...task };
|
||||
break;
|
||||
case 'completed':
|
||||
@@ -335,16 +292,13 @@ export default {
|
||||
task.statusColor = '#909399';
|
||||
}
|
||||
|
||||
// 更新任务统计
|
||||
this.taskPlan.completedTask = this.taskList.filter(t => t.status === 'completed').length;
|
||||
this.taskPlan.runningTask = this.taskList.filter(t => t.status === 'running').length;
|
||||
// 计算整体进度
|
||||
const totalCompleted = this.taskList.reduce((sum, t) => sum + (t.progress / 100) * (t.endNum - t.startNum + 1), 0);
|
||||
this.taskPlan.totalProgress = Math.round((totalCompleted / this.taskPlan.totalCount) * 100);
|
||||
},
|
||||
|
||||
// ========== 核心导出逻辑 ==========
|
||||
// 1. 批量导出所有未完成任务
|
||||
async handleExportAll() {
|
||||
if (this.exportLoading || this.hasRunningTask) return;
|
||||
if (this.taskPlan.totalCount === 0) {
|
||||
@@ -354,10 +308,13 @@ export default {
|
||||
|
||||
try {
|
||||
this.exportLoading = true;
|
||||
// 初始化PDF文档(jsPDF默认创建1个空白页)
|
||||
this.pdfDoc = new jsPDF({ unit: 'mm', format: 'a4', orientation: 'portrait' });
|
||||
|
||||
// 筛选出未完成的任务(pending/failed)
|
||||
// 初始化PDF 动态尺寸【核心修改】
|
||||
this.pdfDoc = new jsPDF({
|
||||
unit: 'mm',
|
||||
orientation: 'portrait',
|
||||
format: [0, 0]
|
||||
});
|
||||
|
||||
const uncompletedTasks = this.taskList.filter(t => t.status !== 'completed');
|
||||
if (uncompletedTasks.length === 0) {
|
||||
Message.info('所有任务均已完成,无需导出');
|
||||
@@ -365,12 +322,10 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// 循环处理每个未完成任务
|
||||
for (const task of uncompletedTasks) {
|
||||
await this.runSingleTaskCore(task.idx);
|
||||
}
|
||||
|
||||
// 保存最终PDF
|
||||
const fileName = `外标签批量导出_${new Date().toLocaleString().replace(/[/: ]/g, '-')}.pdf`;
|
||||
this.pdfDoc.save(fileName);
|
||||
Message.success(`PDF批量导出成功!共完成${uncompletedTasks.length}个任务,总计${this.taskPlan.totalCount}条数据`);
|
||||
@@ -386,7 +341,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// 2. 单独执行单个任务
|
||||
async handleRunSingleTask(taskIdx) {
|
||||
if (this.exportLoading || this.hasRunningTask) {
|
||||
Message.warning('有任务正在执行中,请等待完成后再操作');
|
||||
@@ -395,18 +349,20 @@ export default {
|
||||
|
||||
try {
|
||||
this.exportLoading = true;
|
||||
// 初始化PDF文档(如果未初始化)
|
||||
// 初始化PDF 动态尺寸【核心修改】
|
||||
if (!this.pdfDoc) {
|
||||
this.pdfDoc = new jsPDF({ unit: 'mm', format: 'a4', orientation: 'portrait' });
|
||||
this.pdfDoc = new jsPDF({
|
||||
unit: 'mm',
|
||||
orientation: 'portrait',
|
||||
format: [0, 0]
|
||||
});
|
||||
}
|
||||
|
||||
// 执行单个任务核心逻辑
|
||||
await this.runSingleTaskCore(taskIdx);
|
||||
|
||||
// 保存单个任务的PDF
|
||||
const fileName = `外标签任务${taskIdx}_导出_${new Date().toLocaleString().replace(/[/: ]/g, '-')}.pdf`;
|
||||
this.pdfDoc.save(fileName);
|
||||
Message.success(`任务${taskIdx}导出成功!处理范围:${this.taskList[taskIdx-1].startNum}-${this.taskList[taskIdx-1].endNum}条`);
|
||||
Message.success(`任务${taskIdx}导出成功!处理范围:${this.taskList[taskIdx - 1].startNum}-${this.taskList[taskIdx - 1].endNum}条`);
|
||||
|
||||
} catch (err) {
|
||||
console.error(`任务${taskIdx}执行失败:`, err);
|
||||
@@ -420,12 +376,10 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// 3. 单个任务执行核心逻辑(修复空白页关键)
|
||||
// 单个任务执行核心逻辑【修复后:彻底解决多页尺寸异常】
|
||||
async runSingleTaskCore(taskIdx) {
|
||||
// 更新当前任务为“进行中”
|
||||
this.updateTaskStatus(taskIdx, 'running', 0);
|
||||
|
||||
// 获取当前任务的批次数据
|
||||
|
||||
const pageNum = taskIdx;
|
||||
const res = await listMaterialCoil({
|
||||
status: 0,
|
||||
@@ -444,7 +398,6 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理当前批次的每个标签
|
||||
this.showProgress = true;
|
||||
this.currentIndex = 0;
|
||||
this.progress = 0;
|
||||
@@ -452,8 +405,7 @@ export default {
|
||||
for (let index = 0; index < this.list.length; index++) {
|
||||
this.current = this.list[index];
|
||||
await this.waitForDOMRender();
|
||||
|
||||
// 等待资源加载(图片+字体+二维码)
|
||||
|
||||
const previewComponent = this.$refs.outerTagPreview;
|
||||
await this.waitForAllResources(previewComponent.$el);
|
||||
|
||||
@@ -463,11 +415,20 @@ export default {
|
||||
tempContainer.innerHTML = '';
|
||||
tempContainer.appendChild(renderedDOM);
|
||||
|
||||
// 修复克隆DOM丢失样式问题
|
||||
const labelDom = tempContainer.querySelector('.label-container');
|
||||
if (labelDom) {
|
||||
labelDom.style.width = '100%';
|
||||
labelDom.style.maxWidth = '100%';
|
||||
labelDom.style.boxSizing = 'border-box';
|
||||
labelDom.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
await this.waitForDOMRender();
|
||||
this.copyCanvasContent(previewComponent.$el, tempContainer);
|
||||
|
||||
if (!tempContainer.firstChild) {
|
||||
console.warn(`任务${taskIdx}第${index+1}个标签DOM为空`, this.list[index]);
|
||||
console.warn(`任务${taskIdx}第${index + 1}个标签DOM为空`, this.list[index]);
|
||||
this.updateProgress(index + 1);
|
||||
continue;
|
||||
}
|
||||
@@ -484,74 +445,100 @@ export default {
|
||||
logging: false,
|
||||
allowTaint: true,
|
||||
taintTest: false,
|
||||
scrollX: 0,
|
||||
scrollY: 0,
|
||||
letterRendering: true,
|
||||
imageTimeout: 10000
|
||||
});
|
||||
|
||||
// ========== 修复空白页核心逻辑 ==========
|
||||
// 关键规则:
|
||||
// 1. jsPDF实例化时默认创建1个空白页
|
||||
// 2. 第一个任务的第一个标签:直接使用默认空白页,不新增
|
||||
// 3. 第一个任务的非第一个标签:新增页面
|
||||
// 4. 非第一个任务的所有标签:新增页面
|
||||
const needAddPage = index > 0 || taskIdx > 1;
|
||||
if (needAddPage) {
|
||||
this.pdfDoc.addPage();
|
||||
// 裁剪画布极细空白
|
||||
const cropCanvas = this.cropCanvasWhiteSpace(canvas);
|
||||
|
||||
// 计算当前标签的实际尺寸(每次都重新计算,避免缓存错误)
|
||||
const canvasWidth = cropCanvas.width / this.config.renderScale;
|
||||
const canvasHeight = cropCanvas.height / this.config.renderScale;
|
||||
const imgWidthMM = canvasWidth * (25.4 / 96);
|
||||
const imgHeightMM = canvasHeight * (25.4 / 96);
|
||||
const currentPageFormat = [imgWidthMM, imgHeightMM];
|
||||
|
||||
// ========== 修复核心:正确设置PDF页面尺寸 ==========
|
||||
const totalPdfPages = this.pdfDoc.internal.getNumberOfPages();
|
||||
// 情况1:PDF是“初始空白页”(仅1页且尺寸为0)→ 修改初始页尺寸
|
||||
if (totalPdfPages === 1 && this.pdfDoc.internal.pageSize.getWidth() === 0) {
|
||||
this.pdfDoc.setPage(1);
|
||||
this.pdfDoc.internal.pageSize.setWidth(imgWidthMM);
|
||||
this.pdfDoc.internal.pageSize.setHeight(imgHeightMM);
|
||||
}
|
||||
// 情况2:其他所有情况→ 新增页面并使用当前标签的尺寸
|
||||
else {
|
||||
this.pdfDoc.addPage(currentPageFormat, 'portrait');
|
||||
}
|
||||
|
||||
// 尺寸计算
|
||||
const domWidth = tempContainer.offsetWidth;
|
||||
const domHeight = tempContainer.offsetHeight;
|
||||
const domWidthMM = domWidth * (25.4 / 96);
|
||||
const domHeightMM = domHeight * (25.4 / 96);
|
||||
// 图片0边距填充当前页面
|
||||
const currentPageNum = this.pdfDoc.internal.getNumberOfPages();
|
||||
this.pdfDoc.setPage(currentPageNum); // 切换到当前页面
|
||||
this.pdfDoc.internal.pageSize.setWidth(imgWidthMM);
|
||||
this.pdfDoc.internal.pageSize.setHeight(imgHeightMM);
|
||||
this.pdfDoc.addImage(cropCanvas.toDataURL('image/png', 1.0), 'PNG', 0, 0, imgWidthMM, imgHeightMM);
|
||||
|
||||
const a4Width = 210;
|
||||
const a4Height = 297;
|
||||
const margin = 5;
|
||||
const availableWidth = a4Width - 2 * margin;
|
||||
const availableHeight = a4Height - 2 * margin;
|
||||
|
||||
const scaleRatio = Math.min(availableWidth / domWidthMM, availableHeight / domHeightMM);
|
||||
const finalWidth = domWidthMM * scaleRatio;
|
||||
const finalHeight = domHeightMM * scaleRatio;
|
||||
|
||||
const x = (a4Width - finalWidth) / 2;
|
||||
const y = (a4Height - finalHeight) / 2;
|
||||
|
||||
// 插入图片到PDF
|
||||
this.pdfDoc.addImage(canvas.toDataURL('image/png', 1.0), 'PNG', x, y, finalWidth, finalHeight);
|
||||
|
||||
// 更新单批进度 + 任务进度
|
||||
// 更新进度
|
||||
this.updateProgress(index + 1);
|
||||
this.updateTaskStatus(taskIdx, 'running', this.progress);
|
||||
}
|
||||
|
||||
// 当前任务完成
|
||||
this.updateTaskStatus(taskIdx, 'completed', 100);
|
||||
},
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
// 等待二维码/图片/字体加载
|
||||
// ========== 新增+保留 所有辅助方法 ==========
|
||||
cropCanvasWhiteSpace(canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
const imageData = ctx.getImageData(0, 0, width, height);
|
||||
const data = imageData.data;
|
||||
|
||||
let left = width, top = height, right = 0, bottom = 0;
|
||||
let pixelIndex, alpha;
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
pixelIndex = (y * width + x) * 4;
|
||||
alpha = data[pixelIndex + 3];
|
||||
if (alpha > 0) {
|
||||
left = Math.min(left, x);
|
||||
top = Math.min(top, y);
|
||||
right = Math.max(right, x);
|
||||
bottom = Math.max(bottom, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cropWidth = right - left + 1;
|
||||
const cropHeight = bottom - top + 1;
|
||||
if (cropWidth <= 0 || cropHeight <= 0) return canvas;
|
||||
|
||||
const newCanvas = document.createElement('canvas');
|
||||
newCanvas.width = cropWidth;
|
||||
newCanvas.height = cropHeight;
|
||||
const newCtx = newCanvas.getContext('2d');
|
||||
newCtx.drawImage(canvas, left, top, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
||||
return newCanvas;
|
||||
},
|
||||
|
||||
async waitForAllResources(element) {
|
||||
// 等待图片加载
|
||||
const images = element.querySelectorAll('img');
|
||||
const imgPromises = Array.from(images).map(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));
|
||||
},
|
||||
|
||||
// 等待二维码渲染
|
||||
async waitForQRCodeRender(element) {
|
||||
const qrCanvasList = element.querySelectorAll('canvas');
|
||||
if (qrCanvasList.length === 0) return;
|
||||
@@ -577,11 +564,9 @@ export default {
|
||||
await Promise.all(qrLoadPromises);
|
||||
},
|
||||
|
||||
// 复制Canvas内容
|
||||
copyCanvasContent(sourceEl, targetEl) {
|
||||
const sourceCanvases = sourceEl.querySelectorAll('canvas');
|
||||
const targetCanvases = targetEl.querySelectorAll('canvas');
|
||||
|
||||
sourceCanvases.forEach((canvas, index) => {
|
||||
const targetCanvas = targetCanvases[index];
|
||||
if (!targetCanvas) return;
|
||||
@@ -592,13 +577,11 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 更新单批进度
|
||||
updateProgress(current) {
|
||||
this.currentIndex = current;
|
||||
this.progress = Math.round((current / this.totalCount) * 100);
|
||||
},
|
||||
|
||||
// 等待DOM渲染
|
||||
waitForDOMRender(time = 300) {
|
||||
return new Promise(resolve => setTimeout(resolve, time));
|
||||
}
|
||||
@@ -622,7 +605,6 @@ export default {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 配置区域样式 */
|
||||
.config-container {
|
||||
input {
|
||||
padding: 6px 12px;
|
||||
@@ -630,12 +612,12 @@ export default {
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
/* 按钮样式优化 */
|
||||
button {
|
||||
border: 1px solid #409eff;
|
||||
background: #409eff;
|
||||
@@ -643,6 +625,7 @@ button {
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #a0cfff;
|
||||
cursor: not-allowed;
|
||||
|
||||
@@ -156,22 +156,22 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="毛重(t)" required>
|
||||
<el-input v-model="item.grossWeight" placeholder="请输入毛重" type="number" step="0.01"
|
||||
<el-input-number :controls="false" v-model="item.grossWeight" placeholder="请输入毛重" type="number" step="0.01"
|
||||
:disabled="readonly">
|
||||
<template slot="append">吨</template>
|
||||
</el-input>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="净重(t)" required>
|
||||
<el-input v-model="item.netWeight" placeholder="请输入净重" type="number" step="0.01"
|
||||
<el-input-number :controls="false" v-model="item.netWeight" placeholder="请输入净重" type="number" step="0.01"
|
||||
:disabled="readonly">
|
||||
<template slot="append">吨</template>
|
||||
</el-input>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="长度(m)" required>
|
||||
<el-input v-model="item.length" placeholder="请输入长度" type="number" step="0.01"
|
||||
<el-input-number :controls="false" v-model="item.length" placeholder="请输入长度" type="number" step="0.01"
|
||||
:disabled="readonly">
|
||||
<template slot="append">米</template>
|
||||
</el-input>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="逻辑库区" required>
|
||||
<WarehouseSelect
|
||||
|
||||
@@ -150,24 +150,23 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="毛重(t)" prop="grossWeight">
|
||||
<el-input v-model="updateForm.grossWeight" placeholder="请输入毛重" type="number" step="0.01"
|
||||
<el-input-number :controls="false" v-model="updateForm.grossWeight" placeholder="请输入毛重" type="number" step="0.01"
|
||||
:disabled="readonly">
|
||||
<template slot="append">吨</template>
|
||||
</el-input>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="净重(t)" prop="netWeight">
|
||||
<el-input v-model="updateForm.netWeight" placeholder="请输入净重" type="number" step="0.01"
|
||||
<el-input-number :controls="false" v-model="updateForm.netWeight" placeholder="请输入净重" type="number" step="0.01"
|
||||
:disabled="readonly">
|
||||
<template slot="append">吨</template>
|
||||
</el-input>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="长度(m)" prop="length">
|
||||
<el-input v-model="updateForm.length" placeholder="请输入长度" type="number" step="0.01"
|
||||
<el-input-number :controls="false" v-model="updateForm.length" placeholder="请输入长度" type="number" step="0.01"
|
||||
:disabled="readonly">
|
||||
<template slot="append">米</template>
|
||||
</el-input>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="逻辑库区" prop="warehouseId">
|
||||
|
||||
Reference in New Issue
Block a user