海南 网站 建设,平面设计正规培训机构,沈阳百度推广哪家好,公司企业展厅设计公司目录
需求
一、方案调研
二、wkhtmltopdf使用
如何使用
文档简要说明
三、后端服务
四、前端服务
往期回顾 需求
最近在做报表类的统计项目#xff0c;其中有很多指标需要汇总#xff0c;网页内容有大量的echart图表#xff0c;做成一个网页去浏览#xff0c;同时…目录
需求
一、方案调研
二、wkhtmltopdf使用
如何使用
文档简要说明
三、后端服务
四、前端服务
往期回顾 需求
最近在做报表类的统计项目其中有很多指标需要汇总网页内容有大量的echart图表做成一个网页去浏览同时需要转成PDF格式下载浏览更重要的是pdf格式再打开后需要自定义页眉、页脚页码支持文本的选中、复制、粘贴同时左侧也要有正常的页签导航点击哪里到哪里。 一、方案调研 经过调研主要有以下几种方式生成pdf但是每个方案都有缺陷跟我们的需求相差。
方案优点缺点window.print()1、兼容性最好 2、可以将任意内容导出成 pdf 文档, 甚至是非改页面上的内容 1、调用方法时部分条件下导出pdf需要用户手动选择 2、生成的pdf不支持生成页签导航 3、页眉页脚不适合自定义 jspdf html2canvas1、在jspdf上将生成效果不佳的部分可以转成图片适用于对样式有要求的场景 2、将乱码部分转为了图片解决了中文乱码问题 3、没有预览点击即可保存 1、如果内容包含echart图表或者其它图表该内容需要转图片 2、生成的pdf实际为图片不支持复制 3、不同浏览器生成可能会有略微差异页面周边留白部分差异 4、由于整体效果为图片导致pdf文件较大两页2.5MB左右 5、pdf分页不好处理 6、不支持生成页签导航 wkhtmltopdf 1、支持自定义页眉页脚页码 2、支持文本选中粘贴复制 3、支持将html的h标签自动生成pdf 1、需要结合后端去实现生成接口返回给前端下载 2、wkhtmltopdf 使用 WebKit 渲染引擎这意味着它在某些情况下可能无法完全支持所有现代 CSS 和 JavaScript 特性特别是那些依赖于最新浏览器特性的功能 3、wkhtmltopdf 对 JavaScript 的支持有限。虽然它可以在某些情况下执行简单的 JavaScript但复杂的交互式内容可能无法正确渲染。
前两种是纯前端去实现的方案一是用浏览器打印功能实现这种方案简单粗暴但是需要手动触发不支持自定义页眉页脚页码浏览器也不支持生成页签导航。第二种把整个页面生成图片完整还原了样式但是跟我们的要求差太远。第三种是wkhtmltopdf底层是C去实现的能够高效地将 HTML 内容转换为高质量的 PDF 文件。下面主要介绍下wkhtmltopdf使用。 二、wkhtmltopdf使用 官网入口wkhtmltopdf
如何使用
下载预编译的二进制文件或从源代码构建
下载链接wkhtmltopdf
以下是适配所有操作系统的包我们根据自己的系统不同的下载包 以centeros7为例
1.首先我们下载我们需要的包 我的是x86_64的下载完成后将包传到服务器 运行命令安装
rpm -Uvh wkhtmltox-0.12.6-1.centos7.x86_64.rpm 报错 原因是缺少依赖我们来安装下依赖
yum install fontconfig libX11 libXext libXrender libjpeg libpng xorg-x11-fonts-Type1yum install -y xorg-x11-fonts-75dpi再次运行安装命令 查看版本
wkhtmltopdf --version大功告成 YYDS
安装完成后我们来使用它
创建要转换为PDF或者图像的HTML文档通过命令运行工具生成PDF
比如我要将Google网页保存为pdf则可以直接运行命令
wkhtmltopdf http://google.com google.pdf
文档简要说明
官方文档说明https://wkhtmltopdf.org/usage/wkhtmltopdf.txt
强烈建议查看官方文档以下基于0.12.6的版本
1. 基本命令
wkhtmltopdf [选项] 输入文件或URL 输出PDF文件
示例
wkhtmltopdf input.html output.pdf
2.大纲必要实现
大纲就是PDF阅读器中用于显示导航跳转的部分不属于PDF文档中的一部分主要是方便阅读器浏览导航使用。
Wkhtmltopdf 用 patched qt 支持PDF大纲也称为书签可以通过设置--outline 默认选项选项实现。
大纲是根据 h?(h1–h6) 标签生成的有关如何实现的详细说明请参见目录部分。
如果 h? 标签在HTML文档中嵌套的层级非常深那么大纲树的层级也会变得非常深。可以通过--outline-depth选项来设置大纲的层级深度。
详细使用参考这篇文章哈哈哈
wkhtmltopdf 0.12.6 中文文档精心整理-CSDN博客
原理是wkhtmltopdf将整个带css的html文档转为了pdf因此想要 将我们前端画的好看的页面生成pdf需要将html文档传给wkhtmltopdf。 三、后端服务 我们需要写一个后端服务通过接口将前端绘制的漂亮页面整个以api的方式传给后端后端将文档内容整理后调用wkhtmltopdf的命令来生成pdf然后返回文件流给前端提供下载。
npm为我们提供了调用wkhtmltopdf服务的插件
wkhtmltopdf - npm
以下是简单用法以官方最新为准
var wkhtmltopdf require(wkhtmltopdf);// URL
wkhtmltopdf(http://google.com/, { pageSize: letter }).pipe(fs.createWriteStream(out.pdf));// HTML
wkhtmltopdf(h1Test/h1pHello world/p).pipe(res);// Stream input and output
var stream wkhtmltopdf(fs.createReadStream(file.html));// output to a file directly
wkhtmltopdf(http://apple.com/, { output: out.pdf });// Optional callback
wkhtmltopdf(http://google.com/, { pageSize: letter }, function (err, stream) {// do whatever with the stream
});// Repeatable options
wkhtmltopdf(http://google.com/, {allow : [path1, path2],customHeader : [[name1, value1],[name2, value2]]
});// Ignore warning strings
wkhtmltopdf(http://apple.com/, { output: out.pdf,ignore: [QFont::setPixelSize: Pixel size 0 (0)]
});
// RegExp also acceptable
wkhtmltopdf(http://apple.com/, { output: out.pdf,ignore: [/QFont::setPixelSize/]
});
以下是我写的一个简单的node server.js调用案列
const express require(express);
const path require(path);
const app express();
const port 3002;// 引入 cors 中间件
const cors require(cors);// 使用 cors 中间件
app.use(cors());const fs require(fs);// 解析 JSON 请求体设置最大限制为 50MB
app.use(express.json({ limit: 50mb }));// 解析 application/x-www-form-urlencoded 请求体设置最大限制为 50MB
app.use(express.urlencoded({ extended: true, limit: 50mb }));// PDF生成高并发处理
function getPdfHeavyTask(html) {const wkhtmltopdf require(wkhtmltopdf);const options {output: ./pdfs/demo.pdf,pageSize: letter,orientation: portrait,marginTop: 1.8cm,marginBottom: 1.2cm,marginLeft: 1cm,marginRight: 1cm,encoding: UTF-8,dpi: 300,zoom: 1,title: pdf生成demo,enableSmartShrinking: true,javascriptDelay: 1000,noStopSlowScripts: true,headerHtml: ./template/header.html, // 设置页眉模板footerHtml: ./template/footer.html // 设置页脚模板};return new Promise((resolve) {wkhtmltopdf(html, options, (err, stream) {if (err) {resolve({ status: 500, data: err });return;}resolve({ status: 200, data: stream });});});
}app.post(/generate-pdf, async (req, res) {const { content, css } req.body;let html !DOCTYPE htmlhtml langzh-CNheadmeta charsetUTF-8titlepdf生成demo/titlestylebody { font-family: Microsoft YaHei, SimSun, sans-serif; }${css}/style/headbody${content}/body/html;// 高并发生成异步任务处理const { status, data } await getPdfHeavyTask(html);// PDF生成失败if (status 500) {res.status(500).send(data);return;}// PDF生成成功读取const filePath path.resolve(__dirname, ./pdfs/demo.pdf);const fileStream fs.createReadStream(filePath);const stat fs.statSync(filePath);res.setHeader(Content-Length, stat.size);res.setHeader(Content-Type, application/pdf);res.setHeader(Content-Disposition, attachment; filenamedemo.pdf);fileStream.pipe(res);
});app.listen(port, () {console.log(Server running at http://localhost:${port});
});
页眉页脚代码根据自己的需求添加即可
案例header.html 自定义页码 !DOCTYPE htmlhtmlheadscriptfunction subst() {var vars {};var query_strings_from_url document.location.search.substring(1).split();for (var query_string in query_strings_from_url) {if (query_strings_from_url.hasOwnProperty(query_string)) {var temp_var query_strings_from_url[query_string].split(, 2);vars[temp_var[0]] decodeURI(temp_var[1]);}}var css_selector_classes [page, frompage, topage, webpage, section, subsection, date, isodate, time, title, doctitle, sitepage, sitepages];for (var css_class in css_selector_classes) {if (css_selector_classes.hasOwnProperty(css_class)) {var element document.getElementsByClassName(css_selector_classes[css_class]);for (var j 0; j element.length; j) {element[j].textContent vars[css_selector_classes[css_class]];}}}}/script/headbody styleborder:0; margin: 0; onloadsubst()table styleborder-bottom: 1px solid black; width: 100%trtd classsection/tdtd styletext-align:rightPage span classpage/span of span classtopage/span/td/tr/table/body/html 四、前端服务 前端只需要将我们的html和css通过接口传给后端即可
try {const htmlContent document.getElementById(report-content).outerHTML// 使用fetch API获取CSS文件const response await fetch(../../assets/core-report.css)const css await response.text()this.http.post(/generate-pdf,{content: htmlContent, // 网址或者HTML文档css,},undefined,{responseType: arraybuffer,observe: response,}).subscribe((response: any) {if (!response) {this.dloading falsethrow new Error(生成 PDF 失败)}this.downloadProgress 100// 将 ArrayBuffer 转换为 Blob 对象const blob new Blob([response.body], { type: application/pdf })// 创建一个 URL 对象const url URL.createObjectURL(blob)// 下载 PDF 文件const a document.createElement(a)a.href urla.download demo.pdfdocument.body.appendChild(a)a.click()document.body.removeChild(a)URL.revokeObjectURL(url)},(error) {console.error(PDF生成失败:, error)})} catch (error) {console.error(PDF生成失败:, error)}
我们通过脚本获取到html文档通过fetch直接将文件内容获取然后通过接口将两个参数传给后端后端通过将两个内容组装成完整html调用wkhtmltopdf生成pdf在通过文件流返回前端下载。这样生成的pdf支持文本选中、复制、搜索同时它会根据H标签识别页签导航内容实现页签点击导航YYDS
注意点
1如果内容中存在canvas或者图片需要转base64传给后端或者使用cdn链接
2css3中的样式不支持比如阴影以及flex布局不支持
3内容被切分
在每个章节的标题或者其他地方我们往往不希望标题被切成两半分别出现在两个页面当中。因此我们需要添加如下样式
.title {page-break-before: always;page-break-after: always;page-break-inside: avoid;
}
4 表格切分
文档中会出现大量的表格。如果希望放置表格被切分也是同样的处理方式
table tr {word-break: break-all;page-break-before: always;page-break-after: always;page-break-inside: avoid;
} 欢迎在评论区交流。
如果文章对你有所帮助❤️关注点赞❤️鼓励一下博主会持续更新。。。。 往期回顾 CSS多栏布局-两栏布局和三栏布局 border边框影响布局解决方案 css 设置字体渐变色和阴影
css 重置样式表Normalize.css css实现元素居中的6种方法
Angular8升级至Angular13遇到的问题
前端vscode必备插件(强烈推荐)
Webpack性能优化
vite构建如何兼容低版本浏览器
前端性能优化9大策略面试一网打尽
vue3.x使用prerender-spa-plugin预渲染达到SEO优化 vite构建打包性能优化 vue3.x使用prerender-spa-plugin预渲染达到SEO优化 ES6实用的技巧和方法有哪些 css超出部分显示省略号
vue3使用i18n 实现国际化
vue3中使用prismjs或者highlight.js实现代码高亮
什么是 XSS 攻击什么是 CSRF什么是点击劫持如何防御