← dompdf.js Studio

报价单PDF前端生成完整指南

报价单是销售流程中最常见的商务文档,客户询价后需要一份清晰、规范、可存档的PDF报价单。传统做法是把报价数据提交到后端,用服务端模板引擎渲染PDF,开发成本高、响应慢,遇到高并发还会排队。dompdf.js是一款纯前端的DOM-to-PDF库,基于Rust+WASM内核,直接在浏览器里把HTML渲染成矢量PDF,无需任何后端服务。本文会从实际业务出发,讲解如何用dompdf.js在浏览器端快速生成专业报价单,包括表格设计、中文排版、自动分页和页眉页脚等完整方案,并附上可直接运行的代码示例。除了具体的代码,本文还会分享模板如何保持可维护、渲染前如何校验数据,以及如何避开表格错位、边框丢失这些经典陷阱。无论你是在给SaaS产品做报价模块、给CRM加导出功能,还是单纯想摆脱打印网页的尴尬,这些方法都能帮你少走弯路。读完本文,你会得到一套可以直接上手的思路:模板只设计一次,用数据驱动,两次API调用完成渲染和保存。

报价单生成的业务场景与痛点

报价单在销售、外贸、工程、软件服务等众多行业都是刚需,一份报价单通常包含客户信息、产品明细、单价数量、折扣税率和总金额。销售人员在客户询价后往往要尽快发出报价,任何延迟都可能影响成交,因此报价单的生成速度直接关系到业务效率。

传统后端生成方案存在明显短板:需要部署PDF渲染服务,模板更新要走发布流程,接口在高并发时容易超时。更麻烦的是中文字体,服务端环境缺少中文字体时,生成的PDF会出现乱码或方块字,售后处理成本很高。

前端生成方案则完全不同,dompdf.js内置思源黑体(Source Han Sans SC),中文无需额外配置字体;报价数据在浏览器本地组装,不经过服务器,响应快且天然支持高并发,特别适合SaaS系统、CRM和电商后台等场景。

此外,报价单还面临版本管理的问题:同一个客户可能收到多次调价后的报价,历史版本需要留存备查。前端生成时可以在模板中直接写入报价版本号和有效期,配合文件名规范,让每一版报价都可追溯,避免销售与客户之间因版本混乱产生纠纷。

为什么选dompdf.js而不是jsPDF或html2canvas

前端生成PDF的常用方案有jsPDF、html2canvas和dompdf.js。jsPDF基于坐标定位绘制,写一份带表格的报价单需要手算每个单元格的位置,代码冗长且难以维护,表格跨页更是噩梦。html2canvas则先把页面截图成位图再嵌入PDF,文字放大后模糊,文件体积大,打印清晰度也达不到商务要求。

dompdf.js采用Rust+WASM内核,直接在浏览器中解析DOM和CSS,输出真正的矢量PDF。矢量格式意味着文字和线条放大任意倍数依然清晰,文件体积小,打开速度快,还能被PDF阅读器搜索和复制,商务场景下体验远优于截图方案。

dompdf.js支持表格边框、flex/grid布局、图片嵌入和@page分页媒体属性,报价单这类结构化的商务文档正是它的强项。它还在GitHub开源(lmn1919/dompdf.js,1.5k stars),API设计简洁,addPage加save两个方法就能完成从HTML到PDF的完整流程。

从开发效率看,dompdf.js也让团队受益匪浅。报价单模板就是普通的HTML和CSS,前端工程师可以直接上手,不需要掌握服务端渲染框架;模板可以像普通前端代码一样纳入版本管理、走代码评审,改动可视化,回归测试成本低,这在后端模板方案里是很难做到的。

报价单模板设计:表格与样式

报价单的模板设计要遵循商务文档的规范:顶部是公司Logo和报价单标题,中间是客户信息与报价编号,主体是产品明细表格,底部是金额合计、有效期和签章区域。表格建议使用table标签配合border-collapse: collapse,让边框清晰统一。

样式方面,可以通过style标签或内联样式控制表格列宽、对齐方式和字体大小,dompdf.js支持flex/grid布局,报价单头部可以用flex排列Logo和编号信息。字号建议正文不小于10pt,标题16pt左右,保证打印和屏幕阅读都清晰。

需要特别注意的是金额列建议右对齐,数量列居中,产品名称列左对齐并设置较宽列宽;当报价明细超过一页时,dompdf.js会自动分页,表头也会在新页面重复显示,方便客户阅读后续页面的内容。

在实际项目中,建议把报价单模板抽成独立的组件或函数,输入报价数据对象,输出HTML字符串。这样既可以在网页预览区展示同样的模板,也可以直接传给addPage生成PDF,保证预览与导出完全一致;后续如果需要支持多语言或多种币种,也只需要在模板层做处理。

代码示例:从数据到PDF一键导出

下面给出一个完整的报价单生成示例。先构造报价单的HTML字符串,再创建DomPDF实例,调用addPage添加页面并传入A4纸型和页边距,最后用save输出文件,核心逻辑非常简洁。

如果报价数据来自接口,可以先fetch获取JSON数据,再用模板字符串或数组map方法批量生成表格行,最后拼装成完整HTML。这样报价单的内容完全由数据驱动,客户、产品和价格变更时无需修改任何代码。

上面的示例中,表头使用浅灰背景区分数据行,金额统一右对齐并带千分位分隔符,合计行通过colspan合并前几列,这些都是商务报价单的通用做法。你可以在此基础上扩展:加上公司Logo图片、付款条款、报价有效期,或者根据客户等级动态调整折扣列。

如果报价包含多条明细且长度不一,建议在拼装前对数据进行排序和格式化:空值显示为横线,数量为零的行直接过滤,避免生成后才发现数据问题。格式化逻辑单独封装,便于单元测试,也方便在生成PDF前做一轮数据校验,把错误拦截在渲染之前。

import { DomPDF } from 'dompdf.js';

// 1. 构建报价单 HTML
const quoteHtml = `
  <h1 style='text-align:center'>产品报价单</h1>
  <p>客户:某某科技有限公司  报价单号:QT-2026-0818</p>
  <table style='width:100%;border-collapse:collapse'>
    <tr style='background:#f5f5f5'>
      <th style='border:1px solid #333;padding:8px'>产品名称</th>
      <th style='border:1px solid #333;padding:8px'>单价</th>
      <th style='border:1px solid #333;padding:8px'>数量</th>
      <th style='border:1px solid #333;padding:8px'>小计</th>
    </tr>
    <tr>
      <td style='border:1px solid #333;padding:8px'>企业版软件授权</td>
      <td style='border:1px solid #333;padding:8px'>¥1,200</td>
      <td style='border:1px solid #333;padding:8px'>10</td>
      <td style='border:1px solid #333;padding:8px'>¥12,000</td>
    </tr>
    <tr>
      <td style='border:1px solid #333;padding:8px'>年度维护服务</td>
      <td style='border:1px solid #333;padding:8px'>¥800</td>
      <td style='border:1px solid #333;padding:8px'>10</td>
      <td style='border:1px solid #333;padding:8px'>¥8,000</td>
    </tr>
    <tr>
      <td colspan='3' style='border:1px solid #333;padding:8px;text-align:right'>合计(含税)</td>
      <td style='border:1px solid #333;padding:8px'>¥20,000</td>
    </tr>
  </table>
`;

// 2. 创建 PDF 并添加页面
const pdf = new DomPDF();
pdf.addPage(quoteHtml, { format: 'A4', margin: '20mm' });

// 3. 保存文件
pdf.save('报价单-QT-2026-0818.pdf');

自动分页、页眉页脚与页码

商务报价单通常需要页眉页脚和页码。dompdf.js支持页眉页脚/页码功能,可以通过@page分页媒体属性控制页面尺寸和边距,配合自动分页机制,即使产品明细多达几十行也能正确拆分为多页显示。

在样式表中声明@page { size: A4; margin: 20mm; }即可统一页面规格,页眉可以放公司名称,页脚可以放页码和报价单号,让多页报价单看起来更专业,客户在传阅和打印时也更方便归档。

多页报价单可以连续调用多次addPage,例如封面页、产品明细页和条款页分别添加,最终保存为一个PDF文件。addPage也接受DOM元素作为参数,页面里已经渲染好的报价单区域可以直接传入,实现所见即所得。

分页还有一个容易被忽略的细节:表格行尽量不要跨页断开。可以通过样式让表头行重复显示,并保证至少两行数据一起移动,避免页尾只剩孤零零的表头或单个单元格,影响阅读体验。dompdf.js的自动分页机制已经处理了大部分场景,但在模板设计阶段就要避免使用超大字号和过宽图片,给分页留出余量。

import { DomPDF } from 'dompdf.js';

const html = `
  <style>
    @page { size: A4; margin: 20mm; }
  </style>
  <h1>报价单</h1>
  <!-- 更多内容 -->
`;

const pdf = new DomPDF();
pdf.addPage(html, { format: 'A4', margin: '20mm' });
pdf.addPage(secondPageHtml); // 追加第二页
pdf.save('报价单-多页.pdf');

常见问题与注意事项

常见问题一:中文字体显示为方块。dompdf.js内置思源黑体,正常情况下中文无需配置;如果自定义了字体,需要确保字体文件加载成功,建议优先使用内置中文字体族。

常见问题二:表格边框不显示。检查是否设置了border-collapse以及单元格边框样式;dompdf.js支持表格边框,但需要显式声明,且注意不要与外部样式表的全局重置规则冲突。

常见问题三:图片不显示或变形。dompdf.js支持图片嵌入,推荐使用base64或同源URL,同时为图片设置合适的宽高;跨域图片可能需要先转换为DataURL再传入,避免浏览器安全策略拦截。

最后提醒一点:报价单涉及商业信息,生成后建议不要上传到公共存储,直接由用户浏览器下载保存;如果系统需要留档,应通过加密通道传回服务器并控制访问权限,防止报价内容泄露给竞争对手。

⚡ 现场演示(点击生成 PDF)

下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:

Hello from dompdf.js!

这是由 dompdf.js 渲染的示例 PDF 内容。