最近做了一个功能,将md文档转成pdf发送给客户。偶有心得总结如下:
使用html-pdf
1号选手
- 优点: 简单易懂,非常方便
- 缺点: 不是很灵活,不方便添加目录,分页,样式处理等等
使用方法简便主要如下:
const pdf = require('html-pdf');
const fs = require('fs');
const moment = require('moment'); // moment.js
const html = fs.readFileSync('../public/pdfs/test.html', 'utf8');
const createPDFProtocolFile = function (template, options, reg, filename) {
if (reg && Array.isArray(reg)) {
reg.forEach(item => {
template = template.replace(item.relus, item.match);
});
}
pdf.create(template, options).toFile(filename, function(err, res) {
if (err) {
return console.log(err);
}
console.log(res);
});
}
createPDFProtocolFile(html, options, reg, './test.pdf'); // 传入参数 生成pdf
配置规则如下:
const options = {
"format": 'A4',
"header": {
"height": "10mm",
"contents": ''
}}; // 一些配置
const name = '张三';
// 匹配规则
const reg = [
{
relus: /__name__/g,
match: name
},
{
relus: /__date__/g,
match: moment().format('YYYY年MM月DD日')
}
];
此方法非常便利,但是如此便利方法有很多局限性,比如内部锚点无法跳转,无法根据标题生成目录,无法根据目录完成跳转,无法在页眉页角添加公司logo,无法做到多篇文章导出一个pdf时候分页等等。你所能使用的技术太少。所以需要用另一种框架。
wkhtmltopdf框架
这是一个非常不错的框架,比较轻便。当然也有很多坑。具体使用有两种方式,一种直接使用命令行,简单易懂,需要的小伙伴请参考官方文档,由于前端本身限制,此方法不可行。这里我们着重讲一下第二种方式,基于前端实现。
- 使用前需要搭建一个wkhtmltopdf的运行环境,具体操作参照官网
-
需要引用node包,npm i wkhtmltopdf
-
代码编写如下:
const wkhtmltopdf = require('wkhtmltopdf') const { pdfCss } = require('./pdfcss/css') const { readdirSync, statSync, existsSync, readFileSync, writeFileSync } = require('fs') const { join } = require('path') const { mkdir } = require('./helper/file') const setPdf = (urls) => { contents = '' mkdir(targetPdf) const fileName = targetPdf + '/' + arr[1] + '-' + arr[2] + '-' + arr[3] + '.pdf' // 文件名称 wkhtmltopdf(contents, {output: fileName}) }
当然这是最基础版本,下面我们来看一下坑位
wkhtmltopdf(contents, {output: fileName, headerHtml, footerHtml: targetPdfFooter + '/footer.html', headerLine: true, footerLine: true, headerSpacing: 3, ignore: [ 'Exit with code 1 due to network error: ProtocolUnknownError\n', 'wkhtmltopdf exited with code 1' ]})
此坑位是一个错误兼容处理,如果你导出的文件量比较多,这时候内容会导出,但是在运行到Jenkins等自动部署工具中会有编译错误,所以需要添加一个ignore忽略错误。
-
页眉编辑
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style> img { height: 40px; vertical-align: bottom; float: left; margin-left: -10px; } span { line-height: 35px; float: right; } div { height: 40px; } </style> </head> <body> <div><img src="https://logo-h.png"/><span>音视频通话</span></span></div></body></html>
页眉可以自定义,但是添加公司logo图片时候不能是本地地址,需要可访问地址,内容中的图片做了同样处理。
-
页脚编辑
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style> p { text-align: right; line-height: 30px; } .span-lf { float: left; } </style> </head> <script type="text/javascript"> function substitutePdfVariables() { function getParameterByName(name) { var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search); return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); } function substitute(name) { var value = getParameterByName(name); var elements = document.getElementsByClassName(name); for (var i = 0; elements && i < elements.length; i++) { elements[i].textContent = value; } } ['frompage', 'topage', 'page', 'webpage', 'section', 'subsection', 'subsubsection'] .forEach(function(param) { substitute(param); }); } </script> </head> <body onload="substitutePdfVariables()"> <p>第 <span class="page"></span> 页 共 <span class="topage"></span> 页<span class="span-lf"></span></p> </body> </html>
页脚中做了分页处理
-
样式分页
@media screen{ div.break_here { page-break-after: always !important; } }
在每个文章结尾处需要添加一个分割标签,标签类样式break_here
-
标题整理,wkhtmltopdf可以根据h标签自动生成目录,所以小伙伴们需要在编写时对标题进行规范
-
`不能出现在内容中,如果有需要在用正则替换,此方法耗时耗力,但是么有更高手段如果有好的方法希望小伙伴多多交流