前端导出多页pdf带⽬录页眉页脚及页码
前段时间公司发布新需求,要求⽤户点击按钮可以导出pdf或者html到本地,pdf中要包含可点击跳转的⽬录,要分页记录页码,还有页眉和页脚,和后台的⼩哥哥配合试了好多⽅法,最终完成的效果还不错,在这⾥做个记录。
##需求
点击导出html报告,则导出html⽂件到本地
点击导出pdf报告,则导出pdf到本地
##⾔归正传
因为设计也⽐较给⼒,直接把pdf样式设计成模板了,
所以前端的⼯作就是按照样式⽣成html页⾯扔给后台。
我之前也看了很多博客,查了很多⼯具和插件,下⾯先做个简单介绍。
1.jspdf与html2Canvas
优点在于使⽤简单,前端就可以实现,完全不需要后端配合。
因为要把html页⾯写进canvas⾥,利⽤canvas⽣成图⽚写进pdf,所以⽣成的pdf不是很清晰
另外因为是利⽤canvas⽣成图⽚,所以分页效果也超级差的
如果页⾯引⽤了图⽚的话,那么必须是在服务器环境下导出的pdf才能正确显⽰图⽚
感兴趣的⼩伙伴可以去这⾥学习,有现成的demo,代码简单。
2.pdfmake
优点和上边⼀样,并且⽀持⾃适应布局
缺点是需要引⼊字体,⽽且字体⽂件不仅⼤,⽽且⽣成过程很繁琐
感兴趣的⼩伙伴去这⾥
3.我选择的这种
后台⽤的python3,安装的⼯具是 weasyprint ,这个⽐较成熟,可以⽤来把html⽣成pdf,参考⽹址
前端的静态页⾯不需要ajax 获取数据,放到后端直接⽤jinja2 模板或者框架⾃带的模板替换数据就可以,⽐如下⾯这段代码中,title 是后台返回的⽂章标题,那么数据在后台可以这样引⽤由Mustache语法进⾏解析:
<h1>{{ title }}</h1>
这个 weasyprint ⽀持⾃动分页,⽽且分页效果⽐较好
下⾯是重点了,为了满⾜产品需求,查阅了很多⽂档才发觉的,就是如何让⽣成的pdf带有⽬录跳转,如何配置页眉页脚以及页码等。
⾸先要了解css的⼀个打印选项模块,叫做@page
下⾯这段代码 print.css 是⽤来⽣成页眉页脚及页码的
@page {
size: A4;/*设置导出的pdf的⼤⼩*/
border-bottom: 0.25pt solid #666;/*页脚处的横线*/
border-top: 0.25pt solid #666;/*页眉处的横线*/
@top-left {
content: "我是左侧的页眉内容";
vertical-align: bottom;
color: #333;
font-size: 9pt;
margin: 30pt 0 10pt 0;
}
@top-center {
@top-center {
content: "我是居中的页眉";
vertical-align: bottom;
color: #333;
前端页面模板
margin: 30pt 0 10pt 0;
}
@top-right {
content: "我是右侧的页眉内容";
vertical-align: bottom;
color: #333;
font-size: 9pt;
margin: 30pt 0 10pt 0;
}
@bottom-right {/*在页⾯的右下⾓⽣成页码*/
content:  counter(page)
" / " counter(pages);
vertical-align: top;
margin: 10pt 0 30pt 0;
color: #333;
font-size: 9pt;
}
@bottom-left {
content: "我是左侧页脚";
color: #333;
font-size: 9pt;
vertical-align: top;
margin: 10pt 0 30pt 0;
}
@bottom-center {
content: "我是居中的页脚";
color: #333;
font-size: 9pt;
vertical-align: top;
margin: 10pt 0 30pt 0;
}
}
@page:first { /*设置封⾯*/
margin: 0pt;/*让封⾯的背景图占满整页*/
padding: 0pt;/*让封⾯的背景图占满整页*/
@top-left {/*设置⾸页的页眉页脚内容为空*/
content: "";
}
@top-right {/*设置⾸页的页眉页脚内容为空*/
content: "";
display:none;
}
@bottom-right {/*设置⾸页的页眉页脚内容为空*/
content:"";
}
@bottom-left {/*设置⾸页的页眉页脚内容为空*/
content: "";
}
}
}
在写pdf模板的时候,也踩了不少的坑,在这⾥做个记录
# 单页的⼤⼩: 595 * 842 (px)
# ⾸页如果占满⼀整页,则⼤⼩:800 * 1142 (px)
# table表格添加border 需要⽤ css的⽅式:border:1px solid #000;
# pre显⽰代码并⾃动换⾏:
width: 595px;
white-space: pre-line;  //合并空格
word-break: break-word;
word-wrap: break-word; // 必须,不可舍弃
# 不⽀持css3 的⼀些属性,如: translate  scale  rotate ....
# 设计稿按照普通屏⼤⼩来进⾏设计,不要按照⾼倍屏!不要按照⾼倍屏!!不要按照⾼倍屏
# 打印选项⾥已经设计了留⽩,所以设计稿左右不必留⽩,内容充满即可
后端控制html⽣成pdf的代码如下
from weasyprint import HTML
# ⽣成pdf的⽂件名称,数组中的print.css是打印样式,index.css是pdf⽂件的样式
HTML(string='<p>这是⼀个测试</p>').write_pdf("test_11_22_test12.pdf", stylesheets=['pdf/print.css','pdf/index.css'])
# 将百度⾸页导出为pdf
HTML('www.baidu').write_pdf("baidu.pdf", stylesheets=['pdf/print.css'])
想要在pdf中⽣成⽬录也很简单,只需要给⽬录设置为a链接,然后在href属性中设置相应的id就可以了,本质也就是锚点跳转
<div id="header" >我是页⾯的头部</div>
<div class="section" >
<a href="#header">点击我页⾯将返回id为header的元素所在的位置</a>
<a href="#footer">点击我页⾯将返回id为footer的元素所在的位置</a></div>
<div id="footer" >我是页⾯的尾部</div>
需要注意的是,pdf阅读器也可以读取页⾯中所有的标题(h1-h6),然后⽣成树形结构图,也具有⽬录的作⽤。但是通常⼀个页⾯只有⼀个h1标签,如果不想要h1作为⽬录,可以在css中设置
h1{
bookmark-level:none;
}
关于bookmark,这是css中的书签,⽹上的资料不多,感兴趣的⼩伙伴可以参考
好了,以上内容如果理解了,就可以顺利⽣成pdf了,下⾯来看看如何点击按钮进⾏下载,先上代码
var opt = {
type: 'HEAD',//设置请求⽅式
url:'/api/html_pdf?format=json',//请求路径
headers:{'Authorization':token},
data:{work_name:work_name,flag:flag},
complete:function(xhr, data) {
var dis = ResponseHeader("Content-Disposition")
download(dis)
}
}
$('.downPdf').bind('click',function(){
$.ajax(opt);
})
function getFileName(headers) { //获取responseHeaders中的⽂件名称
place('attachment;filename=', '');
}
function download(res) {
var a = ateElement('a');  //创建⼀个a链接
var filename = getFileName(res);      //pdf⽂件的名称
var url = '/media/down_pdf/' + filename;  //服务器中的pdf⽂件存储位置
a.href = url;                        //将⽂件地址付给a链接
a.setAttribute('download','report.pdf');//设置a链接的download属性,并命名下载的pdf⽂件为report.pdf
a.click();                            //触发a链接的下载动作
}
看看后端是如何返回数据的
服务端向浏览器发送⽂件时,如果是浏览器⽀持的⽂件类型,⼀般会默认被浏览器打开,⽐如txt、jpg、视频以及⾳频等,但如果需要提⽰⽤户保存,就要利⽤ Content-Disposition 进⾏处理:
Response.AppendHeader("Content-Disposition","attachment;filename=FileName.pdf");
Content-disposition 是 MIME 协议的扩展,MIME 协议指⽰ MIME ⽤户代理如何显⽰附加⽂件。所以Content-disposition可以控制⽤户请求所得的内容直接在浏览器上显⽰还是在访问时弹出⽂件下载对话框进⾏下载。
上⾯的代码是赋值语句,类似tDisposition = “Content-Disposition” “:” disposition-type *( “;”
disposition-parm )
Content-Disposition为属性名
disposition-type是以什么⽅式下载,如attachment为以附件⽅式下载
disposition-parm为保存时的默认⽂件名
⼩伙伴可能注意到还有个字段与平时的请求不同,就是Content-Type 这个字段。它相当于告诉浏览器后台返回的数据类型是哪种类型: Response.AppendHeader("Content-Type","application/actet-stream");
actet-stream指定类型为未知类型,或者后端也可以指定为pdf类型
Response.AppendHeader("Content-Type","application/pdf");
我们还要通过这个接⼝导出html页⾯,因此指定为未知类型

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。