基于jsPDF将DOM生成pdf文件

使用jspdf将DOM元素绘制成PDF文件

最近接到了一个需求,将页面绘制好的模板,绘制成pdf文件并传给后台。开始在网上文章中学到,使用html2Canvas+jspdf基本实现了。但是生成的pdf太模糊,要求重新做。经过几天,终于搞定。
现在总结一下。

基本思路是:

  • jspdf将页面元素转换成二进制流
  • 使用new Blob将二进制流转换成文件
  • 使用new FormData将文件传给后台

将DOM元素绘制成二进制流

在插件绘制的方法中,这步非常简单,只需要调用html()函数即可。但是也是最费劲的,因为插件一开始并不支持中文,后来提供了加载字体库的方法也算是解决的了。时间有限,找到字体文件都非常大,使用官方提供的方式后,更大。个人工作中的项目是基于vue2.x开发的,将文件引入后,直接提示内存溢出。后来是用接口方式,将转换后的文件传过来才解决。所以,为了项目性能,正式开发是请做好以下准备。

一个传输base64格式字体的接口

在jsPDF的官网github上克隆本地后,点击fontconverter>fontconverter.html将准备好的中文字库转换成.js文件,个人用的FZYTK.ttf(注意只支持.ttf格式的),经测试思源黑体也可以(不过转换后有40M,接口压力很大)。

将转换后的js文件中的‘var font = ’后面的base64取出后,放在一个文件后给后端开发。后端读取文件,通过接口给前端。

将字体存入本地

由于字体很大,传输不易。建议将字体存在浏览器本地。localstorage大小只有5M,所以建议使用es6新的indexDB进行存储,网上有多封装好的方法。存储和读取都非常方便。

安装jspdf

npm install jspdf –save or yarn add jspdf

准备完成,下面是代码时间。

  import JsPDF from 'jspdf';// 引入插件
  import axios from 'axios'
  import { creatUpdateStore, addDataStore, getStoreData } from './indexDB'// 引入封装好的indexDB函数,creatUpdateStore:创建,addDataStore:添加,getStoreData:读取
  let font = null;// 全局字体变量,个人是生成多个文件,用全局变量后,可以避免多次读取
  creatUpdateStore('fonts');// 创建一个indexDB库,命名为font
  const cretePDF = () => { 
  return new Promise(resolve => {
    const PDF = new JsPDF('', 'pt', 'a4')// 创建一个PDF对象,大小可以自行调整,这里用的是a4纸
    if (font === null) { // 先判断font是否有值
      getStoreData('fonts', 1).then(res => {// font没有值,在查看本地是否有。获取indexDB方法有所差异,可自行调整。
        if (res?.FZYTK) { //本地有字体
          font = res.FZYTK
          PDF.addFileToVFS('FZYTK.ttf', font)
          PDF.addFont('FZYTK.ttf', 'FZYTK', 'normal')
          PDF.setFont('FZYTK')
          resolve(PDF)
        } else { //本地无字体,通过接口获取
          axios.get('XXX').then(response => {
            font = response.data.data.replaceAll('', '')//后端读取文件可能会产生,直接替换掉。
            addDataStore('fonts', { FZYTK: font })
            PDF.addFileToVFS('FZYTK.ttf', font)
            PDF.addFont('FZYTK.ttf', 'FZYTK', 'normal')
            PDF.setFont('FZYTK')
            resolve(PDF)
          })
        }
      })
    } else {//font 有值,直接设置字体
      PDF.addFileToVFS('FZYTK.ttf', font)
      PDF.addFont('FZYTK.ttf', 'FZYTK', 'normal')
      PDF.setFont('FZYTK')
      resolve(PDF)
    }
  })
}
    // vue 全局函数。
    Vue.prototype.getNodePdf = function (nodeId) {
      loading = Loading.service({ //loading,如果需要的话
        lock: true,
        text: '正在生成pdf文件...',
        background: 'rgba(0,500,200,.5)'
      })
      return new Promise((resolve, reject) => {// 生成一个promise,包装文件生成后,在进行下一步动作
        try {
          const node = document.querySelector(`#${nodeId}`) //获取要绘制的DOM
          const clonedNode = node.cloneNode(true) // 克隆DOM,方便改变DOM的样式,不用克隆亦可
          cretePDF().then(PDF => {// PDF对象生成后,
            clonedNode.style.width = `${PDF.getPageWidth() - 20}px`// 获取PDF大小,重新这是DOM大小,使DOM都能绘制在PDF文件上面。
            PDF.html(clonedNode, {// 绘制pdf
              callback: function (doc) {
                const output = doc.output('arraybuffer')// 以流的方式返回结果。使用PDF.save('pdfName'),浏览器会将生成的PDF直接下载下来
                resolve(output) // 返回结果。
              },
              // 开始从10,10坐标开始绘制
              x: 10,
              y: 10
            })
          })
        } catch (e) {
          reject(e)
        } finally {
          loading.close()//关闭loading
        }
      })
    }
复制代码

pdf生成完成。

使用Blob将流转换成file文件

代码如下

    /**
     * @description:将bob转成file
     * @param {string} DOM的id
     * @param {name} 文件名称
     */
    getPdfFn(id, name) {
      if (id && name) {
        return new Promise(resolve => {
          this.getNodePdf(id, name).then(file => {
            const pdfFile = new Blob([file], { type: 'application/pdf' })
            const files = new window.File([pdfFile], `${name}.pdf`, { type: 'application/pdf' })
            resolve(files)
          })
        })
      }
    }
复制代码

通过接口传输给后台

   const _that = this
   const formData = new FormData()
    files.forEach((file, index) => { //files 为生成的pdf集合
      formData.append(`files[${index}]`, file)
    })
    this.$axios.post('XXX', formData)
      .then(function (res) {
        if (res.data.code === 1000) {
          _that.$message.success('提交成功')
          _that.isEdit = false
        } else {
          _that.$message.error(res.data.msg)
        }
      })
复制代码

个人loading和message用的element-ui提供的,使用其他框架可自行调整。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享