vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue3实现html转pdf

vue3实现html转成pdf并导出的示例代码

作者:diang

这篇文章主要为大家详细介绍了如何使用vue3实现html转成pdf并导出功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

方案一:html2canvas + jspdf

代码

import html2Canvas from "html2canvas";
import JsPDF from "jspdf";
import moment from "moment";
import axios from "axios";
var noTableHeight = 0; //table外的元素高度

/**
 * @param title  pdf名称
 * @param dom  想要打印的元素
 * @param fileList  想要打印的元素数组(同类名元素)
 * @param type   纵向还是横向
 * @param isDownLoad  转换完成是否下载
 */
export const htmlPdf = async (title, dom, fileList, type, isDownLoad) => {
  return new Promise((resolve) => {
    const html = document.querySelector(dom);
    let htmlHeight = 0;
    for (let i = 0; i < fileList.length; i++) {
      htmlHeight = htmlHeight + fileList[i].offsetHeight + 30;
    }
    html.style.height = htmlHeight + "px";
    const divParentHeight = JSON.parse(
      JSON.stringify(document.querySelector(dom).offsetHeight)
    );
    let uploadUrl = "";
    if (fileList) {
      const pageHeight = Math.floor((277 * html.scrollWidth) / 190) + 20; //计算pdf高度
      for (let i = 0; i < fileList.length; i++) {
        //循环获取的元素
        const multiple = Math.ceil(
          (fileList[i].offsetTop + fileList[i].offsetHeight) / pageHeight
        ); //元素的高度
        if (isSplit(fileList, i, multiple * pageHeight)) {
          //计算是否超出一页
          var _H = ""; //向pdf插入空白块的内容高度
          if (fileList[i].localName !== "tr") {
            //判断是不是表格里的内容
            _H =
              multiple * pageHeight -
              (fileList[i].offsetTop + fileList[i].offsetHeight);
          } else {
            _H =
              multiple * pageHeight -
              (fileList[i].offsetTop +
                fileList[i].offsetHeight +
                noTableHeight) +
              20;
          }
          var newNode = getFooterElement(_H); //向pdf插入空白块的内容
          const divParent = fileList[i].parentNode; // 获取该div的父节点
          const next = fileList[i].nextSibling; // 获取div的下一个兄弟节点
          const isTruncate = true;
          // 判断兄弟节点是否存在
          if (next && !isTruncate) {
            // 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
            divParent.insertBefore(newNode, next);
            document.querySelector(dom).style.height =
              newNode.offsetHeight + divParentHeight + "px";
          } else {
            // 否则向节点添加最后一个子节点
            divParent.appendChild(newNode);
          }
        }
      }
    }
    html2Canvas(html, {
      allowTaint: false,
      taintTest: false,
      logging: false,
      useCORS: true,
      dpi: window.devicePixelRatio * 1,
      scale: 1, // 按比例增加分辨率
    }).then(async (canvas) => {
      //type==true横向
      var pdfType = type ? "l" : "p";
      var pdf = new JsPDF(pdfType, "mm", "a4"); // A4纸,纵向
      var ctx = canvas.getContext("2d");
      var a4w = type ? 277 : 190;
      var a4h = type ? 190 : 277; // A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
      var imgHeight = Math.floor((a4h * canvas.width) / a4w); // 按A4显示比例换算一页图像的像素高度
      var renderedHeight = 0;
      while (renderedHeight < canvas.height) {
        var page = document.createElement("canvas");
        page.width = canvas.width;
        page.height = Math.min(imgHeight, canvas.height - renderedHeight); // 可能内容不足一页

        // 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
        page
          .getContext("2d")
          .putImageData(
            ctx.getImageData(
              0,
              renderedHeight,
              canvas.width,
              Math.min(imgHeight, canvas.height - renderedHeight)
            ),
            0,
            0
          );
        pdf.addImage(
          page.toDataURL("image/jpeg", 1.0),
          "JPEG",
          10,
          10,
          a4w,
          Math.min(a4h, (a4w * page.height) / page.width)
        ); // 添加图像到页面,保留10mm边距

        renderedHeight += imgHeight;
        if (renderedHeight < canvas.height) {
          pdf.addPage(); // 如果后面还有内容,添加一个空页
        }
        // delete page;
      }
      // 保存文件
      if (isDownLoad) {
        pdf.save(title + ".pdf");
        resolve();
      } else {
        // var pdfData = pdf.output('datauristring'); //获取base64Pdf
        const pdfBlob = pdf.output("blob"); //获取blob
        console.log(pdfBlob)
        //const pdfBlob = base64ToBlob(pdfData.split(',')[1], 'application/pdf');
        const pdfFile = blobToFile(pdfBlob, title + ".pdf");
        const FD = new FormData();
        const nowDate = moment(new Date()).format("YYYYMMDDHHmmss");
        FD.append("file", pdfFile, nowDate + title + ".pdf");
        await uploadFile(null, FD).then((res) => {
          const result = res.result;
          uploadUrl = result.url;
          resolve(uploadUrl);
        });
      }
      document.getElementsByClassName("divRemove")[0]?.remove();
      document.querySelector(dom).style.height = divParentHeight + "px";
      //利用promise 回调到页面中去
    });
  });
  // type传有效值pdf则为横版
};
// pdf截断需要一个空白位置来补充
const getFooterElement = (remainingHeight, fillingHeight = 0) => {
  const newNode = document.createElement("div");
  newNode.style.background = "#ffffff";
  newNode.style.width = "calc(100% + 8px)";
  newNode.style.marginLeft = "-4px";
  newNode.style.marginBottom = "0px";
  newNode.classList.add("divRemove");
  newNode.style.height = remainingHeight + fillingHeight + "px";
  return newNode;
};
const isSplit = (nodes, index, pageHeight) => {
  // 判断是不是tr 如果不是高度存起来
  // 表格里的内容要特殊处理
  // tr.offsetTop 是tr到table表格的高度
  // 所以计算高速时候要把表格外的高度加起来
  // 生成的pdf没有表格了这里可以不做处理 直接计算就行
  if (nodes[index].localName !== "tr") {
    //判断元素是不是tr
    noTableHeight += nodes[index].clientHeight;
  }

  if (nodes[index].localName !== "tr") {
    return (
      nodes[index].offsetTop + nodes[index].offsetHeight < pageHeight &&
      nodes[index + 1] &&
      nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight > pageHeight
    );
  } else {
    return (
      nodes[index].offsetTop + nodes[index].offsetHeight + noTableHeight <
      pageHeight &&
      nodes[index + 1] &&
      nodes[index + 1].offsetTop +
      nodes[index + 1].offsetHeight +
      noTableHeight >
      pageHeight
    );
  }
};

export function base64ToBlob(base64, type) {
  let binary = atob(base64);
  let array = [];
  for (let i = 0; i < binary.length; i++) {
    array.push(binary.charCodeAt(i));
  }
  return new Blob([new Uint8Array(array)], { type });
}

export function blobToFile(theBlob, fileName) {
  theBlob.lastModifiedDate = new Date();
  theBlob.name = fileName;
  return theBlob;
}

export const uploadFile = async (
  uploadUrl,
  params,
  module = "CA",
  fileSymbol = "",
  customFileName = "",
  renameFlag = true
) => {
  return await new Promise((resolve) => {
    const api = uploadUrl || window?.uploadOptions?.upload?.action
    axios
      .post(
        `${api}?module=${module}&fileSymbol=${fileSymbol}&customFileName=${customFileName}&renameFlag=${renameFlag}`,
        params,
        {
          headers: {
            "Content-Type": "multipart/form-data",
          },
        }
      )
      .then((res) => {
        resolve(res.data);
      });
  });
};

/*  */


原理: 先用 html2canvas 将 HTML 内容转换为图片,然后将图片放入 jsPDF 中生成 PDF。

优点

缺点

适用场景: 对PDF质量要求不高、内容量很小的场景,或者需要生成“快照”式PDF。

方案二:html2canvas + pdf-lib

代码

import html2Canvas from "html2canvas";
import { PDFDocument, rgb } from 'pdf-lib';
import moment from "moment";
import axios from "axios";
var noTableHeight = 0; //table外的元素高度

export const htmlPdf = async (title, dom, fileList, type, isDownLoad) => {
  return new Promise(async (resolve) => {
    const html = document.querySelector(dom);
    let htmlHeight = 0;
    const divParentHeight = JSON.parse(
      JSON.stringify(document.querySelector(dom).offsetHeight)
    );
    let uploadUrl = "";
    if (fileList) {
      for (let i = 0; i < fileList.length; i++) {
        htmlHeight = htmlHeight + fileList[i].offsetHeight + 30;
      }
      html.style.height = htmlHeight + "px";
      const pageHeight = Math.floor((277 * html.scrollWidth) / 190) + 20; //计算pdf高度
      for (let i = 0; i < fileList.length; i++) {
        //循环获取的元素
        const multiple = Math.ceil(
          (fileList[i].offsetTop + fileList[i].offsetHeight) / pageHeight
        ); //元素的高度
        if (isSplit(fileList, i, multiple * pageHeight)) {
          //计算是否超出一页
          var _H = ""; //向pdf插入空白块的内容高度
          if (fileList[i].localName !== "tr") {
            //判断是不是表格里的内容
            _H =
              multiple * pageHeight -
              (fileList[i].offsetTop + fileList[i].offsetHeight);
          } else {
            _H =
              multiple * pageHeight -
              (fileList[i].offsetTop +
                fileList[i].offsetHeight +
                noTableHeight) +
              20;
          }
          var newNode = getFooterElement(_H); //向pdf插入空白块的内容
          const divParent = fileList[i].parentNode; // 获取该div的父节点
          const next = fileList[i].nextSibling; // 获取div的下一个兄弟节点
          const isTruncate = true;
          // 判断兄弟节点是否存在
          if (next && !isTruncate) {
            // 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
            divParent.insertBefore(newNode, next);
            document.querySelector(dom).style.height =
              newNode.offsetHeight + divParentHeight + "px";
          } else {
            // 否则向节点添加最后一个子节点
            divParent.appendChild(newNode);
          }
        }
      }
    }

    if (!html) {
      throw new Error('DOM元素未找到');
    }

    // 1. 分块处理大文档
    const chunks = await splitContentIntoChunks(html);

    // 2. 创建PDF文档
    const pdfDoc = await PDFDocument.create();

    // 3. 处理每个分块
    const reusableCanvas = document.createElement('canvas');
    const ctx = reusableCanvas.getContext('2d');
    // for (const chunk of chunks) {
    for (let i = 0; i < chunks.length; i++) {
      const chunk = chunks[i];
      console.log(chunk)
      reusableCanvas.width = html.offsetWidth * 1  // type ? 277 : 190//window.innerWidth * 0.5;
      reusableCanvas.height = chunk.height;
      // 使用 html2Canvas 渲染到现有 Canvas
      await html2Canvas(chunk.html, {
        allowTaint: false,
        logging: false,
        useCORS: true,
        scale: 1,
        y: chunk.offset,
        windowHeight: chunk.offset,
        canvas: reusableCanvas, // 指定使用现有 Canvas
      });

      // 4. 将canvas添加到PDF
      await addCanvasToPdf(pdfDoc, reusableCanvas);

      // 不需要手动释放内存,因为我们会复用同一个 Canvas
      // 只需清除上一块的内容
      ctx.clearRect(0, 0, reusableCanvas.width, reusableCanvas.height);
      // 释放内存
      reusableCanvas.width = 1;
      reusableCanvas.height = 1;
      // }
    }


    // 5. 保存或上传PDF
    const pdfBytes = await pdfDoc.save();

    if (isDownLoad) {
      downloadPDF(pdfBytes, title);
      return;
    } else {
      const blob = new Blob([pdfBytes], { type: 'application/pdf' });
      const pdfFile = blobToFile(blob, title + ".pdf");
      const FD = new FormData();
      const nowDate = moment(new Date()).format("YYYYMMDDHHmmss");
      FD.append("file", pdfFile, nowDate + title + ".pdf");
      await uploadFile(null, FD).then((res) => {
        const result = res.result;
        uploadUrl = result.url;
        resolve(uploadUrl);
      });
    }
  })
}
// pdf截断需要一个空白位置来补充
const getFooterElement = (remainingHeight, fillingHeight = 0) => {
  const newNode = document.createElement("div");
  newNode.style.background = "#ffffff";
  newNode.style.width = "calc(100% + 8px)";
  newNode.style.marginLeft = "-4px";
  newNode.style.marginBottom = "0px";
  newNode.classList.add("divRemove");
  newNode.style.height = remainingHeight + fillingHeight + "px";
  return newNode;
};
const isSplit = (nodes, index, pageHeight) => {
  // 判断是不是tr 如果不是高度存起来
  // 表格里的内容要特殊处理
  // tr.offsetTop 是tr到table表格的高度
  // 所以计算高速时候要把表格外的高度加起来
  // 生成的pdf没有表格了这里可以不做处理 直接计算就行
  if (nodes[index].localName !== "tr") {
    //判断元素是不是tr
    noTableHeight += nodes[index].clientHeight;
  }

  if (nodes[index].localName !== "tr") {
    return (
      nodes[index].offsetTop + nodes[index].offsetHeight < pageHeight &&
      nodes[index + 1] &&
      nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight > pageHeight
    );
  } else {
    return (
      nodes[index].offsetTop + nodes[index].offsetHeight + noTableHeight <
      pageHeight &&
      nodes[index + 1] &&
      nodes[index + 1].offsetTop +
      nodes[index + 1].offsetHeight +
      noTableHeight >
      pageHeight
    );
  }
};

export function base64ToBlob(base64, type) {
  let binary = atob(base64);
  let array = [];
  for (let i = 0; i < binary.length; i++) {
    array.push(binary.charCodeAt(i));
  }
  return new Blob([new Uint8Array(array)], { type });
}

export function blobToFile(theBlob, fileName) {
  theBlob.lastModifiedDate = new Date();
  theBlob.name = fileName;
  return theBlob;
}



/**
 * 将大内容分割成多个可处理的部分
 */
async function splitContentIntoChunks(html) {
  const chunks = [];
  const totalHeight = html.scrollHeight;
  const viewportHeight = window.innerHeight;
  const chunkHeight = Math.min(viewportHeight, 960) * 1.5; // 每块最多5000px
  for (let offset = 0; offset < totalHeight; offset += chunkHeight) {
    const height = Math.min(chunkHeight, totalHeight - offset);
    chunks.push({
      html,
      height,
      offset
    });

    // 避免浏览器卡顿
    await new Promise(resolve => setTimeout(resolve, 10));
  }

  return chunks;
}

/**
 * 将canvas添加到PDF文档
 */
async function addCanvasToPdf(pdfDoc, canvas) {
  // 转换为PNG并嵌入
  const pngImage = await pdfDoc.embedPng(canvas.toDataURL('image/png'));
  // 
  const page = pdfDoc.addPage([canvas.width, canvas.height]); // A4尺寸
  console.log(pngImage)

  // 计算缩放比例以适应页面
  let scale = Math.min(
    (page.getWidth() - 40) / pngImage.width,  // 左右各20pt边距
    (page.getHeight() - 40) / pngImage.height // 上下各20pt边距
  );
  page.drawImage(pngImage, {
    x: 20,
    y: 20,
    width: pngImage.width * scale,
    height: pngImage.height * scale,
  });
}

/**
 * 下载PDF文件
 */
function downloadPDF(pdfBytes, fileName) {
  const blob = new Blob([pdfBytes], { type: 'application/pdf' });
  const link = document.createElement('a');
  link.href = URL.createObjectURL(blob);
  link.download = `${fileName}.pdf`;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  URL.revokeObjectURL(link.href);
}




export const uploadFile = async (
  uploadUrl,
  params,
  module = "CA",
  fileSymbol = "",
  customFileName = "",
  renameFlag = true
) => {
  return await new Promise((resolve) => {
    const api = uploadUrl || window?.uploadOptions?.upload?.action
    axios
      .post(
        `${api}?module=${module}&fileSymbol=${fileSymbol}&customFileName=${customFileName}&renameFlag=${renameFlag}`,
        params,
        {
          headers: {
            "Content-Type": "multipart/form-data",
          },
        }
      )
      .then((res) => {
        resolve(res.data);
      });
  });
};

原理: 用方案一,一旦遇到超长内容打印的时候就会出现文档内容全部是黑屏现象,原因是因为canvas的画布高度是有限的,方案二使用分块的形式,对内容进行分割得到多个canvas,然后逐一添加到pdf,可以避免黑屏问题, 

优点

缺点

使用时注意事项

适用场景: 对PDF质量要求不高、内容量很小的场景,或者需要生成“快照”式PDF。

方案三:window.print()

如果只是想单纯的得到一份pdf直接右击另存为pdf格式的就能实现

方案四:提供静态模版给服务端生成

这个方案是最好的,就是费服务端,需要前后端配合,不论是性能还是得到的pdf清晰度都是比较好的。

到此这篇关于vue3实现html转成pdf并导出的示例代码的文章就介绍到这了,更多相关vue3实现html转pdf内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文