likes
comments
collection
share

Vue项目中使用html2pdf.js打印长页面

作者站长头像
站长
· 阅读数 26

Vue项目中使用html2pdf.js打印长页面

1,安装依赖

npm install html2pdf.js
pnpm add html2pdf.js

2,代码示例

html2canvas的配置项可查看html2canvas.hertzen.com/configurati…

jsPDF的配置项可查看rawgit.com/MrRio/jsPDF…

html2pdf.js的配置项可在官方仓库的readme文档查看github.com/eKoopmans/h…

import html2pdf from 'html2pdf.js'
import type { jsPDF, jsPDFOptions } from 'jspdf'
import type { Options as Html2CanvasOptions } from 'html2canvas'
type PDFOptions = { name: string; cover?: HTMLDivElement }
export function exportHTMLToPDF2(pages: HTMLDivElement, options: PDFOptions = { name: '模版' }) {
  const { name, cover } = options || {}

  const opt = {
    margin: 10,
    filename: name + '.pdf',
    image: {
      type: 'jpeg',
      quality: 0.98
    },
    html2canvas: {
       dpi: 192,
       scale: 2,
       letterRendering: true
    } as Html2CanvasOptions,
    jsPDF: {
      unit: 'mm',
      format: 'a4'
      orientation: 'landscape'
    } as jsPDFOptions,
    pagebreak: { avoid: ['.avoid-break'] }
  }

  let worker = html2pdf()

  worker = worker
    .set(opt)
    .from(pages)
    .toContainer()
    .toCanvas()
    .toPdf()
    .get('pdf')
    .then((pdf: jsPDF) => {
      pdf.addPage()
    })

  worker.save()
}

执行worker.save()之后就可以下载pdf

值得一提的是html2pdf.js有多种处理分页截断的方式github.com/eKoopmans/h…

未配置pagebreak前,表格内容打印出来会被截断

Vue项目中使用html2pdf.js打印长页面

但我想表格的表头如果被截断了就另起一页

只需要简单配置pagebreak属性

  const opt = {
    pagebreak: { avoid: ['.avoid-break'] }
  }

给每一个表格加上class

Vue项目中使用html2pdf.js打印长页面 这样打印出来的pdf,当dom含有.avoid-break的类名且当前页不能完全放置内容,就会另起一页,如下所示

Vue项目中使用html2pdf.js打印长页面

也可以设置模式为avoid-all,这样会自动添加分页符,以避免跨页面拆分任何元素

  const opt = {
    pagebreak: { mode: ['avoid-all'] }
  }

这种模式就无须往dom上添加额外的类名,从而实现分页

这两种模式已经满足了我的需求,官方仓库还有更多的分页配置,就没有仔细研究了

有兴趣的可以查看github.com/eKoopmans/h…

3,添加封面

封面比较简单只需要将img元素传入,先生成封面的pdf页

type PDFOptions = { name: string; cover?: HTMLDivElement }
export function exportHTMLToPDF2(pages: HTMLDivElement, options: PDFOptions = { name: '模版' }) {
  const { name, cover } = options || {}

  const opt = {
    margin: 10,
    filename: name + '.pdf',
    image: {
      type: 'jpeg',
      quality: 0.98
    },
    html2canvas: {
      // dpi: 192,
      // scale: 2,
      // letterRendering: true
    } as Html2CanvasOptions,
    jsPDF: {
      unit: 'mm',
      format: 'a4'
      // orientation: 'landscape'
    } as jsPDFOptions,
    pagebreak: { mode: ['avoid-all'] }
  }

  let worker = html2pdf()

  if (cover) {
    // 封面
    worker = worker
      .set({ ...opt, margin: 0 })
      .from(cover)
      .toContainer()
      .toCanvas()
      .toPdf()
      .get('pdf')
      .then((pdf: jsPDF) => {
        pdf.addPage()
      })
  }

  worker = worker
    .set(opt)
    .from(pages)
    .toContainer()
    .toCanvas()
    .toPdf()
    .get('pdf')
    .then((pdf: jsPDF) => {
      pdf.addPage()
    })

  worker.save()
}

4,添加水印

添加水印只需要在.then回调中调用jsPDF.setPage()定位到每一页,然后添加文本

const addWaterMark = (pdf: jsPDF) => {
  const { length } = pdf.internal.pages
  const height = pdf.internal.pageSize.getHeight()
  pdf.setTextColor('#d8d8d8')
  for (let i = 1; i < length; i++) {
    pdf.setPage(i)
    for (let i = 0; i < 6; i++) {
      for (let j = 0; j < 6; j++) {
        pdf.text('watermark', j * 50, height / 5 + i * 50, undefined, 45)
      }
    }
  }
}

worker = worker
    .set(opt)
    .from(pages)
    .toContainer()
    .toCanvas()
    .toPdf()
    .get('pdf')
    .then((pdf: jsPDF) => {
        pdf.addPage()
        addWaterMark(pdf)
    })

worker.save()

5,踩坑

(1)表格右侧被截取

当我将jsPDF的配置设置如下

jsPDF: {
    unit: 'mm',
    format: 'a4',
    orientation: 'portrait'
}

会出现表格右侧被截取的情况,像这样

Vue项目中使用html2pdf.js打印长页面

起初以为是因为打印元素在el-dialog组件里面导致的,尝试了一下这个方法,也不起作用

const el = pages.cloneNode(true) as HTMLDivElement
document.body.appendChild(el)

最后在仓库issues里面找到了github.com/eKoopmans/h…

Vue项目中使用html2pdf.js打印长页面 作者的回复是

Vue项目中使用html2pdf.js打印长页面 不过作者已经很久没维护这个项目了,这些问题都没有修复,评论区提供了几种方法

1,改变配置
jsPDF: {
    unit: 'mm',
    format: 'a4',
    orientation: 'landscape'
}

只要不打印a4,portrait的纸张大小,就不会被截断,不过我的需求就是a4纸张,所以没用这种方法

Vue项目中使用html2pdf.js打印长页面

2,改变打印元素的大小

Vue项目中使用html2pdf.js打印长页面 这位老哥提供的方法虽然没解决我的问题,但提供了思路

打印内容被截取无非就是内容超过了pdf本身的宽度

Vue项目中使用html2pdf.js打印长页面

使用的vxe-grid组件,他的宽度会根据父元素的大小动态计算。但是当我使用html2pdf.js打印的时候,他还是固定的967px,按理说应该自适应为pdf的宽度。

这里猜测是html2pdf.js只是将dom单纯的clone然后生成canvas,因此不会触发vxe-grid组件的数据更新。

作者也说了,插件并没有适应宽度的选项

我这里的解决方案是,调用打印方法之前改变pdfDom的宽度来触发组件更新数据

Vue项目中使用html2pdf.js打印长页面

打印结束之后再把宽度设置为auto(这里需要把函数改为异步)

或者直接给dialog增加固定的宽度,让生成的页面就是a4的宽度,不过这样打印预览页面的宽度就不满足UI图了

Vue项目中使用html2pdf.js打印长页面

(2)文件里全是空白页

出现这种情况一般是canvas画布超过了浏览器限制的最大高度,会导致大型 PDF 在 html2pdf.js 中呈现完全空白

Chrome:
Maximum height/width: 32,767 px
Maximum area: 268,435,456 px (例如, 16,384 x 16,384)

Firefox:
Maximum height/width: 32,767 px
Maximum area: 472,907,776 px (例如, 22,528 x 20,992)

IE:
Maximum height/width: 8,192 px
Maximum area: N/A

IE Mobile:
Maximum height/width: 4,096 px
Maximum area: N/A

解决办法就是分段打印(pages是一个dom数组)

  for (let i = 0; i < pages.length; i++) {
    worker = worker
      .set(opt)
      .from(pages[i])
      .toContainer()
      .toCanvas()
      .toPdf()
      .get('pdf')
      .then((pdf: jsPDF) => {
        if (i < pages.length - 1) {
          pdf.addPage()
        }
      })
  }
  worker.save()

这样做的缺点也很明显,打印时间更长了

转载自:https://juejin.cn/post/7355738933664727078
评论
请登录