乐趣区

基于前端技术生成PDF方案

需求背景

  • 业务系统需要预览报告(如产品周报,体检报告等)并生成 pdf 格式供用户下载,或者定期发送给指定用户
  • 报告格式相对固定,由文本,图片和图表组成,基本与前端页面保持一致

解决方案

需求分为两步:报告预览和报告生成。

  • 报告预览在前端进行展示,可使用前端技术,如 React/Vue 等技术栈对其进行还原,数据从服务端获取。
  • 报告生成需要对第一步生成的 HTML 进行 PDF 的转换生成,HTML2PDF 的方式又分为两种:

    • 基于 canvas 的客户端生成方案
    • 基于 nodejs + puppeteer 的服务端生成方案

一个完整的案例

下面以一个体检报告的案例进行这两种方案的说明:
体检报告展示形式如下,格式相对固定,分为四个页面:个人信息页,建议页,原理页,个人信息页与建议页数据来源于服务器。
report.png

基于 canvas 的客户端生成方案

canvas 是 HTML5 标准中新增的元素,可用于通过使用 JS 的脚本来绘制图形。canvas 提供了 toDataURL/toBlob 方法,用于把 canvas 中的内容转换为图片,API 文档如下(来源于 MDN):

由于 HTML 文档再浏览器中是以 DOM 树的形式存在,所以我们可以通过三步完成 HTML 到 PDF 的转换:

  • 将 DOM 树转换为 canvas 对象,可使用 html2canvas 完成
  • 将 canvas 转换为图片,可使用 canvas.toDataURL 完成
  • 将图片转换为 PDF,可使用 jsPDF 完成

完整代码实现:https://github.com/simonwoo/d…

截图如下,点击下载按钮可进行 pdf 生产:

该方案完全基于客户端的方式生成,不需要服务器进行支持。在使用该方案的过程中,发现了一些问题:

  • 生产的 PDF 比较模糊,质量不高
  • 如果 HTML 中有外链图片,无法生成
  • 由于第一步是通过 DOM 去生成 canvas,所以针对特别长的报告,DOM 尚未加载完便点击下载时,会造成报告生成问题
  • 因为是客户端方案,所以需要用户主动触发生成,但对于一些定期发送给用户的报告,该方案无法使用

基于 nodejs + puppeteer 的服务端生成方案

puppeteer 是 google 推出的 headless 浏览器,即没有图形界面的浏览器,但又可以实现普通浏览器 HTML/JS/CSS 的渲染,以及其他基本浏览器功能。你可以理解为一个没有界面的 Chrome 浏览器。主要有以下几种使用场景:

  • 生成页面的截图和 PDF
  • 抓取 SPA 并生成预先呈现的内容(即“SSR”)
  • 爬虫,从网站抓取你需要的内容
  • 自动化测试,自动表单提交,UI 测试,键盘输入等
  • 创建一个最新的自动化测试环境。使用最新的 JavaScript 和浏览器功能,直接在最新版本的 Chrome 中运行测试

通过理解 puppeteer 的功能,我们可以开启一个实例去渲染 HTML 报告,然后再利用其提供的转换 PDF 功能进行 PDF 的生成。

两个重要的 API:

  • page.goto(url, [options]) – 打开指定 url 的文件,可以是本地文件(file://)也可以是网络文件(http://)
  • page.pdf([options]) – 转换页面成 PDF 文件

puppeteer 使用一个小例子,将百度网页转换为 pdf:

完整代码如下:

  • 前端部分:https://github.com/simonwoo/d…
  • 后端部分:https://github.com/simonwoo/d…

项目启动流程如下:

  • 进入到 webapp 目录,使用 npm install 和 npm run start 启动前端服务器,地址为:localhost:3000
  • 进入到 server 目录,使用 npm install 和 npm run dev 启动 node 服务器,地址为:localhost:7001

整个服务架构如下:

node 服务器通过路由增加一个 pdf 生成的 controller,该 controller 通过启动 puppeteer 实例去加载 localhost:3000 的页面并生成 pdf。直接在浏览器中通过 http://localhost:7001/pdf 即可访问到生成的 pdf.

在实际环境中,前端页面可部署在 nginx 服务器上或者直接放在 Node 服务器上,puppeteer 也支持使用 cookie 的操作,这样可以避免一些需要身份认证的问题。

相比客户端生成方式,使用 puppeteer 生成的 pdf 质量比较高,可满足生产要求。

本文中提到的两种方案中,均省去了 ajax 后端请求数据部分,读者可根据需要自行增加。

Reference

  • html2canvas – https://html2canvas.hertzen.c…
  • jsPDF – https://github.com/MrRio/jsPDF
  • puppeteer – https://zhaoqize.github.io/pu…
  • Eggjs – https://eggjs.org/zh-cn/

原文链接:https://juejin.im/post/5d036a…

退出移动版