需求:
应广大后端同事需求,将下载pdf功能放在前端实现。后来在思考实现到什么程度的时候想干脆一步到位,前后端分离,跨平台使用。后端只要提供接口,其他的全部交给前端来做。当然也存在一定的问题,比如说安全性,好在公司要实现的东西都是内部在使用。所以撸起袖子就开干了。
一、产品需求
- table被切割时,另起一页。
- 生成的每一页pdf不被页眉页脚覆盖。
- 一个table超过一页pdf,则跨行的那一个tr另起一页
二、前期技术选型
三、方案确定
-
前后端分离
-
前端实现预览下载pdf功能,后端提供接口
-
独立部署,不依赖于任何一个已有项目。可供公司跨平台项目使用
四、前端插件
- html2canvas 使用JavaScript屏幕截图
- jspdf 用JavaScript生成pdf的库
- jsrender 模板库,它具有无代码标记语法和高性能,既不依赖jQuery,也不依赖文档对象模型(documentobjectmodel,DOM),支持创建自定义函数,并使用纯基于字符串的呈现。
五、html2canvas+jspdf(踩坑)
插件自身短板许多问题,大量查阅资料得到了解决。也有曲线救国方案(文字图片被分割,通过计算动态添加删除padding)。
1. 下载pdf背景黑色(期望:#FFFFFF)
html2canvas配置添加background: #FFFFFF
var element = $("#demo");
html2canvas(element,{background :'#FFFFFF'}).then(function(canvas) {
2. 像素失真模糊
并引入含有这两项配置的js文件jspdf
var ele = $("#demo");
html2canvas(ele,{background :'#FFFFFF', scale:2, dpi: 150}).then(function(canvas){})
3. 水印
文字水印
function addWaterMark(pdf) {
var totalPages = pdf.internal.getNumberOfPages();
for (i = 1; i <= totalPages; i++) {
pdf.setPage(i);
pdf.setTextColor(150);
pdf.text(50, doc.internal.pageSize.height - 30, 'Watermark');
}
return pdf;
}
图片水印
// 添加图片水印
var totalPages = pdf.internal.getNumberOfPages();
var img = new Image;
img.crossOrigin = ""; // for demo as we are at different origin than image
img.src = './logo-img1.png'; // some random imgur image
img.onload = function() {
for (i = 1; i <= totalPages; i++) {
pdf.setPage(i);
pdf.addImage(img, 520, 0);
}
pdf.save('order.pdf');
};
4. 页眉页脚(同水印文字)
5. 分页text、table、img被切割
解决问题的设计思想:(与业务强关联)
计算元素:tr
计算方式:(这里我们默认最小单位tr高度不超过pdf一页高度)
- tr自身高度超过pdf一页高度,先另起一页。再向下找小单位的tr进行递归算法。
- tr自身高度不超过pdf一页高度。但是正好跨页或者到当前页底部的距离不够展示页脚。需要另起一页。
计算收尾:下载后清除预览的padding
技术实现:
两种情况需要分页(当前tr下的所有td添加padding-top)
- tr高度 > pdf height
- tr高度 < pdf height,但是tr top 到 tr top所在页的距离 – tr自高 < 页脚pageFooterH(即tr跨页或者tr 到当前页底部距离< 页脚)
页眉页脚及水印不影响table跨页问题:将页眉页脚做成了两张白色背景的width: 100%的图片加入pdf
异步操作dom及下载:由于存在递归循环操作dom,以及等待dom被操作(tt)后才可下载pdf,运用了async/await完美解决
注意:编码html的时候不给td添加任何的padding-top,为后期下载pdf做预留。为了实现td中添加padding,可给td中元素包一个父元素设置样式。
6. html2canvas无法绘制图片
img元素地址跨域,可将图片添加到本地访问。
// 不显示
<img src="https:baidu.com/public/logo.png">
// 显示
<img src="../public/logo.png">
六、jsrender js模版引擎
在react、vue横行的时代。早期的模版引擎基本上没有什么用武之地了。这次的项目因为要实现跨平台前后端分离。所以启用了在github上还比较活跃的jsrender来实现。
1. 结构
<div class="content-body"></div>
<script type="text/x-jsrender" id="j-parent"> <table border="0" cellspacing="0" cellpadding="0" id="pdf-content"> <tbody> <tr> <td>123</td> </tr> </tbody> </table> </script>
<script src="../jquery-3.5.1.min.js"></script>
<script src="../jsrender.min.js"></script>
<script type="text/javascript"> const data = { date: '20210127', firstName: 'yang', lastName: 'yd', number: 27, cat: '<span>mimi<span>', skill: ['javascript','html','vue'], } //获取模板 jsRenderTpl = $.templates('#j-order-pdf'), //模板与数据结合 finalTpl = jsRenderTpl(data); document.getElementsByClassName('content-body')[0].innerHTML = finalTpl </script>
2. 基本用法
{{:}}
和 {{>}}
(或{{html:}}
)两者都可以输出内容,不过后者是经过html
编码的。
<p>a cat {{:cat}}</p> // a mimi
<p>a cat {{>cat}}</p> // a <span>mimi</span>
<p>a cat {{html:cat}}</p> // a <span>mimi</span>
3. 判断if/else
语法:{{if condition}} ... {else condition} ... {{else}}... {{/if}}
<p>
{{if age < 6}}
baby
{{else age > 50}}
old man
{{else}}
youth
{{/if}}
<p>
// youth
4. 循环for
语法: {{for}} ... {{/for}}
<p>
{{for skill itemVar="~item"}}
<span>{{:~item}}</span>
{{/for}}
</p>
// javascript html vue
其中~item当前循环的值的别名。
5. 模版:include组合模版
语法:include tmpl="模板id"
<script type="text/x-jsrender" id="j-parent"> {{include {"item": ~item} tmpl="#j-child-template"/}} </script>
<script type="text/x-jsrender" id="j-child-template"> <p>我被include了</p> </script>
// 我被include
6. views.helpers
语法:
-
视图 {{~”标签名称”(附加参数)}}
-
逻辑 $.views.helpers({“标签名称”:function(参数){code}})
7. views.converters 转换器
语法:
-
视图 {{“转化器名称”:参数}}
-
js .views.converters({“转换器名称”:function(参数){code}})
name: {{:~replaceName(firstName, lastName)}}
七、安全
当项目搬来前端实现基本上已经放弃了安全这部分的考虑……^_^,由于是公司内部使用,所以前端来实现了这个功能。在路由上给每一个id添加了加密,至少可以防止通过修改链接访问数据。
总结:
由于以前都是后端负责下载pdf这一块。后端同事每次修改样式都痛不欲生。这一次也是为了解决后端同事的三千烦恼做了一次大的改革。实现了跨平台,公司的任何有关下载pdf项目都可以使用这一套来实现。
开始的技术选型很重要,条条大路通罗马。也许有更好的方式来实现。
今天的文章html转pdf分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22802.html