Files
klp-oa/klp-ui/src/views/wms/coil/panels/tool.vue
砂糖 5b2f7683fb feat(wms/coil): 添加外标签批量导出功能并优化日期显示
- 在base.vue中设置dataType默认值为1
- 在OuterTagPreview.vue中优化生产日期显示格式
- 新增tool.vue组件实现批量导出外标签为PDF功能,包含进度条显示
2026-01-08 10:02:59 +08:00

236 lines
7.2 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>
<button
@click="handleExportAll"
:disabled="exportLoading"
style="padding: 8px 16px; cursor: pointer; margin-bottom: 20px;"
>
{{ exportLoading ? '导出中...' : '导出所有外标签' }}
</button>
<!-- 进度条容器导出时显示 -->
<div v-if="showProgress" class="progress-container" style="margin-bottom: 20px; width: 100%; max-width: 600px;">
<!-- 进度文本显示当前进度/总数量 -->
<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>
</div>
<!-- 预览组件保留预览同时用于复用渲染逻辑 -->
<OuterTagPreview ref="outerTagPreview" :content="current" />
<!-- 临时渲染容器用于逐个渲染标签并转换图片 -->
<div ref="tempTagContainer" style="position: fixed; top: -9999px; left: -9999px; z-index: -9999;"></div>
</div>
</template>
<script>
import OuterTagPreview from './LabelRender/OuterTagPreview.vue';
import { listMaterialCoil } from "@/api/wms/coil";
import jsPDF from 'jspdf';
import domtoimage from 'dom-to-image';
export default {
name: 'ToolPanel',
components: {
OuterTagPreview,
},
data() {
return {
list: [],
pos: 0,
current: {},
exportLoading: false,
// 进度条相关变量
showProgress: false, // 是否显示进度条
progress: 0, // 进度值0-100
currentIndex: 0, // 当前处理的标签索引
totalCount: 0, // 总标签数量
}
},
mounted() {
this.fetchData();
},
methods: {
// 获取标签数据
fetchData() {
listMaterialCoil({
status: 0,
dataType: 1,
materialType: '成品',
itemType: 'product',
selectType: 'raw_material',
pageSize: 100,
pageNum: 10
}).then(res => {
this.list = res.rows || [];
if (this.list.length > 0) {
this.current = this.list[0];
}
}).catch(err => {
console.error('获取标签数据失败:', err);
this.$message.error('获取标签数据失败,请刷新重试');
})
},
// 核心复用OuterTagPreview组件渲染标签再转图片导出PDF
async handleExportAll() {
if (this.exportLoading) return;
if (this.list.length === 0) {
this.$message.warning('暂无外标签数据可导出');
return;
}
try {
// 初始化进度条
this.exportLoading = true;
this.showProgress = true; // 显示进度条
this.totalCount = this.list.length; // 总数量
this.currentIndex = 0; // 重置当前索引
this.progress = 0; // 重置进度
const doc = new jsPDF({
unit: 'mm',
format: 'a4',
orientation: 'portrait'
});
const tempContainer = this.$refs.tempTagContainer;
const previewComponent = this.$refs.outerTagPreview;
// 循环处理每个标签
for (let index = 0; index < this.list.length; index++) {
const item = this.list[index];
this.current = item; // 更新预览组件的content触发重新渲染
// 等待组件渲染完成
await this.waitForDOMRender();
// 复制预览组件的DOM到临时容器
const renderedDOM = previewComponent.$el.cloneNode(true);
tempContainer.innerHTML = '';
tempContainer.appendChild(renderedDOM);
// 再次等待临时容器DOM渲染
await this.waitForDOMRender();
// 检查临时容器是否有内容
if (!tempContainer.firstChild) {
console.warn(`${index+1}个标签DOM为空`, item);
// 即使当前标签为空,也更新进度
this.updateProgress(index + 1);
continue;
}
// 将临时容器的DOM转为图片
const imgData = await domtoimage.toPng(previewComponent.$el);
// 分页:非第一个标签新增页面
if (index > 0) {
doc.addPage();
}
// 计算图片在PDF中的尺寸适配A4保持原比例
const a4Width = 210;
const a4Height = 297;
const imgRatio = (previewComponent.$el.offsetWidth || 800) / (previewComponent.$el.offsetHeight || 1100);
const imgWidth = a4Width - 20;
const imgHeight = imgWidth / imgRatio;
// 确保图片高度不超过A4高度
const finalImgHeight = imgHeight > a4Height - 20 ? (a4Height - 20) : imgHeight;
const finalImgWidth = finalImgHeight * imgRatio;
// 居中显示
const xPos = (a4Width - finalImgWidth) / 2;
const yPos = (a4Height - finalImgHeight) / 2;
// 插入图片到PDF
doc.addImage(imgData, 'PNG', xPos, yPos, finalImgWidth, finalImgHeight);
// 更新进度条(关键:处理完一个标签就更新)
this.updateProgress(index + 1);
}
// 保存PDF
const fileName = `外标签导出_${new Date().toLocaleString().replace(/[/: ]/g, '-')}.pdf`;
doc.save(fileName);
this.$message.success('PDF导出成功');
} catch (err) {
console.error('PDF导出失败详情', err);
this.$message.error('PDF导出失败详情请查看控制台');
} finally {
// 重置进度条和加载状态
this.$refs.tempTagContainer.innerHTML = '';
this.exportLoading = false;
this.showProgress = false; // 隐藏进度条
this.progress = 0;
this.currentIndex = 0;
this.totalCount = 0;
}
},
// 辅助方法:更新进度条
updateProgress(current) {
this.currentIndex = current;
// 计算进度百分比(保留整数)
this.progress = Math.round((current / this.totalCount) * 100);
},
// 辅助方法等待DOM渲染完成
waitForDOMRender(time = 300) {
return new Promise(resolve => setTimeout(resolve, time));
}
}
}
</script>
<style scoped>
button {
border: 1px solid #409eff;
background: #409eff;
color: white;
border-radius: 4px;
transition: all 0.3s;
}
button:disabled {
background: #a0cfff;
cursor: not-allowed;
}
/* 进度条样式优化(可选:替换内联样式,统一管理) */
.progress-container {
:deep(.progress-text) {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
:deep(.progress-bar-bg) {
width: 100%;
height: 8px;
background: #f5f5f5;
border-radius: 4px;
overflow: hidden;
}
:deep(.progress-bar) {
height: 100%;
background: #409eff;
transition: width 0.3s ease;
border-radius: 4px;
}
}
/* 确保预览组件的DOM能被正确复制 */
:deep(.label-container) {
width: 100%;
height: auto;
box-sizing: border-box;
}
</style>