vue实现打印PDF功能 - html2Pdf.js

1,356 阅读5分钟

踩坑预警

最近更新已经三年前的事情了,将近四百多个问题还未close(重复的问题也挺多的) image.png 官网地址

安装操作

npm install --save html2pdf.js 
or
npm install --save html2pdf.js -- force
or
cnpm install --save html2pdf.js 

实现直接下载保存PDF操作

可以通过html布局好了,直接打印下载

import html2pdf from 'html2pdf.js';
let tableElement = document.querySelectorAll('table')
html2pdf().from(tableElement).save(filename);

踩坑历程

要求: 我这边是纯表格的打印操作,但是要求是分页的时候每个表格都是需要携带表头,表格内的图片需要展示

踩坑一: 网络图片无法展示

翻阅了源码开发者上别人提的Issues中发现,这玩意html2canvas处理的时候只能支持base64,虽然有人说可以配置 html2canvas: {scale: 2, useCORS: true},可以解决【但是我测试不行,开发者提议使用base64】

image.png

let imageUrl = 'https://www.baidu.com/img/flexible/logo/pc/result.png'
let image = new Image();
//解决跨域问题
image.setAttribute('crossOrigin', 'Anonymous');
image.src = imageUrl
//image.onload为异步加载
let base64Datas = []
image.onload = () => {
    var canvas = document.createElement("canvas");
    canvas.width = image.width;
    canvas.height = image.height;
    var context = canvas.getContext('2d');
    context.drawImage(image, 0, 0, image.width, image.height);
    var quality = 0.8;
    //这里的dataurl就是base64类型
    var dataURL = canvas.toDataURL("image/jpeg", quality);//使用toDataUrl将图片转换成jpeg的格式,不要把图片压缩成png,因为压缩成png后base64的字符串可能比不转换前的长!
    //数组存放图片base64
    base64Datas.push(dataURL);
    //递归执行图片url转base64        
}

踩坑二: 表头没办法在分页后的自动携带上(printJS这个插件就相当不错,可太傲娇)

这个也是我搞了好久的地方,一开始在Issues中发现添加页码的方式想法去添加表头,如

 html2pdf().from(tableElement).set(options).toPdf().get('pdf').then(function (pdf) {
    console.log(pdf)
    // paf.table({html: thead})
  }).save(filename);

,then做PDF的操作,打印PDF暴露出来的API方法中改哪个都油盐不进,一点变化都没有,放弃了,运用固定住表格里的数据高度固定,动态去插入表头进去每个数据中,实现现有表头功能

 const clonedThead = tableElement.querySelector('thead').innerHTML;
  let tbodyList = tableElement.querySelectorAll('tbody');
  console.log(tbodyList)
  tbodyList.forEach((item,index) => {
    if( index % 5 === 0 && index > 0) {
      item.insertAdjacentHTML('afterbegin', '<div style="padding-top: 20px">'+clonedThead+'</div>');
    }
  })

踩坑三: 分页出现截断内容

  1. 配置options 解决分页问题:
const options = {
    margin: 10,
    html2canvas: { 
      scale: 2 
    },
    jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait'},
    pagebreak: {  mode: ['avoid-all', 'css', 'legacy'] },
}
  1. 样式控制

page-break-inside: avoid 可以保留完整的页面

page-break-before: always 可以另起新的一页

从源码发现可以添加类 html2pdf__page-break 进行换页

//对表格内容保持完整性
.td {
    min-width: 60px;
    text-align: center;
    word-wrap: break-word;
    word-break: break-all;
    border: 1px solid #000;
}

image.png 3. 浑身解数之后发现还是会保留一点第二页的数据在第一页中,这种情况有可能是html2canvas画的canvas和设置的A4纸的大小有关了

解决: 我这边运用了插入表头的时候增加个paddingTop的高度可以解决

printJS 打印使用

挺好用的,以上的要求基本能符合需求的开发要求,可惜开源博主在Issues中说我只专注打印开发,如果需要直接下载可以用别的插件很好支持,这边就专注于打印功能。 很显然有点满足不了我多表格区分下载的实现。他这个只支持于单个pdf导出打印预览,有实现过的小伙伴欢迎评论给出兼容的解决方案【【根据HTML可解决直接下载多个PDF】】

// 与html2Pdf.js 区别在的是样式需要单独配置进插件中,无法识别html里的样式,
// 如果需要分页可以根据踩坑三中的样式控制操作,并且图片无需转成base64,
// 我要处理的图片出现跨域问题,在这个插件是不存在这个问题,直接自动处理网络图片
// 附上实现方案
setTimeout(() => {
    printJS({
      printable: 'print', // 要打印的id
      type: 'html',
      header: '',
      style: `@page{size:auto;margin: 0cm 1cm 0cm 1cm;}
      .store-img-stock {
        display:block;
        padding:20px;
        width: 260px;
        height: 260px;
      }
      .s-placeholder{ visibility: hidden; }
      @media print {
        body {
          -webkit-print-color-adjust: exact;
          -moz-print-color-adjust: exact;
          -ms-print-color-adjust: exact;
          print-color-adjust: exact;
        }
      }
      .hidden-border-bottom {
        border-bottom: none;
        border-top: none;
      }
      .thead-th {
        background-color: #333586 !important;
        color: #fff !important;
      }
      .empty-height {
        max-height: 50px;
      }
      .height-20 {
        height: 16px;
      }
      .store-table-a1 {
        margin-bottom: 20px;
        page-break-before: always;
      }
      .store-table-a1 td {
        min-width: 60px;
        text-align: center;
        word-wrap: break-word;
        word-break: break-all;
        border: 1px solid #000;
      }
      .store-table th {
        height: 35px;
        border: 1px solid #000;
      }
      .hidden-border {
        border: none !important;
      }
      ` // 去除页眉页脚
    })
  }, 500)

拓展 html2canvas 与 printJS 实现

需求: 如果我们需要只对弹窗内容做相关的打印操作,并且弹窗内容的列表是分页情况,那么我们需要结合canvas画布后实现打印列表操作。那在实现过程中结合el-dialog@opened执行弹窗打印调用

<el-dialog>
    :close-on-click-modal="false"
    width="800px"
    title="打印預覽"
    custom-class="print-dialog"
    :visible.sync="printModal"
    @opened="handleOpen"
 >
     <div id="pdfDom" ref="imageWrap" >
         <div
            v-for="(v, k) in entryList"
            :id="'pdfDom' + (Number(k) + 1)"
            :key="k"
            class="image-wrap"
          >
            <div style="flex: 1" :id="'headInfo' + (Number(k) + 1)">
              <div> Header </div>
              <div v-for="item in 100"> 内容 </div>
              <div> Footer </div>
            </div>
         <div>
     </div>
</el-dialog>
<script>
data() {
    return {
        entryList: [],
        imgmap: []
    }
}
//源数据获取拆分分页
getList() {
  const groupSize = 12; //一页12条,具体根据排版走
  getData().then(res => {
   for(let i =0 ;i < res.length ; i += groupSize) {
      entryList.push(entryListOrgin.slice(i,i+groupSize))
    }
  })
}
async handleOpen() {
  const len = this.entryList.length; //源数据拆分后执行
  for (let i = 1; i <= len; i++) {
    const cloneDom = document.querySelector("#pdfDom" + i);
    await this.getPdf(cloneDom, i, len);
  }
},
async getPdf(cloneDom, index, len) {
  const _self = this;
  await html2canvas(cloneDom, {
    allowTaint: true,
    taintTest: false,
    useCORS: true,
    width: cloneDom.offsetWidth,
    height: cloneDom.offsetHeight,
    scale: 2,
  }).then(function (canvas) {
    _self.imgmap.push(canvas.toDataURL("image/png"));
    const style = "@page {margin:0mm 0mm; size: 148mm 210mm;} "; // 去除页眉页脚,size设置打印尺寸
    if(len === index) {
      setTimeout(() => {
        printJS({
          printable: _self.imgmap, // 要打印的id
          width: cloneDom.offsetWidth,
          height: cloneDom.offsetHeight,
          type: "image",
          maxWidth: 200,
          style: style, // 去除页眉页脚
        });
      }, 200);
    }
  });
},
</script>

此处需要注意的点是printable引入的参数值,如果不在data声明的话直接push进去,会在多次执行的getpdf清空成为一个