Vue 项目里“导出 PDF”几乎是所有后台管理系统的标配功能:订单详情、财务报表、合同文本、数据报表都要提供导出按钮。传统方案要么走后端渲染,接口慢、服务器压力大,高峰期还容易超时;要么用 jsPDF 手绘坐标,开发成本高、样式还原差,改一次版式就要重写一遍代码。dompdf.js 可以直接渲染 Vue 页面上的真实 DOM,生成的 PDF 与页面版式一致,且支持中文、表格、分页,无需任何后端参与。本文介绍如何在 Vue 2 和 Vue 3 项目中集成 dompdf.js:从安装、组件内调用,到封装可复用的导出工具函数,都给出完整代码示例和注意事项,帮助你一次接入、全项目复用,把导出能力沉淀成团队的基础设施。
后台管理系统里,订单详情、财务报表、合同文本都需要导出 PDF;运营和客户拿到的是格式统一的正式文档,而不是网页截图或打印预览,专业度完全不同,这也是很多企业对导出功能的基本要求,几乎每个管理系统都逃不掉这个需求,早做早受益。
前后端分离架构下,导出需求如果都走后端,需要额外维护模板引擎和渲染服务,接口响应慢、服务器并发压力大,遇到导出高峰还容易超时,运维团队也很头疼;纯前端导出即时完成,不占用任何服务端资源,运维成本几乎为零,高峰期也不怕被打爆。
页面数据本身就是响应式的,前端直接取当前 DOM 或数据渲染 PDF,能保证“页面看到什么、导出就是什么”,这也是用户对导出功能最直接的体验预期;避免了前后端数据不一致的问题,所见即所得让验收也变得简单,需求沟通成本显著下降。
在项目根目录执行 npm install dompdf.js 即可完成安装;包同时支持 import 方式引入,配合 Vite、Webpack 等打包工具可以正常按需打包,不污染全局,构建产物也足够精简,升级维护都很省心,安装这一步一分钟内就能完成,安装命令、引入方式、调用三步在文档里都有完整示例,团队里任何人照文档操作都不会出错,接入门槛降到最低。
引入方式:import { DomPDF } from 'dompdf.js',之后在组件里创建实例、添加页面、保存文件,三步完成一次导出,与框架本身完全解耦,Vue 2 和 Vue 3 的用法没有任何区别,团队里任何水平的同事都能快速上手,培训成本很低,示例代码直接复制即可运行,不需要理解底层实现细节,把精力留给业务逻辑,学习曲线非常平缓。
低代码平台或纯 CDN 场景也可以直接用 script 标签引入 dist 下的构建文件,在全局变量上调用;两种方式 API 完全一致,切换成本极低,接入方式可以根据项目架构自由选择,不必为集成方式纠结,先跑通再优化。
用 ref 拿到要导出的 DOM 区域,直接传给 addPage;模板字符串或 DOM 节点都可以作为输入,前者适合纯数据生成,后者实现所见即所得,两种方式可以按场景选择,也可以混合使用,灵活性很高,代码量却只有几行。
导出按钮绑定点击事件,生成 PDF 后 save 直接触发浏览器下载;数据量大的场景建议给按钮加 loading 状态并禁用,避免用户重复点击产生多份文件,同时给用户明确的等待反馈,交互细节决定专业度,别忘了处理异常分支,导出失败时给出友好提示并支持重试,避免静默失败,用户反馈也更少。
Vue 2 的 Options API 用法类似:this.$refs.content 拿到节点,逻辑写在 methods 里;代码结构稍有差异,但调用方式完全一致,从 Vue 2 迁移到 Vue 3 时导出逻辑基本可以原样复用,迁移成本很低,老项目也能放心接入,升级 Vue 版本时导出模块原样保留,不需要重写,投资可以长期复用,团队技术演进更从容。
<script setup>
import { ref } from 'vue';
import { DomPDF } from 'dompdf.js';
const contentRef = ref(null);
async function exportPdf() {
const pdf = new DomPDF();
pdf.addPage(contentRef.value, { format: 'A4', margin: '15mm' });
pdf.save('report.pdf');
}
</script>
<template>
<div>
<button @click="exportPdf">导出 PDF</button>
<div ref="contentRef">
<h1>月度经营报告</h1>
<table>
<tr><th>指标</th><th>本月</th><th>环比</th></tr>
<tr><td>营收</td><td>120 万</td><td>+8%</td></tr>
</table>
</div>
</div>
</template>
多个页面都需要导出时,建议把导出逻辑抽成 composable(如 usePdfExport),统一处理实例创建、默认配置和下载命名,避免每个组件重复写一遍样板代码,也让团队的导出行为保持一致,代码评审更轻松,返工更少。
工具函数接收三个参数:内容源(HTML 字符串或 DOM 节点)、页面配置(纸张、边距)、文件名,返回 Promise,调用方只关心传参,使用体验干净,测试时也可以直接对工具函数做单元测试,质量有保障,回归风险可控,导出相关的改动都集中在同一个模块,排查问题、做回归都更快,长期维护心里有底。
抽成模块后测试和升级都更方便:以后要加页眉页脚、自定义字体或进度提示,只需要在工具函数里改一处,所有页面自动生效,不需要逐个组件修改,维护成本大幅降低,团队迭代速度明显提升,基础设施越做越厚。
// composables/usePdfExport.js
import { DomPDF } from 'dompdf.js';
export function usePdfExport() {
async function exportPdf(source, options = {}, filename = 'export.pdf') {
const pdf = new DomPDF();
pdf.addPage(source, { format: 'A4', margin: '15mm', ...options });
pdf.save(filename);
}
return { exportPdf };
}
// 组件中使用
// const { exportPdf } = usePdfExport();
// exportPdf(contentRef.value, { margin: '10mm' }, 'orders.pdf');
Vue 的 scoped 样式会给元素加上 data 属性选择器,DOM 快照渲染时这些样式依然生效,一般无需特殊处理;如果发现样式丢失,优先检查是否依赖了组件作用域之外的全局样式,必要时把关键样式内联,问题基本都能解决,排查方向要正确,scoped 样式对快照渲染的影响很小,绝大多数情况无需特殊处理,按文档排查即可。
分页效果与容器宽度强相关:A4 纸在 96 DPI 下对应 794px,建议把导出区域宽度与目标纸张对应,分页结果最稳定,跨页断点也可预期,长文档不会出现内容被裁切的情况,打印出来也整齐,交付前用真实数据验证一遍。
导出区域可能被弹窗、导航等页面元素影响,建议把要导出的内容放在独立容器或专门的打印区域里,保证版式纯净,导出结果与设计一致;如果页面存在隐藏元素,导出前确认其不影响快照,避免无关内容混入 PDF,文档内容干净。
Q: SSR 或服务端渲染环境下能用吗?A: dompdf.js 依赖浏览器 DOM,SSR 下只能在客户端调用,比如 onMounted 之后或事件回调里,服务端渲染阶段直接跳过,避免报错,这是最常见的集成坑,务必注意,文档里也写清楚了边界,服务端渲染阶段调用会直接报错,提前在代码里做环境判断最稳妥,上线前用构建产物验证一遍。
Q: 中文字体会乱码吗?A: 不会,内置思源黑体,Vue 项目无需额外配置即可输出规范中文;公司有指定字体时可以通过字体配置显式嵌入,品牌字体也能完整还原,输出效果与网页一致,正式文档放心使用,无需担心字库缺失或版权问题,中英文混排的报表、合同都能正确呈现,交付质量稳定。
Q: 大型表格导出卡顿怎么办?A: 开启分页、容器宽度匹配纸张、避免一次性渲染超大 DOM;数据特别多时还可以分批 addPage,逐批生成保证流畅,同时配合进度提示提升体验,用户等待时也不会焦虑,长报表场景实测效果不错,上万行的明细也能稳定导出,分页和进度提示配合到位,放心使用。
下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:
这是由 dompdf.js 渲染的示例 PDF 内容。