Vue实现HTML转PDF:智能分页与批量合并的解决方案

  将html内容导出为pdf是一个常见的需求,无论是生成报告还是内容打印,都需要一个可靠的pdf导出方案,同时还要智能分页,避免出现一行字分为两半,在上下两页的问题

  同时我在开发过程中发现当pdf页数过长时,会导致下载后的页面内容全部黑屏,针对这个情况我采取的措施是将内容分割成多个小的pdf,最后在下载的时候合并成整个的pdf然后再下载,由此根据我的实际开发和部分来源于网络的方法,再进行调整和适配,整理出来如下的内容

首先看html部分,我的需要打印的html片段是后端直接返回的

htmlData是一个html片段的数组

<div v-if="Array.isArray(htmlData)">
  <div v-for="(i, index) in htmlData" :key="`htmlData${index}`">
    <!-- 白色背景,用于PDF转换 -->
    <div :id="`htmlData${index}`" class="home_detail_text home_detail_text_white" v-html="i"></div>
    <!-- 深色背景,用于界面显示 -->
    <div class="home_detail_text" v-html="i"></div>
  </div>
</div>

由于展示出的界面样式背景颜色等都做了处理,所以我将未作处理的内容通过z-index隐藏在显示层的下方,实际生成pdf的内容是未做处理的片段

.home_detail_text_white {
  position: absolute;
  top: 0;
  z-index: -1;  /* 隐藏在显示层下方 */
  width: 100%;
  color: #333 !important;  /* 白色背景用深色文字 */
}

.home_detail_text {
  /* 正常显示样式 */
  color: rgba(255,255,255,0.9);  /* 深色背景用浅色文字 */
}

需要用到的第三方依赖库

import html2Canvas from "html2canvas";   // HTML转Canvas
import jsPDF from "jspdf";               // Canvas转PDF
// import STSONG from "@/assets/font/STSONG-normal.js"; // 中文字体(可参考)
import { PDFDocument } from "pdf-lib";   // PDF合并操作

属性值,pdfArr的内容其实和htmlData的内容是一一对应的

data(){
    return {
      pdfArr: [], //pdf集合
      htmlData: [], //html片段集合
    }
}

将页面内容转成pdf的核心代码如下:

   convertToPDF() {
      if (
        Array.isArray(this.htmlData)
      ) {
        const loading = this.$loading({
          lock: true,
          text: "下载中,请稍后。。。",
          spinner: "el-icon-loading",
        });
        // 遍历html,首先拆分pdf
        this.$nextTick(async () => {
          let pdfPromises = [];
          //遍历html片段集合
          for (const key in this.htmlData) {
            const dom = document.getElementById(`htmlData${key}`);
            dom.style.padding = "20px";
            dom.style.padding = "0";
            // 等待getPdf执行完一次再循环
            let pdfPromise = await this.getPdf(dom);
            pdfPromises.push(pdfPromise);
          }
          // 等待所有 getPdf 调用完成
          Promise.all(pdfPromises).then((pdfs) => {
            this.pdfArr.push(pdfs);
            this.traversePdf();
            loading.close();
          });
        });
      }
    },

getPdf():将html片段转成canvas,再将canvas转成pdf

   async getPdf(dom) {
      const that = this;
      return new Promise((resolve, reject) => {
        html2Canvas(dom, {
          useCORS: true,
          allowTaint: false,
          backgroundColor: null,
          logging: true,
          scale: 1,
          backgroundColor: "#ffffff",
          imageTimeout: 8000,
          dpi: 300,
        })
          .then(function (canvas) {
            //未生成pdf的html页面高度
            let leftHeight = canvas.height;
            let a4Width = 190;
            let a4Height = 277; //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
            //一页pdf显示html页面生成的canvas高度;
            let a4HeightRef = Math.floor((canvas.width / a4Width) * a4Height);
            //pdf页面偏移
            let position = 0;
            // let pageData = canvas.toDataURL("image/jpeg", 1.0);
            let pdf = new jsPDF("p", "mm", "a4"); //A4纸,纵向
            let index = 1,
              canvas1 = document.createElement("canvas"),
              height;
            pdf.setDisplayMode("fullwidth", "continuous", "FullScreen");
            //处理分页
            function createImpl(canvas) {
              if (leftHeight > 0) {
                index++;
                let checkCount = 0;
                if (leftHeight > a4HeightRef) {
                  let i = position + a4HeightRef;
                  for (i = position + a4HeightRef; i >= position; i--) {
                    let isWrite = true;
                    for (let j = 0; j < canvas.width; j++) {
                      let c = canvas
                        .getContext("2d")
                        .getImageData(j, i, 1, 1).data;
                      if (c[0] != 0xff || c[1] != 0xff || c[2] != 0xff) {
                        isWrite = false;
                        break;
                      }
                    }
                    if (isWrite) {
                      checkCount++;
                      if (checkCount >= 10) {
                        break;
                      }
                    } else {
                      checkCount = 0;
                    }
                  }
                  height =
                    Math.round(i - position) ||
                    Math.min(leftHeight, a4HeightRef);
                  if (height <= 0) {
                    height = a4HeightRef;
                  }
                } else {
                  height = leftHeight;
                }
                canvas1.width = canvas.width;
                canvas1.height = height;
                let ctx = canvas1.getContext("2d");
                ctx.drawImage(
                  canvas,
                  0,
                  position,
                  canvas.width,
                  height,
                  0,
                  0,
                  canvas.width,
                  height
                );
                if (position != 0) {
                  pdf.addPage();
                }
                // 页码
                const pageNumberText = "第" + that.pageNumber + "页";
                // 设置字体
                // pdf.setFont("STSONG");
                // 设置文字大小
                pdf.setFontSize(8);
                // 设置文字颜色
                pdf.setTextColor("#000000");
                // 在页脚添加页码
                pdf.text(pageNumberText, a4Width / 2 + 8, a4Height + 16);
                pdf.addImage(
                  canvas1.toDataURL("image/jpeg", 1.0),
                  "JPEG",
                  10,
                  10,
                  a4Width,
                  (a4Width / canvas1.width) * height
                );
                leftHeight -= height;
                position += height;
                if (leftHeight > 0) {
                  setTimeout(createImpl, 500, canvas);
                } else {
                  resolve(pdf);
                }
              }
            }
            //当内容未超过pdf一页显示的范围,还是走分页
            if (leftHeight < a4HeightRef) {
              setTimeout(createImpl, 500, canvas);
            } else {
              try {
                pdf.deletePage(0);
                setTimeout(createImpl, 500, canvas);
              } catch (err) {}
            }
          })
          .catch((err) => {
            reject(err);
          });
      });
    },

traversePdf():将多个pdf合并后并下载

   async traversePdf() {
      // 将多个pdf合成一个
      // 创建一个新的PDF文档
      const pdfDoc = await PDFDocument.create();
      for (let index = 0; index < this.pdfArr[0].length; index++) {
        const pdf1 = await PDFDocument.load(
          this.pdfArr[0][index].output("arraybuffer")
        );
        const copiedPage = await pdfDoc.copyPages(pdf1, pdf1.getPageIndices());
        copiedPage.forEach((p) => {
          pdfDoc.addPage(p);
        });
      }
      const uint8Array = await pdfDoc.save();
      let mergeBuffer = Buffer.from(uint8Array);
      const url = window.URL.createObjectURL(new Blob([mergeBuffer]));
      const link = document.createElement("a");
      link.href = url;
      link.download = `pdf下载.pdf`;
      link.click();
      // 打印后调用打印设置的接口
      printSave(this.printInfo);
    }

只展示了核心关键代码,主要是提供一个思路,具体方法的调用和处理可以根据开发情况自行调整,希望能给大家带来一定的帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值