当前位置:   article > 正文

前端在vue2框架中导出PDF_vue 前端导出pdf

vue 前端导出pdf

 1.需求

        导出具有页眉页脚、页码的Pdf,并且解决Pdf分割的问题。

2.实现思路

        该需求主要的难点在于分页的时候容易出现分割问题,并且要将页眉页脚加进去。实现的大概思路:

        (1)先使用jsPDF、html2canvas将页面可以导出;

        (2)第一页的页眉在html代码中添加,第二页的页眉在分页的时候动态添加;

        (3)在会出现分割问题的节点上添加一个分割类名,这个节点必须要有父元素,因为如果是在这个节点分割的话,需要判断这个节点距离底部的距离,然后在这个节点后面添加页脚、页码;

        (4)获取在分页节点的元素,domList是根据分割类名获取的节点,循环这些节点,判断它是否处于分页的位置;

  1. for (let i = 0; i < domList.length; i++) {
  2. const eleBounding = this.ele.getBoundingClientRect();
  3. const node = domList[i];
  4. const bound = node.getBoundingClientRect();
  5. const offset2Ele = bound.top - eleBounding.top;
  6. const currentPage = Math.ceil((bound.bottom - eleBounding.top) /pageHeight)
  7. }

       (5)定义一个创建空白元素、页眉、页脚的函数

       (6)添加页眉页脚,由于页脚是在A4纸的底部,所以在添加页脚之前需要获取分页节点距离A4纸底部的空白高度,如果这个空白高度大于页脚高度,那么先添加一个空白元素,空白元素的高度等于空白高度减去页脚高度,最后再依次添加页脚和页眉。如果空白高度小于页脚高度,那么需要依次向上累加分割节点的高度,至到高度大于页脚高度;

  1. const divParent = domList[i].parentNode // 获取该分割节点的父节点
  2. let emptyHeight=pageHeight * pageNum - offset2Ele;
  3. if (pageNum < currentPage) {
  4. // console.log(i,pageNum,currentPage,offset2Ele,emptyHeight);
  5. pageNum++
  6. if(emptyHeight>=70){//空白节点高度如果大于页脚高度才添加空白节点
  7. divParent.insertBefore(this.createEmptyNode(emptyHeight), node);
  8. divParent.insertBefore(this.creatFooterNode(pageNum,id), node);
  9. divParent.insertBefore(this.createHeaderNode(practiceName), node)
  10. }else{// /空白节点高度如果小于页脚高度,就要把上一个节点挤下去
  11. emptyHeight=emptyHeight<0?0:emptyHeight;
  12. let j=1;
  13. emptyHeight=emptyHeight+domList[i-j].offsetHeight;
  14. while(emptyHeight<70){
  15. j++;
  16. emptyHeight=emptyHeight+domList[i-j].offsetHeight;
  17. }
  18. let newDivParent= domList[i-j].parentNode;
  19. newDivParent.insertBefore(this.createEmptyNode(emptyHeight), domList[i-j]);
  20. newDivParent.insertBefore(this.creatFooterNode(pageNum,id), domList[i-j]);
  21. newDivParent.insertBefore(this.createHeaderNode(practiceName), domList[i-j])
  22. }
  23. }

        (7)在分割节点循环完成之后根据第(6)条思路为最后一张A4纸添加页脚。并且计算出总的A4纸页数。

3.踩坑点

        (1)分割的节点一定要有父元素不然找不到父元素要报错!

        (2)需要导出的html页面的宽度要定死,宽度越大,导出的字体越小,高度定成100%。

        (3)将要导出的页面封装成一个组件,引用组件的时候设置以下样式。

  1. width: 900px;
  2. height: 100%;
  3. position: fixed;
  4. top:0;
  5. left: 0;
  6. z-index:-1000"

4. 使用自己封装的pdf.js

  1. import PdfLoader from "@/libs/pdf.js";
  2. const pdf = document.getElementById('sportsTreatRecord');
  3. let fileName=`导出的pdf文件名`
  4. setTimeout(() => {
  5. let pdfReq=new PdfLoader(pdf,fileName,'splitClassName');
  6. pdfReq.outPutPdfFn(fileName,null,this.practiceName);
  7. }, 3000);

5.实现代码

  1. import jsPDF from 'jspdf'
  2. import html2canvas from 'html2canvas'
  3. import logoUrl from '../assets/image/newLogo.png'
  4. /*
  5. * 使用说明
  6. * ele:需要导出pdf的容器元素(dom节点)
  7. * pdfFileName: 导出文件的名字 通过调用outPutPdfFn方法也可传参数改变
  8. * splitClassName: 避免分段截断的类名 当pdf有多页时需要传入此参数 , 避免pdf分页时截断元素 如表格<tr class="itemClass"></tr>
  9. * 调用方式 先 let pdf = new PdfLoader(ele, 'pdf' ,'itemClass');
  10. * 若想改变pdf名称 pdf.outPutPdfFn(fileName); outPutPdfFn方法返回一个promise 可以使用then方法处理pdf生成后的逻辑
  11. * */
  12. class PdfLoader {
  13. constructor(ele, pdfFileName, splitClassName) {
  14. this.ele = ele
  15. this.pdfFileName = pdfFileName
  16. this.splitClassName = splitClassName
  17. this.A4_WIDTH = 595.28
  18. this.A4_HEIGHT = 841.89
  19. }
  20. async getPDF(resolve) {
  21. const ele = this.ele
  22. const pdfFileName = this.pdfFileName
  23. const eleW = ele.offsetWidth// 获得该容器的宽
  24. const eleH = ele.scrollHeight// 获得该容器的高
  25. const eleOffsetTop = ele.offsetTop// 获得该容器到文档顶部的距离
  26. const eleOffsetLeft = ele.offsetLeft// 获得该容器到文档最左的距离
  27. const canvas = document.createElement('canvas')
  28. let abs = 0
  29. const win_in = document.documentElement.clientWidth || document.body.clientWidth// 获得当前可视窗口的宽度(不包含滚动条)
  30. const win_out = window.innerWidth// 获得当前窗口的宽度(包含滚动条)
  31. if (win_out > win_in) {
  32. abs = (win_out - win_in) / 2// 获得滚动条宽度的一半
  33. }
  34. canvas.width = eleW * 2// 将画布宽&&高放大两倍
  35. canvas.height = eleH * 2
  36. const context = canvas.getContext('2d')
  37. context.scale(2, 2) // 增强图片清晰度
  38. context.translate(-eleOffsetLeft - abs, -eleOffsetTop)
  39. html2canvas(ele, {
  40. useCORS: true// 允许canvas画布内可以跨域请求外部链接图片, 允许跨域请求。
  41. }).then(async canvas => {
  42. const contentWidth = canvas.width
  43. const contentHeight = canvas.height
  44. // 一页pdf显示html页面生成的canvas高度;
  45. const pageHeight = (contentWidth / this.A4_WIDTH) * this.A4_HEIGHT // 这样写的目的在于保持宽高比例一致 pageHeight/canvas.width = a4纸高度/a4纸宽度// 宽度和canvas.width保持一致
  46. // 未生成pdf的html页面高度
  47. let leftHeight = contentHeight
  48. // 页面偏移
  49. let position = 0
  50. // a4纸的尺寸[595,842],单位像素,html页面生成的canvas在pdf中图片的宽高
  51. const imgWidth = this.A4_WIDTH - 10 // -10为了页面有右边距
  52. const imgHeight = (this.A4_WIDTH / contentWidth) * contentHeight
  53. const pageData = canvas.toDataURL('image/jpeg', 1.0)
  54. const pdf = jsPDF('', 'pt', 'a4')
  55. // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
  56. // 当内容未超过pdf一页显示的范围,无需分页
  57. if (leftHeight < pageHeight) {
  58. // 在pdf.addImage(pageData, 'JPEG', 左,上,宽度,高度)设置在pdf中显示;
  59. pdf.addImage(pageData, 'JPEG', 5, 0, imgWidth, imgHeight)
  60. // pdf.addImage(pageData, 'JPEG', 20, 40, imgWidth, imgHeight);
  61. } else {
  62. // 分页
  63. while (leftHeight > 0) {
  64. pdf.addImage(pageData, 'JPEG', 5, position, imgWidth, imgHeight)
  65. leftHeight -= pageHeight
  66. position -= this.A4_HEIGHT
  67. // 避免添加空白页
  68. if (leftHeight > 0) {
  69. pdf.addPage()
  70. }
  71. }
  72. }
  73. pdf.save(pdfFileName + '.pdf', { returnPromise: true }).then(() => {
  74. // 去除添加的空div 防止页面混乱
  75. const doms = document.querySelectorAll('.emptyDiv')
  76. for (let i = 0; i < doms.length; i++) {
  77. doms[i].remove()
  78. }
  79. })
  80. this.ele.style.height = ''
  81. resolve()
  82. })
  83. }
  84. //此方法是防止(图表之类)内容因为A4纸张问题被截断
  85. async outPutPdfFn(pdfFileName,id,practiceName) {
  86. return new Promise((resolve, reject) => {
  87. this.ele.style.height = 'initial';
  88. pdfFileName ? this.pdfFileName = pdfFileName : null
  89. const target = this.ele;
  90. const pageHeight = target.scrollWidth / this.A4_WIDTH * this.A4_HEIGHT;
  91. // 获取分割dom,此处为class类名为item的dom
  92. // const domList = document.getElementsByClassName(this.splitClassName);
  93. const domList=this.ele.getElementsByClassName(this.splitClassName);
  94. // 进行分割操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
  95. let pageNum = 1 // pdf页数
  96. for (let i = 0; i < domList.length; i++) {
  97. const eleBounding = this.ele.getBoundingClientRect();
  98. const node = domList[i];
  99. const bound = node.getBoundingClientRect();
  100. const offset2Ele = bound.top - eleBounding.top;
  101. const currentPage = Math.ceil((bound.bottom - eleBounding.top) / pageHeight) // 当前元素应该在哪一页
  102. const divParent = domList[i].parentNode // 获取该div的父节点
  103. let emptyHeight=pageHeight * pageNum - offset2Ele;
  104. if (pageNum < currentPage) {
  105. // console.log(i,pageNum,currentPage,offset2Ele,emptyHeight);
  106. pageNum++
  107. if(emptyHeight>=70){//空白节点高度如果大于页脚高度才添加空白节点
  108. divParent.insertBefore(this.createEmptyNode(emptyHeight), node);
  109. divParent.insertBefore(this.creatFooterNode(pageNum,id), node);
  110. divParent.insertBefore(this.createHeaderNode(practiceName), node)
  111. }else{// /空白节点高度如果小于页脚高度,就要把上一个节点挤下去
  112. emptyHeight=emptyHeight<0?0:emptyHeight;
  113. let j=1;
  114. emptyHeight=emptyHeight+domList[i-j].offsetHeight;
  115. while(emptyHeight<70){
  116. j++;
  117. emptyHeight=emptyHeight+domList[i-j].offsetHeight;
  118. }
  119. let newDivParent= domList[i-j].parentNode;
  120. newDivParent.insertBefore(this.createEmptyNode(emptyHeight), domList[i-j]);
  121. newDivParent.insertBefore(this.creatFooterNode(pageNum,id), domList[i-j]);
  122. newDivParent.insertBefore(this.createHeaderNode(practiceName), domList[i-j])
  123. }
  124. }
  125. }
  126. // 给最后一页添加页脚
  127. let lastNode=domList[domList.length-1].getBoundingClientRect();
  128. const eleBounding1 = this.ele.getBoundingClientRect();
  129. let lastEmptyH=pageHeight * pageNum - lastNode.bottom - eleBounding1.top;
  130. if(lastEmptyH>=70){
  131. pageNum++;
  132. this.ele.appendChild(this.createEmptyNode(lastEmptyH-15));
  133. let lastFooterNode=this.creatFooterNode(pageNum,id);
  134. lastFooterNode.style.paddingBottom=0+'px';
  135. // console.log(lastFooterNode);
  136. this.ele.appendChild(lastFooterNode);
  137. }else{//最后一页放不下页脚,那么把上一个节点挤下去
  138. lastEmptyH=lastEmptyH<0?0:lastEmptyH;
  139. let k=0;
  140. let i=domList.length-1;
  141. lastEmptyH=lastEmptyH+domList[i].offsetHeight;
  142. while(lastEmptyH<70){
  143. k++;
  144. lastEmptyH=lastEmptyH+domList[i-k].offsetHeight;
  145. }
  146. let lastDivParent=domList[i-k].parentNode;
  147. pageNum++;
  148. lastDivParent.insertBefore(this.createEmptyNode(lastEmptyH), domList[i-k]);
  149. lastDivParent.insertBefore(this.creatFooterNode(pageNum+1,id), domList[i-k]);
  150. lastDivParent.insertBefore(this.createHeaderNode(practiceName), domList[i-k]);
  151. // 挤下去了一个节点后为新开的一页添加页脚,前面新添加了空白页、页脚、页眉,所以需要重新获取ele的高度
  152. const eleBounding2 = this.ele.getBoundingClientRect();
  153. const lastNode2=domList[domList.length-1].getBoundingClientRect();
  154. let emptyHeight2=pageHeight * pageNum - lastNode2.bottom - eleBounding2.top;
  155. this.ele.appendChild(this.createEmptyNode(emptyHeight2-15));
  156. pageNum++;
  157. let lastFooterNode=this.creatFooterNode(pageNum,id);
  158. lastFooterNode.style.paddingBottom=0+'px';
  159. this.ele.appendChild(lastFooterNode);
  160. }
  161. // 为页脚添加总页数
  162. let allPageNodeList=Array.from(this.ele.getElementsByClassName('allPageNode')) ;
  163. allPageNodeList.forEach(element => {
  164. element.innerHTML=pageNum-1;
  165. });
  166. // 异步函数,导出成功后处理交互
  167. this.getPDF(resolve, reject)
  168. })
  169. }
  170. //创建页脚,内容根据需求改变
  171. creatFooterNode(pageNum,id=null){
  172. // const target = this.ele;
  173. const pageNode = document.createElement('div');
  174. pageNode.className = 'flexRowCenterColCenter footerNode';
  175. pageNode.style.width=this.ele.scrollWidth *90%+'px';
  176. pageNode.style.cssText=`box-sizing: border-box;padding-top:10px;padding-bottom:50px;`;
  177. let str1='';
  178. if(!id){
  179. str1 = `<div style="border-top:3px solid #08B9BB;width:100%" class="flexRowCenterColCenter">${pageNum-1} /<span style='margin-left:2px' class='allPageNode'></span></div>`
  180. }else{
  181. 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>`
  182. }
  183. pageNode.innerHTML = str1;
  184. // console.log(pageNode.offsetHeight);
  185. return pageNode;
  186. }
  187. createEmptyNode(height){
  188. const emptyDiv = document.createElement('div')
  189. emptyDiv.className = 'emptyDiv'
  190. emptyDiv.style.background = 'white'
  191. emptyDiv.style.boxSizing='border-box'
  192. emptyDiv.style.height = height-50+ 'px' // +25为了在换下一页时有顶部的边距
  193. emptyDiv.style.width = this.ele.scrollWidth *90%+'px';
  194. return emptyDiv;
  195. }
  196. //创建页眉,根据实际需求改变
  197. createHeaderNode(practiceName){
  198. const newNode = document.createElement('div')
  199. newNode.className = 'flexRowBetweenColCenter'
  200. newNode.style.width = this.ele.scrollWidth *90%+'px'
  201. newNode.style.cssText="padding-top:0px;padding-bottom:10px;border-bottom:3px solid #08B9BB;margin-bottom:20px";
  202. let str=""
  203. if(practiceName!=""){
  204. str = `<div style='font-size: 18px;text-align: left;'>${practiceName}</div>`
  205. }else{
  206. str=`<img style="height:24px" src=${logoUrl}/>`
  207. }
  208. newNode.innerHTML = str
  209. return newNode;
  210. }
  211. }
  212. export default PdfLoader

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/880309
推荐阅读
相关标签
  

闽ICP备14008679号