最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue3實(shí)現(xiàn)html轉(zhuǎn)成pdf并導(dǎo)出的示例代碼

 更新時(shí)間:2025年08月29日 10:04:48   作者:diang  
這篇文章主要為大家詳細(xì)介紹了如何使用vue3實(shí)現(xiàn)html轉(zhuǎn)成pdf并導(dǎo)出功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

方案一: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  想要打印的元素?cái)?shù)組(同類名元素)
 * @param type   縱向還是橫向
 * @param isDownLoad  轉(zhuǎn)換完成是否下載
 */
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; //計(jì)算pdf高度
      for (let i = 0; i < fileList.length; i++) {
        //循環(huán)獲取的元素
        const multiple = Math.ceil(
          (fileList[i].offsetTop + fileList[i].offsetHeight) / pageHeight
        ); //元素的高度
        if (isSplit(fileList, i, multiple * pageHeight)) {
          //計(jì)算是否超出一頁
          var _H = ""; //向pdf插入空白塊的內(nèi)容高度
          if (fileList[i].localName !== "tr") {
            //判斷是不是表格里的內(nèi)容
            _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插入空白塊的內(nèi)容
          const divParent = fileList[i].parentNode; // 獲取該div的父節(jié)點(diǎn)
          const next = fileList[i].nextSibling; // 獲取div的下一個兄弟節(jié)點(diǎn)
          const isTruncate = true;
          // 判斷兄弟節(jié)點(diǎn)是否存在
          if (next && !isTruncate) {
            // 存在則將新節(jié)點(diǎn)插入到div的下一個兄弟節(jié)點(diǎn)之前,即div之后
            divParent.insertBefore(newNode, next);
            document.querySelector(dom).style.height =
              newNode.offsetHeight + divParentHeight + "px";
          } else {
            // 否則向節(jié)點(diǎn)添加最后一個子節(jié)點(diǎn)
            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的邊距,顯示區(qū)域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); // 可能內(nèi)容不足一頁

        // 用getImageData剪裁指定區(qū)域,并畫到前面創(chuàng)建的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(); // 如果后面還有內(nèi)容,添加一個空頁
        }
        // 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 回調(diào)到頁面中去
    });
  });
  // type傳有效值pdf則為橫版
};
// pdf截?cái)嘈枰粋€空白位置來補(bǔ)充
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 如果不是高度存起來
  // 表格里的內(nèi)容要特殊處理
  // tr.offsetTop 是tr到table表格的高度
  // 所以計(jì)算高速時(shí)候要把表格外的高度加起來
  // 生成的pdf沒有表格了這里可以不做處理 直接計(jì)算就行
  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 內(nèi)容轉(zhuǎn)換為圖片,然后將圖片放入 jsPDF 中生成 PDF。

優(yōu)點(diǎn)

  • 純前端完成,不依賴服務(wù)器。
  • 可以實(shí)現(xiàn)一鍵下載。

缺點(diǎn)

  • 質(zhì)量堪憂:生成的是圖片型 PDF,文字無法選中、搜索,清晰度不高(尤其對高分辨率大頁面)。
  • 性能差,轉(zhuǎn)換大頁面容易卡頓甚至崩潰。
  • 分頁控制非常麻煩。

適用場景: 對PDF質(zhì)量要求不高、內(nèi)容量很小的場景,或者需要生成“快照”式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; //計(jì)算pdf高度
      for (let i = 0; i < fileList.length; i++) {
        //循環(huán)獲取的元素
        const multiple = Math.ceil(
          (fileList[i].offsetTop + fileList[i].offsetHeight) / pageHeight
        ); //元素的高度
        if (isSplit(fileList, i, multiple * pageHeight)) {
          //計(jì)算是否超出一頁
          var _H = ""; //向pdf插入空白塊的內(nèi)容高度
          if (fileList[i].localName !== "tr") {
            //判斷是不是表格里的內(nèi)容
            _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插入空白塊的內(nèi)容
          const divParent = fileList[i].parentNode; // 獲取該div的父節(jié)點(diǎn)
          const next = fileList[i].nextSibling; // 獲取div的下一個兄弟節(jié)點(diǎn)
          const isTruncate = true;
          // 判斷兄弟節(jié)點(diǎn)是否存在
          if (next && !isTruncate) {
            // 存在則將新節(jié)點(diǎn)插入到div的下一個兄弟節(jié)點(diǎn)之前,即div之后
            divParent.insertBefore(newNode, next);
            document.querySelector(dom).style.height =
              newNode.offsetHeight + divParentHeight + "px";
          } else {
            // 否則向節(jié)點(diǎn)添加最后一個子節(jié)點(diǎn)
            divParent.appendChild(newNode);
          }
        }
      }
    }

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

    // 1. 分塊處理大文檔
    const chunks = await splitContentIntoChunks(html);

    // 2. 創(chuàng)建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 渲染到現(xiàn)有 Canvas
      await html2Canvas(chunk.html, {
        allowTaint: false,
        logging: false,
        useCORS: true,
        scale: 1,
        y: chunk.offset,
        windowHeight: chunk.offset,
        canvas: reusableCanvas, // 指定使用現(xiàn)有 Canvas
      });

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

      // 不需要手動釋放內(nèi)存,因?yàn)槲覀儠?fù)用同一個 Canvas
      // 只需清除上一塊的內(nèi)容
      ctx.clearRect(0, 0, reusableCanvas.width, reusableCanvas.height);
      // 釋放內(nèi)存
      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截?cái)嘈枰粋€空白位置來補(bǔ)充
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 如果不是高度存起來
  // 表格里的內(nèi)容要特殊處理
  // tr.offsetTop 是tr到table表格的高度
  // 所以計(jì)算高速時(shí)候要把表格外的高度加起來
  // 生成的pdf沒有表格了這里可以不做處理 直接計(jì)算就行
  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;
}



/**
 * 將大內(nèi)容分割成多個可處理的部分
 */
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) {
  // 轉(zhuǎn)換為PNG并嵌入
  const pngImage = await pdfDoc.embedPng(canvas.toDataURL('image/png'));
  // 
  const page = pdfDoc.addPage([canvas.width, canvas.height]); // A4尺寸
  console.log(pngImage)

  // 計(jì)算縮放比例以適應(yīng)頁面
  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);
      });
  });
};

原理: 用方案一,一旦遇到超長內(nèi)容打印的時(shí)候就會出現(xiàn)文檔內(nèi)容全部是黑屏現(xiàn)象,原因是因?yàn)閏anvas的畫布高度是有限的,方案二使用分塊的形式,對內(nèi)容進(jìn)行分割得到多個canvas,然后逐一添加到pdf,可以避免黑屏問題, 

優(yōu)點(diǎn)

  • 純前端完成,不依賴服務(wù)器。
  • 可以打印復(fù)雜量多內(nèi)容。

缺點(diǎn)

  • 質(zhì)量堪憂:生成的是圖片型 PDF,文字無法選中、搜索,清晰度不高(尤其對高分辨率大頁面)。
  • 性能差,轉(zhuǎn)換超長內(nèi)容時(shí)用時(shí)很久。

使用時(shí)注意事項(xiàng)

  • 注意如果是轉(zhuǎn)換彈框中modal中的內(nèi)容,一定要另外起一個新頁面,避免內(nèi)容中太多靜態(tài)資源,因?yàn)閏anvas轉(zhuǎn)換成base64圖片的時(shí)候會一直加載容器底下的所有靜態(tài)資源。
  • 想要控制一頁展示內(nèi)容多少,用方法splitContentIntoChunks中的chunkHeight就行,參數(shù)值越大,一頁展示的內(nèi)容越多,生成的canvas越少,會節(jié)省時(shí)間,但是打印出來的內(nèi)容會很小,所以要調(diào)整好自己合適的尺寸。
  • 將canvas添加到PDF文檔的方法addCanvasToPdf使用時(shí)
    const page = pdfDoc.addPage([canvas.width, canvas.height]); // A4尺寸
    這行代碼里面的image的寬高就直接用canvas的,不要在自定義,不然容易出現(xiàn)變形

適用場景: 對PDF質(zhì)量要求不高、內(nèi)容量很小的場景,或者需要生成“快照”式PDF。

方案三:window.print()

如果只是想單純的得到一份pdf直接右擊另存為pdf格式的就能實(shí)現(xiàn)

方案四:提供靜態(tài)模版給服務(wù)端生成

這個方案是最好的,就是費(fèi)服務(wù)端,需要前后端配合,不論是性能還是得到的pdf清晰度都是比較好的。

到此這篇關(guān)于vue3實(shí)現(xiàn)html轉(zhuǎn)成pdf并導(dǎo)出的示例代碼的文章就介紹到這了,更多相關(guān)vue3實(shí)現(xiàn)html轉(zhuǎn)pdf內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

榆社县| 永嘉县| 麻阳| 读书| 资溪县| 双流县| 紫金县| 玉环县| 衢州市| 营山县| 仙桃市| 沭阳县| 临汾市| 印江| 哈巴河县| 合山市| 景谷| 岢岚县| 青州市| 元氏县| 专栏| 洛川县| 榆社县| 饶平县| 新干县| 安顺市| 呼伦贝尔市| 沈阳市| 宁武县| 安福县| 若羌县| 岳西县| 弥渡县| 上林县| 平昌县| 通州市| 花莲县| 泗水县| 孝义市| 仙居县| 楚雄市|