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

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

1,安装依赖

js 复制代码
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...

js 复制代码
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前,表格内容打印出来会被截断

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

只需要简单配置pagebreak属性

js 复制代码
  const opt = {
    pagebreak: { avoid: ['.avoid-break'] }
  }

给每一个表格加上class

这样打印出来的pdf,当dom含有.avoid-break的类名且当前页不能完全放置内容,就会另起一页,如下所示

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

js 复制代码
  const opt = {
    pagebreak: { mode: ['avoid-all'] }
  }

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

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

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

3,添加封面

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

js 复制代码
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()定位到每一页,然后添加文本

js 复制代码
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的配置设置如下

css 复制代码
jsPDF: {
    unit: 'mm',
    format: 'a4',
    orientation: 'portrait'
}

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

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

javascript 复制代码
const el = pages.cloneNode(true) as HTMLDivElement
document.body.appendChild(el)

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

作者的回复是

不过作者已经很久没维护这个项目了,这些问题都没有修复,评论区提供了几种方法

1,改变配置
css 复制代码
jsPDF: {
    unit: 'mm',
    format: 'a4',
    orientation: 'landscape'
}

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

2,改变打印元素的大小

这位老哥提供的方法虽然没解决我的问题,但提供了思路

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

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

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

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

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

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

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

(2)文件里全是空白页

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

yaml 复制代码
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数组)

js 复制代码
  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()

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

相关推荐
山禾女鬼0011 小时前
Vue 3 自定义指令
前端·javascript·vue.js
麦麦大数据1 小时前
知识图谱中医知识问答系统|养生医案综合可视化系|推荐算法|vue+flask+neo4j+mysql
vue.js·知识图谱·推荐算法
啊卡无敌1 小时前
Vue 3 reactive 和 ref 区别及 失去响应性问题
前端·javascript·vue.js
hy_花花1 小时前
Vue3.4之defineModel的用法
前端·vue.js
我是仙女你信不信2 小时前
生成pdf并下载
前端·javascript·vue.js
周星星日记3 小时前
12.vue3中组件实现原理(下)之emit和slots
前端·vue.js·面试
周星星日记4 小时前
11.vue3中组件实现原理(中)
前端·vue.js·面试
i_am_a_div_日积月累_4 小时前
前端路由缓存实现
前端·javascript·vue.js
郝某人一生平安5 小时前
前端 Word 模板参入特定数据 并且下载
前端·vue.js
—Qeyser5 小时前
用 Deepseek 写的uniapp油耗计算器
前端·vue.js·gpt·chatgpt·uni-app·gpt-3·deepseek