阅读 16298

html转PDF文件,完美解决方案——jsPDF,htmltocanvas,pdfmake,wkhtmltopdf,TuesPechkin,snappy

-----------------------------------------2019年3月13日更新------------------------------------------------


这篇文章写后有很多人私聊问我如何解决内容切分的问题,还有如何在文档中添加图表等问题。这里我对这些问题做一个答复,希望能够帮到大家。

在我们使用wkhtmltopdf工具将html页面转换成pdf的时候,如果不想让内容被切分,则需要给工具一个明确的指示,这个指示就是 css。

防止内容被切分

在每个章节的标题或者其他地方我们往往不希望标题被切成两半,分别出现在两个页面当中。因此,我们需要添加如下样式:

.title {
    page-break-before: always;
    page-break-after: always;
    page-break-inside: avoid;
}
复制代码

样式的含义已经一目了然了,当wkhtmltopdf工具在渲染到标有.title样式的元素时如果剩余空间不足以放置该元素,则会将元素另起一页。

同样,在一般的公司文档中会出现大量的表格。如果希望放置表格被切分也是同样的处理方式:

table tr {
    word-break: break-all;
    page-break-before: always;
    page-break-after: always;
    page-break-inside: avoid;
}
复制代码

有一个技巧,如果希望表格分布在两个页面时,第二页表格依然保有表头,则需要在编写table的时候将元素进行结构化,即分为:<thead>,<tbody>,<tfoot>

在文档中添加图表

我生成图表的方式是使用了echarts库。很多人觉得图表的数据是通过http请求的来的,然后基于数据生成图表,所以一遇到图表就不知道该怎么办了。其实图表和普通的html元素并没有什么不同。遇到这种问题我采取的办法就是先利用获取的数据将页面渲染好,然后用wkhtmltopdf工具将渲染好的页面生成pdf。

具体方法有两种:

  • 方法一:前端在浏览器中将整个页面渲染好,然后将整个页面通过http请求传送至后台,后台在接收到html数据后,利用接收到的数据生成一个html文件,再用wkhtmltopdf工具去处理生成的html文件即可。因为此时的html文件只是一个静态页面,里面的所有数据都是固定的,不会再存在异步的问题。
  • 方法二:另一个做法就是利用服务端渲染。有过传统web项目开发的人都知道,以前的html页面都是通过后台渲染然后推送至浏览器进行展示的。使用的工具有freemarker, themeleaf等。前端开发人员将整个静态html开发好交给后台开发人员,后台开发人员通过themeleaf等工具将后台数据推送至页面并渲染。然后将生成的html页面利用wkhtmltopdf生成pdf文件。这样的好处就是可以后台进行pdf文件的生成。

第一种方法的弊端就是每次必须通过浏览器将页面打开,而第二种方法则不需要。

将特定内容单独另起一页

有时候我们的文档可能分为几个部分,每个部份需要从新的一页开始展示。但是通过以上方法并不能解决这个问题。所以我们需要对每个需要单独展示的部分进行特殊处理。其实处理的方式与防止内容被切分是一样的。如下:

当你需要在当前部分之前进行分页时:

.page-break-before {
    page-break-before: always;
}
复制代码

当你需要在当前部分之后进行分页时:

.page-break-after {
    page-break-after: always;
}
复制代码

总结

到这里应该能够满足大多数的开发需求了,希望能够帮到大家。如果本文对你有帮助请为我点个赞,你的支持是我继续分享的最大动力。

---------------------------------------------- 分割线 ------------------------------------------------


最近新换了一个公司,一入职就给了我一个报表下载的项目,从零开始做起。因为之前也做过导出Excel和PDF的相关工作,所以一开始并不觉得有什么困难。直到看到UI设计出的报表之后我的内心是崩溃的。整个报表很长,有各种样式,各种Table。。。这跟用Excel导出一系列数据,或者生成一个简单PDF是很不一样的。因为这两种情况都可以通过后台实现,而且也不复杂。

关于如何通过后台生成一个Excel与PDF这里就不做介绍了,java里都有现成的插件。

先来介绍下我的开发环境:

  • Vue.js
  • Vuex
  • vue-router
  • vue-cli(所以项目构建使用的是webpack)

下面来说说这一路走过来我摔了几次键盘。

第一次摔键盘:

拿到任务第一时间开始到网上找各种方案,参考下各位大牛有没有成熟的解决方案,毕竟这也算是一个合理且应用场景比较广泛的需求。我坚信网上一定会有插件提供类似功能。

过程很顺利,我成功找到了jsPDF,jsPDF是一款能够在前端生成PDF并下载的插件,感觉很牛逼。通过jsPDF与htmltocanvas配合使用就能实现将html页面转换成PDF文件并下载。原理就是通过htmltocanvas给html页面拍个照,然后将页面保存在canvas中,再通过jsPDf将canvas贴到PDF文件中。所以,本质上生成的生成的PDF其实里面就是一张图片。

到这里,一切都很完美。。。但是!只能生成单页PDF!!!这么坑?我是不信的,后来找个一个方案:根据a4纸的高度将生成的canvas图片截成一块一块,然后再分别贴入PDF的不同页面中。这样就能够给生成多页面PDF了。注意:例子里面的调用方法已经过时了,可以参考html2canvas官网的例子。

但是!生成的PDF会被截断!不只是图片会被截断,甚至文字也会被截断。这简直让人无法忍受,于是果断摔键盘,换!方!案!

这里给出几个jsPDF的官方网站,如果键盘多的土豪可以研究一下:

各位老板可别打我,你可能发现网站打不开,于是很心急的买了个VPN,结果发现还是打不开。这时候不要怀疑,没错,后面两个官方的网址就是打!不!开!所以根本没有文档可以参考,甚至有什么API都不知道。。。有前同事很自(chou)信(pi)的说:你可以去看源文件啊。我只能说:嗯嗯,你说的都对。

第二次摔键盘:

第一种方案失败后万念俱灰,因为网上解决生成PDF文件的方案基本上都是使用jsPDF+htmltocanvas。可是这个方案如此不靠谱,不知道还有没有其他更好的方案,心里有点没底。

经过好长时间的查找,终于在一个提问的回答中看到了一个关键字:wkhtmltopdf,于是赶紧搜索。于是找到了一篇介绍wkhtmltopdf与jsPDF优缺点的文章,简直是找到了知音有木有,遇到的问题一样一样的。

找到方案之后立马开始尝试,wkhtmltopdf的执行语法是这样的:

wkhtmltopdf htmlPath ouputPath
复制代码

其中htmlPath是文件路径,可以是网络地址,也可以是本地文件地址。outputPath是导出的PDF文件的存放路径。

在网上找了几个网址,比如CSDN什么的试了一下,还真的可以!生成的PDF文件能够自动分页,唯一的问题就是内容还是会被切分。不过在这之前我已经找到避免分页时,内容被切分的方案了,所以暂时没有理会切分问题,想着回头再解决。具体解决方案我会在接下来的内容里介绍。

有了初步成果之后我开始用我自己的页面做尝试,看看能否顺利转成PDF文件。

。。。。。。。。。耐心等待中。。。。。。。。。

喜闻乐见,失败了。。。文件是生成了,但是里面的内容时一片空白。。。

于是又开始了无尽的google之旅。翻了无数博客之后找了一篇关于wkhtmltopdf导出文件空白的博客,里面给出了一些可能会导致文档空白的例子。我的空白问题并不是这些情况引起的,因此我没有采用这个方案。如何在vue项目中解决空白问题我会单独写一篇博客,这里只介绍一些通用情况。在我的项目中引起空白页的主要原因是就是,使用webpack打包的项目,index.html页面在查看的时候是不显示具体页面内容的,具体内容都包含在一个js文件中,只有在访问到具体某个页面路由的时候相应的资源才会被调出来。但是wkthtmltopdf只能解析静态资源,不会去运行js文件。因此,index.html中包含什么内容,导出来的PDF文件就包含哪些内容。

这里介绍一个判断当前页面能否被wkhtmltopdf正常导出的一个方法:

将当前页面在浏览器中另存为,保存到本地,如果本地文件打开后是有内容的,那么wkhtmltopdf就能正常导出。因为这个工具只会解析html与CSS,并不会去运行js文件。所以在webpack这种项目中,所有资源都被打包成一个js文件,wkhtmltopdf就无法正常导出了。还有一个问题就是:如果页面中的内容时在页面开始渲染时才通过ajax请求从后台获取的,也是无法被渲染出来的,原因还是wkhtmltopdf不会去执行js文件,所以在渲染的时候ajax请求是不会发送的,更加不会被渲染到页面上。

这个问题我的解决方案是:等待页面正常渲染过后将整个页面传给后台,后台在接到页面数据后在本地保存为一个html文件,然后使用wkhtmltopdf将本地html文件转换成PDF文件。不过这样做要注意:一定要在后台提前准备好静态资源,否则在生产环境下CSS样式和图片什么的就无法渲染。

获取页面信息的代码如下:

let htmlEle = document.querySelector('#downloadPaper');
复制代码

为index.html页面的html跟标签附一个id值:downloadPaper,然后通过ajax请求将整个页面信息传递给后台。注意:传送类型为post,参数类型为'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'(具体类型还是要以后台实际为准)。在传参的时候记得取出htmlEle中的html元素:

{
    searchData: htmlEle.innerHTML
}
复制代码

其他也没啥了,后台在成功生成PDF文件后就可以返回文件路径啦,然后前端调用下载接口,将文件路径传递回去就能下载文件啦:

window.open(vm.apiHost + 'download?pdfFilePath=' + res.pdfFilePath)
复制代码

到此为止,已经可以成功下载PDF文件啦,如果你在后台准备了静态文件,生成的PDF文件应该是包含样式的。但是背景图这种较大的图片因为无法被压缩成base64,所以也需要在静态问价那种包含,并将路径配置正确。

为了庆祝一下阶段性成果,我还是要怒砸键盘!因为导出的内容切割问题还没解决。今天写的有点累了,内容切割问题下次再补充吧,休息一会儿。

对了还可以使用Freemarker来生成PDF文件,但是因为一开始把这个方案忘了(其实是不知道怎么在Vue框架中使用),所以就没有研究这种方案。

文章分类
前端