当前位置:   article > 正文

vue导出PDF(vue2,插件:pdfmake ,表格)_vue 导出pdf

vue 导出pdf

:文章为记录为主,想直接看pdfmake 打印的请划到最下面部分

需求

页面有一个表格,之前导出excel 表(用XLSX和XLSXS制作了样式之类的表格),现在需要制作带样式的pdf,而且格式如下,并且最上面的三行需要在每一页的顶部出现作为表头

效果图,(换页前面三行也会有)

script 要打印的盒子:ref="printSection" ,这个表格是用 el-table 来写的,而且表格中存在嵌套children,一共嵌套了3层

如上图:大写数字(一、二)是第一层,附和大写字母(A、B)是第二层,其他(没有序号的的行)是第三层

  1. <div id="pdfDom" ref="printSection">
  2. <el-table
  3. empty-text="暂无报告"
  4. v-loading="loading"
  5. :data="staffJieyuList"
  6. @selection-change="handleSelectionChange"
  7. default-expand-all
  8. row-key="row"
  9. :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
  10. >
  11. <el-table-column align="center" :label="subTitle">
  12. <el-table-column :label="searchInformation.lysuoName">
  13. <el-table-column prop="id" align="center" label="序号">
  14. </el-table-column>
  15. <el-table-column prop="name" align="center" label="项目">
  16. </el-table-column>
  17. <el-table-column prop="amount" align="center" label="金额">
  18. </el-table-column>
  19. <el-table-column prop="contractNum" align="center" label="备注">
  20. </el-table-column>
  21. </el-table-column>
  22. </el-table-column>
  23. </el-table>
  24. </div>

上面表格data对应的staffJieyuList数据的格式如下:

  1. staffJieyuList = [ //这里是第一层数组,里面数据是对象格式
  2. {
  3. id: "一",
  4. row: 1,
  5. name: "业务收入",
  6. amount: "",
  7. contractNum: "",
  8. //这里是第二层数组,里面数据是对象格式
  9. children: [
  10. {
  11. id: "",
  12. row: 101,
  13. name: "本月合计",
  14. amount: data.income,
  15. },
  16. {
  17. id: "",
  18. row: 102,
  19. name: "本年累计",
  20. amount: data.incomeYear,
  21. },
  22. {
  23. id: "附",
  24. row: 103,
  25. name: "累计本年未收款明细",
  26. amount: "",
  27. //这里是第三层数组,里面数据是对象格式
  28. children: data.incomeUnpaidList.map((ele, index) => {
  29. let i = {
  30. id: "",
  31. row: "104" + index,
  32. name: ele.fapiaoName + "(占比:" + ele.portion + ")",
  33. amount: ele.amount,
  34. };
  35. return i;
  36. }),
  37. },
  38. ],
  39. },
  40. 第二个数组...
  41. ]

我要打印的主要是四列:

序号id 、项目name、金额amount、备注contractNum(打印出来的看最上面的图)

一.window.print();

打印可以直接调用:window.print(); 但是这里只调用了一次,取消打印之后不再弹出,而且打印的内容也不符合我的要求

  1. printDiv3() {
  2. console.log("触发---");
  3. const printContents = this.$refs.printSection.innerHTML;
  4. const originalContents = document.body.innerHTML;
  5. document.body.innerHTML = printContents;
  6. window.print();
  7. this.$nextTick(() => {
  8. document.body.innerHTML = originalContents;
  9. });
  10. }

效果图:

二.用 iframe 来打印

  1. printDiv2() {
  2. const printContent = document.querySelector("#pdfDom").innerHTML;
  3. const iframe = document.createElement("iframe");
  4. iframe.setAttribute("style", "position: absolute; width: 0; height: 0;");
  5. document.body.appendChild(iframe);
  6. const iframeDoc = iframe.contentWindow.document;
  7. // 设置打印展示方式 - 横向展示
  8. iframeDoc.write('<style media="print">@page {size: landscape;}</style>');
  9. // 向 iframe 中注入 printContent 样式
  10. iframeDoc.write(
  11. `<link href="./print.css" media="print" rel="stylesheet" />`
  12. );
  13. // 写入内容
  14. iframeDoc.write("<div>" + printContent + "</div>");
  15. setTimeout(function () {
  16. iframe.contentWindow.print();
  17. document.body.removeChild(iframe);
  18. }, 50);
  19. },

样式没研究,打印出来的效果图如下,连边框线也没加,更别说需要算哪里的背景色,字体居中和靠左了

 三.JsPDF 插件

用 JsPDF 插件来导出pdf (配合插件html2canvas) 导出的第二页也会被截断,样式我也没有研究,感兴趣的朋友可以研究下,文件:htmlToPdf.js

  1. // 导出页面为PDF格式
  2. import html2Canvas from 'html2canvas'
  3. import JsPDF from 'jspdf'
  4. export default {
  5. install(Vue, options) {
  6. Vue.prototype.getPdf = function (flieName) {
  7. // 当下载pdf时,若不在页面顶部会造成PDF样式不对,所以先回到页面顶部再下载
  8. let top = document.getElementById('pdfDom');
  9. if (top != null) {
  10. top.scrollIntoView();
  11. top = null;
  12. }
  13. let title = flieName;
  14. html2Canvas(document.querySelector('#pdfDom'), {
  15. allowTaint: true
  16. }).then(function (canvas) {
  17. // 获取canvas画布的宽高
  18. let contentWidth = canvas.width;
  19. let contentHeight = canvas.height;
  20. // 一页pdf显示html页面生成的canvas高度;
  21. let pageHeight = contentWidth / 841.89 * 592.28;
  22. // 未生成pdf的html页面高度
  23. let leftHeight = contentHeight;
  24. // 页面偏移
  25. let position = 0;
  26. // html页面生成的canvas在pdf中图片的宽高(本例为:横向a4纸[841.89,592.28],纵向需调换尺寸)
  27. let imgWidth = 841.89;
  28. let imgHeight = 841.89 / contentWidth * contentHeight;
  29. // console.log('pdf宽-高-pageHeight--imgHeight',contentWidth,contentHeight,pageHeight,imgHeight);
  30. let pageData = canvas.toDataURL('image/jpeg', 1.0);
  31. let PDF = new JsPDF('l', 'pt', 'a4');
  32. // 两个高度需要区分: 一个是html页面的实际高度,和生成pdf的页面高度
  33. // 当内容未超过pdf一页显示的范围,无需分页
  34. if (leftHeight < pageHeight) {
  35. PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
  36. } else {
  37. while (leftHeight > 0) {
  38. PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
  39. leftHeight -= pageHeight;
  40. position -= 592.28;
  41. // 避免添加空白页
  42. if (leftHeight > 0) {
  43. PDF.addPage();
  44. }
  45. }
  46. }
  47. PDF.save(title + '.pdf')
  48. })
  49. }
  50. }
  51. }

引入和使用(这里是全局注册了的)

main.js

  1. import htmlToPdf from '@/utils/htmlToPdf';
  2. Vue.use(htmlToPdf);

 页面使用该组件:

  1. <el-col :span="1.5">
  2. <el-button type="primary" plain icon="" size="mini" @click="printDiv" >打印</el-button>
  3. </el-col>
  4. printDiv() {
  5. this.$nextTick(() => {
  6. this.getPdf(this.exportPDFtitle);
  7. });
  8. },

四. pdfmake 导出表格

pdfMake 文档:

先解决下载安装问题:(版本:"pdfmake": "^0.2.10",我是直接下载默认最新的版本)

npm install pdfmake  --save   

下载字体和导入字体:

找到 node_modules 的pdfmake (下载的插件) 文件,新建 examples/fonts文件夹,导入需要的字体,这里的字体需要去找对应的字体文件,我的字体是本机电脑找的,我在我的C盘找到一些,这里用的是 仿宋

把这个文件复制,下载,放到下面examples/fonts文件夹下,复制出来的名字是大写,我改为:simfang.ttf 小写

控制台定位到pdfmake文件夹下,运行命令:node build-vfs.js "./examples/fonts"

这样就算完成了(可以导入多种字体,我这里没有字体要求,只用了仿宋)

前置问题和解决方案:pdfmake 要求的表格的内容传入需要时一个个的对象数组,如下图:(红色框是我完成需求用到的一些属性)

要解决的问题:

1). 之前嵌套的数组(树结构)要导出为一维数组(我发现导出的表格只需要4列,而且是固定的,只需要给每一条的数据固定4个属性就可以,表头也可以单独写,非常方便)

下面是嵌套的数组转为一维数组的代码(使用递归)

  1. //嵌套的多维数组 生成 一维数组
  2. flattenData(staffJieyuList) {
  3. // 初始化一维数组
  4. const flattenedData = [];
  5. // 递归函数,处理对象及其子数组
  6. function processObject(obj) {
  7. const flattenedObj = {
  8. id: obj.id || "", // 如果 id 不存在,则用空字符串填充
  9. name: obj.name || "", // 如果 name 不存在,则用空字符串填充
  10. amount: obj.amount || "", // 如果 amount 不存在,则用空字符串填充
  11. contractNum: obj.contractNum || "", // 如果 contractNum 不存在,则用空字符串填充
  12. };
  13. // 将当前对象添加到一维数组中
  14. flattenedData.push(flattenedObj);
  15. // 处理子数组
  16. if (obj.children && obj.children.length > 0) {
  17. obj.children.forEach((child) => {
  18. processObject(child);
  19. });
  20. }
  21. }
  22. // 遍历主列表中的每个对象
  23. staffJieyuList.forEach((item) => {
  24. processObject(item);
  25. });
  26. // 返回转换后的一维数组
  27. return flattenedData;
  28. },

当然上面的代码可以拆分为两个函数:(函数都在methods: {} 中)

  1. // 辅助函数,处理对象及其子数组
  2. processObject(obj, flattenedData) {
  3. const flattenedObj = {
  4. id: obj.id || "",
  5. name: obj.name || "",
  6. amount: obj.amount || "",
  7. contractNum: obj.contractNum || "",
  8. };
  9. // 将当前对象添加到一维数组中
  10. flattenedData.push(flattenedObj);
  11. // 处理子数组
  12. if (obj.children && obj.children.length > 0) {
  13. obj.children.forEach((child) => {
  14. this.processObject(child, flattenedData);
  15. });
  16. }
  17. }
  18. // 调用辅助函数来生成一维数组
  19. flattenDataWithHelper(staffJieyuList) {
  20. // 初始化一维数组
  21. const flattenedData = [];
  22. // 遍历主列表中的每个对象
  23. staffJieyuList.forEach((item) => {
  24. this.processObject(item, flattenedData);
  25. });
  26. // 返回转换后的一维数组
  27. return flattenedData;
  28. }

2).表头是3行,直接用下图的属性 headerRows: 3, 表头占一行,可以直接:colSpan: 4,其余的列用{} 来占位

[{ text: 我是表头内容, colSpan: 4, }, {}, {}, {}],

3).颜色,背景色,文字显示位置,需要 alignment: 'center', fillColor: '#91aadf' 等属性

页面使用:(全局跟上面一样引入,这里是单独页面使用)

  1. import pdfMake from "pdfmake/build/pdfmake";
  2. import pdfFonts from "pdfmake/build/vfs_fonts";
  3. pdfMake.fonts = {
  4. simfang: {
  5. normal: "simfang.ttf",
  6. bold: "simfang.ttf",
  7. italics: "simfang.ttf",
  8. bolditalics: "simfang.ttf",
  9. },
  10. };
  11. pdfMake.vfs = pdfFonts.pdfMake.vfs;

打印按钮:

  1. <el-col :span="1.5">
  2. <el-button type="primary" plain icon="" size="mini" @click="printDiv" >打印PDF</el-button>
  3. </el-col>

下面附上完整的pdfmake 导出表格的代码:

  1. printDiv() {
  2. // 调用函数,获得所需的一维数组
  3. const flattenedArray = this.flattenData(this.staffJieyuList);
  4. console.log("flattenedArray----", flattenedArray);
  5. var fileName = this.exportPDFtitle + ".pdf";
  6. // 匹配大写字母、大写阿拉伯数字或者特定的中文字符的正则表达式
  7. const pattern = /^(附|[A-Z]|[一二三四五六七八九十])+$/;
  8. //除了1-3行,还有下面匹配的这些 文字居中,项目这里列的其他内容要往左
  9. const pattern2 = /^([一二三四五六七八九十])+$/
  10. const docDefinition = {
  11. content: [
  12. {
  13. table: {
  14. headerRows: 3,
  15. widths: [50, "auto", 60, "auto"],
  16. body: [
  17. [{ text: this.subTitle, colSpan: 4, alignment: 'center', fillColor: '#91aadf'}, {}, {}, {}],
  18. [{ text: this.searchInformation.lysuoName, colSpan: 4, alignment: 'left', fillColor: '#91aadf'}, {}, {}, {}],
  19. [
  20. { text: "序号", alignment: 'center', fillColor: '#91aadf' },
  21. { text: "项目", alignment: 'center', fillColor: '#91aadf' },
  22. { text: "金额", alignment: 'center', fillColor: '#91aadf' },
  23. { text: "备注", alignment: 'center', fillColor: '#91aadf' },
  24. ],
  25. ...flattenedArray.map((item) => {
  26. const backgroundColor =
  27. pattern.test(item.id) ? "#d9e1f4" : "#ffffff";
  28. const fontPosition =
  29. pattern2.test(item.id) ? "center" : "left"
  30. return [
  31. { text: item.id, fillColor: backgroundColor, alignment: 'center', },
  32. { text: item.name, fillColor: backgroundColor, alignment: fontPosition, },
  33. { text: item.amount, fillColor: backgroundColor, alignment: 'center', },
  34. { text: item.contractNum, fillColor: backgroundColor, alignment: 'center', },
  35. ];
  36. }),
  37. ],
  38. },
  39. },
  40. ],
  41. defaultStyle: {
  42. font: "simfang",
  43. },
  44. };
  45. pdfMake.createPdf(docDefinition).download(fileName);
  46. },

效果:

 

小结:用pdfmake插件制作pdf确实方便,而且样式等配置也比较齐全,(上面写的不好的地方欢欢迎指正,相互讨论学习)纯前端用XLSX和XLSXS制作导出excel 之前没有记录,有空再做记录

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号