DomPDF导出
项目使用传入dom结构导出文件为PDF格式
demo查看
使用技术栈版本
vue@3
dompdf.js@1.3
vxetable@4.19
中文默认乱码 需要加载字体
html
<template>
<div>
<button @click="handlerPrint">导出</button>
<!-- 动态绑定pdf-mode,仅导出时添加 -->
<div
ref="printDom"
:class="['print-container', printFlag ? 'pdf-mode' : '']"
>
<div class="header-info">
<div class="info-row" v-for="(item, index) in list" :key="index">
<div class="info-title">{{ item.title }}</div>
<div class="info-item">{{ index }}</div>
</div>
</div>
<!-- [table] 不支持的参数 "show-overflow=tooltip",可能为 "show-overflow=title" -->
<div style="width: 100%; height: 800px">
<vxe-grid
class="h5-vxe-grid"
v-if="gridColumns.length"
size="mini"
ref="xTable"
border="none"
align="center"
stripe
resizable
auto-resize
height="auto"
show-overflow="title"
:show-header-overflow="false"
:scroll-y="printFlag ? { enabled: false } : { enabled: true }"
:columns="gridColumns"
:data="dataSource"
:span-method="colspanMethod"
:cell-style="getCellStyle"
>
<template v-slot:evalResult="{ row }">
<span class="text-result"> {{ row.evalResult }} </span>
</template>
</vxe-grid>
</div>
</div>
</div>
</template>
<script>
import dompdf from 'dompdf.js';
import '@assets/font/domPdf/SourceHanSansSC-Normal-Min-normal.js';
export default {
name: 'tableLog',
props: {},
data() {
return {
printFlag: false, // 新增打印标识
domHeight: 400,
otherHeight: 80,
column: 2,
list: [
{
title: 'label1',
},
{
title: 'label1',
},
{
title: 'label1',
},
{
title: 'label1',
},
],
gridColumns: [
{
title: '名称',
field: 'mainItem',
// width: 160,
// 合并单元格渲染主分类
// rowspanMethod: ({ row }) => row.rowSpan
},
{
title: '数据',
field: 'subItem',
width: 160,
},
{
title: '值',
field: 'limitVal',
// width: 140
},
{
title: '合格率',
field: 'passRate',
width: 80,
},
{
title: '结论',
field: 'evalResult',
slots: {
default: 'evalResult',
},
},
{
title: '数据分析',
field: 'dataAnalyse',
//slots: {
// default: 'action',
// },
},
],
dataSource: [
{
mainItem: 'label1',
rowSpan: 1,
subItem: '------',
limitVal: '-0.2Hz~0.2Hz',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
// 不平衡
{
mainItem: 'label2',
rowSpan: 2,
subItem: '电流',
limitVal: '-2%~2%',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label2',
rowSpan: 0,
subItem: '电压',
limitVal: '<2%',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label3',
rowSpan: 3,
subItem: 'A相',
limitVal: '-7%~7%',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label3',
rowSpan: 0,
subItem: 'B相',
limitVal: '-7%~7%',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label3',
rowSpan: 0,
subItem: 'C相',
limitVal: '-7%~7%',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label4',
rowSpan: 1,
subItem: '3',
limitVal: '0.9~1.0',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label5',
rowSpan: 12,
subItem: 'A',
limitVal: '173.1',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label5',
rowSpan: 0,
subItem: 'A',
limitVal: '173.1',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label5',
rowSpan: 0,
subItem: 'A',
limitVal: '173.1',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label5',
rowSpan: 0,
subItem: '1',
limitVal: '173.1',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label5',
rowSpan: 0,
subItem: '3',
limitVal: '173.1',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label5',
rowSpan: 0,
subItem: '5',
limitVal: '173.1',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '---',
hasLookBtn: false,
},
{
mainItem: 'label5',
rowSpan: 0,
subItem: '7',
limitVal: '173.1',
passRate: '98.2%',
evalResult: '合格',
dataAnalyse: '',
hasLookBtn: true,
},
{
mainItem: 'label5',
rowSpan: 0,
subItem: '9',
limitVal: '173.1',
passRate: '78.2%',
evalResult: '不合格',
dataAnalyse: '',
hasLookBtn: true,
},
],
};
},
methods: {
async handlerPrint() {
const originDom = this.$refs.printDom;
// 1. 深度克隆整个打印区域DOM(包含所有子元素、表格、插槽)
const cloneDom = originDom.cloneNode(true);
// 2. 给克隆副本添加打印专属class,只修改副本,原页面不动
cloneDom.classList.add('pdf-mode');
// 3. 创建临时隐藏容器,把克隆体丢进去,脱离页面正常流
const tempWrap = document.createElement('div');
tempWrap.style.position = 'absolute';
tempWrap.style.left = '-9999px'; // 移出可视区域,看不见
tempWrap.style.width = '210mm';
tempWrap.appendChild(cloneDom);
document.body.appendChild(tempWrap);
try {
// 等待克隆DOM样式、表格渲染完成
await new Promise((resolve) => setTimeout(resolve, 150));
// 直接传入克隆后的dom导出PDF
const blob = await dompdf(cloneDom, {
pagination: true,
format: 'a4',
scale: 1.1,
pageConfig: {
header: { content: '评估报告', height: 15 },
footer: {
content: '第 ${currentPage} 页',
height: 15,
contentPosition: 'center',
},
},
fontConfig: {
fontFamily: 'SourceHanSansSC-Normal-Min',
fontBase64: window.fontBase64,
fontStyle: 'normal',
fontWeight: 400,
},
});
// 下载逻辑不变
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '评估报告.pdf';
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
} catch (err) {
console.error('导出失败', err);
} finally {
// 导出完成销毁临时DOM,释放内存
document.body.removeChild(tempWrap);
}
},
async handlerPrint2() {
const element = this.$refs.printDom;
// 开启打印模式,加载pdf专属样式
this.printFlag = true;
// 等待dom更新完成再导出
await this.$nextTick();
dompdf(element, {
pagination: true,
format: 'a4',
scale: 1.2, // 缩小缩放,避免内容横向溢出
pageConfig: {
header: { content: '评估报告', height: 20 },
footer: {
content: '第 ${currentPage} 页',
height: 20,
contentPosition: 'center',
},
},
fontConfig: {
fontFamily: 'SourceHanSansSC-Normal-Min',
fontBase64: window.fontBase64,
fontStyle: 'normal',
fontWeight: 400,
},
}).then((blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '评估报告.pdf';
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
// 打印完成关闭打印模式,恢复页面原有样式
this.printFlag = false;
}, 100);
});
},
getCellStyle({ row, rowIndex, column, columnIndex }) {
if (columnIndex === 0) {
return {
background: 'rgba(201, 236, 255, 0.45)',
};
}
},
handleLookDetail(row) {
console.log('查看分项详情', row);
// 可弹窗/跳转查看对应谐波、暂态事件详情
},
getRef() {
return this.$refs.xTable;
},
colspanMethod({ row, $rowIndex, column, data }) {
let fields = ['mainItem']; // 需要合并的列
if (fields.length === 0) {
return;
}
let cellValue = row[column.property];
if (cellValue && fields.includes(column.property)) {
let prevRow = data[$rowIndex - 1];
let nextRow = data[$rowIndex + 1];
if (prevRow && prevRow[column.property] === cellValue) {
return { rowspan: 0, colspan: 0 };
} else {
let countRowspan = 1;
while (nextRow && nextRow[column.property] === cellValue) {
nextRow = data[++countRowspan + $rowIndex];
}
if (countRowspan > 1) {
return { rowspan: countRowspan, colspan: 1 };
}
}
}
},
},
};
</script>
<style scoped lang="less">
.header-info {
display: flex;
gap: 10px;
box-sizing: border-box;
padding: 10px 0;
margin-bottom: 8px;
.info-row {
flex: 1;
background: linear-gradient(180deg, #ffffff 0%, #ebebeb 98.57%);
border: 0.5px solid #9f9e9e;
border-radius: 4px;
padding: 10px 25px;
text-align: center;
.info-title {
font-family: PingFang SC;
color: #333333;
font-size: 12px;
padding-bottom: 5px;
margin-bottom: 5px;
border-bottom: 0.5px solid #dddddd;
}
.info-item {
font-family: PingFang SC;
font-weight: 600;
color: #0093ff;
font-size: 14px;
}
}
.info-row:nth-child(2) .info-item {
color: #333333;
}
.info-row:nth-child(4) .info-item {
width: 60px;
text-align: center;
color: #fc5454;
border: 1px solid currentColor;
margin: 0 auto;
}
}
.h5-vxe-grid.vxe-grid {
:deep(.vxe-header--row) {
background: #4e82bb;
font-family: SF Pro Display;
font-weight: 500;
color: #ffffff;
font-size: 14px;
.vxe-resizable {
opacity: 0;
}
.vxe-body--row.row--stripe {
background-color: #f4f4f4;
}
/* 页面预览:文字超出省略 */
.vxe-cell {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.text-result {
display: inline-block;
width: 60px;
box-sizing: border-box;
border: 1px solid;
}
.text-red {
color: #23d09a;
border-color: currentColor;
font-weight: bold;
}
.text-blue {
color: #fc5454;
border-color: currentColor;
font-weight: bold;
}
.look-btn {
cursor: pointer;
}
/* 页面预览模式,不限制宽度、不强制换行 */
.print-container {
width: 100%;
background: transparent;
padding: 0;
}
</style>
<style lang="less">
/* ========== PDF导出专属样式,页面正常浏览完全不生效 ========== */
.print-container.pdf-mode {
min-height: auto !important; /* 删掉固定297mm,不要强制A4高度 */
height: auto !important;
margin: 0 auto !important;
margin-bottom: 0 !important;
padding: 0 10mm !important;
background: #fff !important;
box-sizing: border-box !important;
max-width: 100% !important;
overflow: visible !important;
/* 清除底部多余留白 */
margin-block-end: 0 !important;
}
/* PDF全局强制换行,解决文字截断 */
.print-container.pdf-mode * {
word-break: break-all !important;
overflow-wrap: break-word !important;
white-space: normal !important;
}
/* PDF表格自适应,铺满宽度,取消滚动 */
.print-container.pdf-mode table,
.print-container.pdf-mode .vxe-table {
table-layout: fixed !important;
width: 100% !important;
}
.print-container.pdf-mode .vxe-table .vxe-cell {
white-space: normal !important;
overflow: visible !important;
text-overflow: unset !important;
}
/* PDF模式取消表格固定高度,自动撑开,解决底部空白 */
.print-container.pdf-mode [style*='height'] {
height: auto !important;
}
/* 打印隐藏操作按钮 */
.print-container.pdf-mode .look-btn {
display: none !important;
}
/* 打印背景色渲染 */
@media print {
body {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
}
</style>