赞
踩
导出具有页眉页脚、页码的Pdf,并且解决Pdf分割的问题。
该需求主要的难点在于分页的时候容易出现分割问题,并且要将页眉页脚加进去。实现的大概思路:
(1)先使用jsPDF、html2canvas将页面可以导出;
(2)第一页的页眉在html代码中添加,第二页的页眉在分页的时候动态添加;
(3)在会出现分割问题的节点上添加一个分割类名,这个节点必须要有父元素,因为如果是在这个节点分割的话,需要判断这个节点距离底部的距离,然后在这个节点后面添加页脚、页码;
(4)获取在分页节点的元素,domList是根据分割类名获取的节点,循环这些节点,判断它是否处于分页的位置;
- for (let i = 0; i < domList.length; i++) {
- const eleBounding = this.ele.getBoundingClientRect();
- const node = domList[i];
- const bound = node.getBoundingClientRect();
- const offset2Ele = bound.top - eleBounding.top;
- const currentPage = Math.ceil((bound.bottom - eleBounding.top) /pageHeight)
- }
(5)定义一个创建空白元素、页眉、页脚的函数
(6)添加页眉页脚,由于页脚是在A4纸的底部,所以在添加页脚之前需要获取分页节点距离A4纸底部的空白高度,如果这个空白高度大于页脚高度,那么先添加一个空白元素,空白元素的高度等于空白高度减去页脚高度,最后再依次添加页脚和页眉。如果空白高度小于页脚高度,那么需要依次向上累加分割节点的高度,至到高度大于页脚高度;
- const divParent = domList[i].parentNode // 获取该分割节点的父节点
- let emptyHeight=pageHeight * pageNum - offset2Ele;
- if (pageNum < currentPage) {
- // console.log(i,pageNum,currentPage,offset2Ele,emptyHeight);
- pageNum++
- if(emptyHeight>=70){//空白节点高度如果大于页脚高度才添加空白节点
- divParent.insertBefore(this.createEmptyNode(emptyHeight), node);
- divParent.insertBefore(this.creatFooterNode(pageNum,id), node);
- divParent.insertBefore(this.createHeaderNode(practiceName), node)
- }else{// /空白节点高度如果小于页脚高度,就要把上一个节点挤下去
- emptyHeight=emptyHeight<0?0:emptyHeight;
- let j=1;
- emptyHeight=emptyHeight+domList[i-j].offsetHeight;
- while(emptyHeight<70){
- j++;
- emptyHeight=emptyHeight+domList[i-j].offsetHeight;
- }
- let newDivParent= domList[i-j].parentNode;
- newDivParent.insertBefore(this.createEmptyNode(emptyHeight), domList[i-j]);
- newDivParent.insertBefore(this.creatFooterNode(pageNum,id), domList[i-j]);
- newDivParent.insertBefore(this.createHeaderNode(practiceName), domList[i-j])
- }
- }

(7)在分割节点循环完成之后根据第(6)条思路为最后一张A4纸添加页脚。并且计算出总的A4纸页数。
(1)分割的节点一定要有父元素不然找不到父元素要报错!
(2)需要导出的html页面的宽度要定死,宽度越大,导出的字体越小,高度定成100%。
(3)将要导出的页面封装成一个组件,引用组件的时候设置以下样式。
- width: 900px;
- height: 100%;
- position: fixed;
- top:0;
- left: 0;
- z-index:-1000"
- import PdfLoader from "@/libs/pdf.js";
- const pdf = document.getElementById('sportsTreatRecord');
- let fileName=`导出的pdf文件名`
- setTimeout(() => {
- let pdfReq=new PdfLoader(pdf,fileName,'splitClassName');
- pdfReq.outPutPdfFn(fileName,null,this.practiceName);
- }, 3000);
- import jsPDF from 'jspdf'
- import html2canvas from 'html2canvas'
- import logoUrl from '../assets/image/newLogo.png'
- /*
- * 使用说明
- * ele:需要导出pdf的容器元素(dom节点)
- * pdfFileName: 导出文件的名字 通过调用outPutPdfFn方法也可传参数改变
- * splitClassName: 避免分段截断的类名 当pdf有多页时需要传入此参数 , 避免pdf分页时截断元素 如表格<tr class="itemClass"></tr>
- * 调用方式 先 let pdf = new PdfLoader(ele, 'pdf' ,'itemClass');
- * 若想改变pdf名称 pdf.outPutPdfFn(fileName); outPutPdfFn方法返回一个promise 可以使用then方法处理pdf生成后的逻辑
- * */
- class PdfLoader {
- constructor(ele, pdfFileName, splitClassName) {
- this.ele = ele
- this.pdfFileName = pdfFileName
- this.splitClassName = splitClassName
- this.A4_WIDTH = 595.28
- this.A4_HEIGHT = 841.89
- }
-
- async getPDF(resolve) {
- const ele = this.ele
- const pdfFileName = this.pdfFileName
- const eleW = ele.offsetWidth// 获得该容器的宽
- const eleH = ele.scrollHeight// 获得该容器的高
- const eleOffsetTop = ele.offsetTop// 获得该容器到文档顶部的距离
- const eleOffsetLeft = ele.offsetLeft// 获得该容器到文档最左的距离
- const canvas = document.createElement('canvas')
- let abs = 0
- const win_in = document.documentElement.clientWidth || document.body.clientWidth// 获得当前可视窗口的宽度(不包含滚动条)
- const win_out = window.innerWidth// 获得当前窗口的宽度(包含滚动条)
- if (win_out > win_in) {
- abs = (win_out - win_in) / 2// 获得滚动条宽度的一半
- }
- canvas.width = eleW * 2// 将画布宽&&高放大两倍
- canvas.height = eleH * 2
- const context = canvas.getContext('2d')
- context.scale(2, 2) // 增强图片清晰度
- context.translate(-eleOffsetLeft - abs, -eleOffsetTop)
- html2canvas(ele, {
- useCORS: true// 允许canvas画布内可以跨域请求外部链接图片, 允许跨域请求。
- }).then(async canvas => {
- const contentWidth = canvas.width
- const contentHeight = canvas.height
- // 一页pdf显示html页面生成的canvas高度;
- const pageHeight = (contentWidth / this.A4_WIDTH) * this.A4_HEIGHT // 这样写的目的在于保持宽高比例一致 pageHeight/canvas.width = a4纸高度/a4纸宽度// 宽度和canvas.width保持一致
- // 未生成pdf的html页面高度
- let leftHeight = contentHeight
- // 页面偏移
- let position = 0
- // a4纸的尺寸[595,842],单位像素,html页面生成的canvas在pdf中图片的宽高
- const imgWidth = this.A4_WIDTH - 10 // -10为了页面有右边距
- const imgHeight = (this.A4_WIDTH / contentWidth) * contentHeight
- const pageData = canvas.toDataURL('image/jpeg', 1.0)
- const pdf = jsPDF('', 'pt', 'a4')
- // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
- // 当内容未超过pdf一页显示的范围,无需分页
- if (leftHeight < pageHeight) {
- // 在pdf.addImage(pageData, 'JPEG', 左,上,宽度,高度)设置在pdf中显示;
- pdf.addImage(pageData, 'JPEG', 5, 0, imgWidth, imgHeight)
- // pdf.addImage(pageData, 'JPEG', 20, 40, imgWidth, imgHeight);
- } else {
- // 分页
- while (leftHeight > 0) {
- pdf.addImage(pageData, 'JPEG', 5, position, imgWidth, imgHeight)
- leftHeight -= pageHeight
- position -= this.A4_HEIGHT
- // 避免添加空白页
- if (leftHeight > 0) {
- pdf.addPage()
- }
- }
- }
- pdf.save(pdfFileName + '.pdf', { returnPromise: true }).then(() => {
- // 去除添加的空div 防止页面混乱
- const doms = document.querySelectorAll('.emptyDiv')
- for (let i = 0; i < doms.length; i++) {
- doms[i].remove()
- }
- })
- this.ele.style.height = ''
- resolve()
- })
- }
- //此方法是防止(图表之类)内容因为A4纸张问题被截断
- async outPutPdfFn(pdfFileName,id,practiceName) {
- return new Promise((resolve, reject) => {
- this.ele.style.height = 'initial';
- pdfFileName ? this.pdfFileName = pdfFileName : null
- const target = this.ele;
- const pageHeight = target.scrollWidth / this.A4_WIDTH * this.A4_HEIGHT;
- // 获取分割dom,此处为class类名为item的dom
- // const domList = document.getElementsByClassName(this.splitClassName);
- const domList=this.ele.getElementsByClassName(this.splitClassName);
- // 进行分割操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
- let pageNum = 1 // pdf页数
-
- for (let i = 0; i < domList.length; i++) {
- const eleBounding = this.ele.getBoundingClientRect();
- const node = domList[i];
- const bound = node.getBoundingClientRect();
- const offset2Ele = bound.top - eleBounding.top;
-
- const currentPage = Math.ceil((bound.bottom - eleBounding.top) / pageHeight) // 当前元素应该在哪一页
- const divParent = domList[i].parentNode // 获取该div的父节点
- let emptyHeight=pageHeight * pageNum - offset2Ele;
-
- if (pageNum < currentPage) {
- // console.log(i,pageNum,currentPage,offset2Ele,emptyHeight);
- pageNum++
- if(emptyHeight>=70){//空白节点高度如果大于页脚高度才添加空白节点
- divParent.insertBefore(this.createEmptyNode(emptyHeight), node);
- divParent.insertBefore(this.creatFooterNode(pageNum,id), node);
- divParent.insertBefore(this.createHeaderNode(practiceName), node)
- }else{// /空白节点高度如果小于页脚高度,就要把上一个节点挤下去
- emptyHeight=emptyHeight<0?0:emptyHeight;
- let j=1;
- emptyHeight=emptyHeight+domList[i-j].offsetHeight;
- while(emptyHeight<70){
- j++;
- emptyHeight=emptyHeight+domList[i-j].offsetHeight;
- }
- let newDivParent= domList[i-j].parentNode;
- newDivParent.insertBefore(this.createEmptyNode(emptyHeight), domList[i-j]);
- newDivParent.insertBefore(this.creatFooterNode(pageNum,id), domList[i-j]);
- newDivParent.insertBefore(this.createHeaderNode(practiceName), domList[i-j])
- }
- }
- }
- // 给最后一页添加页脚
- let lastNode=domList[domList.length-1].getBoundingClientRect();
- const eleBounding1 = this.ele.getBoundingClientRect();
- let lastEmptyH=pageHeight * pageNum - lastNode.bottom - eleBounding1.top;
- if(lastEmptyH>=70){
- pageNum++;
- this.ele.appendChild(this.createEmptyNode(lastEmptyH-15));
- let lastFooterNode=this.creatFooterNode(pageNum,id);
- lastFooterNode.style.paddingBottom=0+'px';
- // console.log(lastFooterNode);
- this.ele.appendChild(lastFooterNode);
- }else{//最后一页放不下页脚,那么把上一个节点挤下去
- lastEmptyH=lastEmptyH<0?0:lastEmptyH;
- let k=0;
- let i=domList.length-1;
-
- lastEmptyH=lastEmptyH+domList[i].offsetHeight;
- while(lastEmptyH<70){
- k++;
- lastEmptyH=lastEmptyH+domList[i-k].offsetHeight;
- }
- let lastDivParent=domList[i-k].parentNode;
- pageNum++;
- lastDivParent.insertBefore(this.createEmptyNode(lastEmptyH), domList[i-k]);
- lastDivParent.insertBefore(this.creatFooterNode(pageNum+1,id), domList[i-k]);
- lastDivParent.insertBefore(this.createHeaderNode(practiceName), domList[i-k]);
-
- // 挤下去了一个节点后为新开的一页添加页脚,前面新添加了空白页、页脚、页眉,所以需要重新获取ele的高度
- const eleBounding2 = this.ele.getBoundingClientRect();
- const lastNode2=domList[domList.length-1].getBoundingClientRect();
- let emptyHeight2=pageHeight * pageNum - lastNode2.bottom - eleBounding2.top;
-
- this.ele.appendChild(this.createEmptyNode(emptyHeight2-15));
- pageNum++;
- let lastFooterNode=this.creatFooterNode(pageNum,id);
- lastFooterNode.style.paddingBottom=0+'px';
- this.ele.appendChild(lastFooterNode);
- }
- // 为页脚添加总页数
- let allPageNodeList=Array.from(this.ele.getElementsByClassName('allPageNode')) ;
- allPageNodeList.forEach(element => {
- element.innerHTML=pageNum-1;
- });
-
- // 异步函数,导出成功后处理交互
- this.getPDF(resolve, reject)
- })
- }
-
- //创建页脚,内容根据需求改变
- creatFooterNode(pageNum,id=null){
- // const target = this.ele;
- const pageNode = document.createElement('div');
- pageNode.className = 'flexRowCenterColCenter footerNode';
- pageNode.style.width=this.ele.scrollWidth *90%+'px';
- pageNode.style.cssText=`box-sizing: border-box;padding-top:10px;padding-bottom:50px;`;
- let str1='';
- if(!id){
- str1 = `<div style="border-top:3px solid #08B9BB;width:100%" class="flexRowCenterColCenter">${pageNum-1} /<span style='margin-left:2px' class='allPageNode'></span></div>`
- }else{
- str1 = `<div class="flexRowBetweenColCenter" style="width:100%;border-top:3px solid #08B9BB;"><span style='color:#fff'>--</span><span style='color:#fff'>${pageNum-1}</span><span style='color:#333;font-size:12px;'>ID:${id}</span></div>`
- }
- pageNode.innerHTML = str1;
- // console.log(pageNode.offsetHeight);
- return pageNode;
- }
- createEmptyNode(height){
- const emptyDiv = document.createElement('div')
- emptyDiv.className = 'emptyDiv'
- emptyDiv.style.background = 'white'
- emptyDiv.style.boxSizing='border-box'
- emptyDiv.style.height = height-50+ 'px' // +25为了在换下一页时有顶部的边距
- emptyDiv.style.width = this.ele.scrollWidth *90%+'px';
- return emptyDiv;
- }
- //创建页眉,根据实际需求改变
- createHeaderNode(practiceName){
- const newNode = document.createElement('div')
- newNode.className = 'flexRowBetweenColCenter'
- newNode.style.width = this.ele.scrollWidth *90%+'px'
- newNode.style.cssText="padding-top:0px;padding-bottom:10px;border-bottom:3px solid #08B9BB;margin-bottom:20px";
- let str=""
- if(practiceName!=""){
- str = `<div style='font-size: 18px;text-align: left;'>${practiceName}</div>`
- }else{
- str=`<img style="height:24px" src=${logoUrl}/>`
- }
- newNode.innerHTML = str
- return newNode;
- }
- }
-
- export default PdfLoader

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。