vue3实现将HTML导出为pdf,HTML转换为文件流

Vue3 项目中实现HTML导出为PDF以及将HTML生成的pdf转换为file或blob

1-直接开始下载依赖

javascript 复制代码
pnpm install jspdf html2canvas

2-导出方法

自己生成一个ts文件来写工具函数

javascript 复制代码
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';

/**
 * 获取设备的每英寸点数(DPI)。
 * 通过创建一个宽度为 1 英寸的临时 div 元素,获取其实际像素宽度作为 DPI 值。
 * @returns {number} 设备的 DPI 值。
 */
export function getDPI() {
  const div = document.createElement('div');
  div.style.cssText = 'height: 1in; left: -100%; position: absolute; top: -100%; width: 1in;';
  document.body.appendChild(div);
  const devicePixelRatio = window.devicePixelRatio || 1,
    dpi = div.offsetWidth * devicePixelRatio;
  return dpi;
}

/**
 * 将毫米单位转换为像素单位。
 * @param {number} mm - 要转换的毫米值。
 * @param {number} dpi - 设备的 DPI 值。
 * @returns {number} 转换后的像素值,四舍五入取整。
 */
export function mmToPixel(mm: any, dpi: any) {
  let inches = mm / 25.4;
  let pixels = inches * dpi;
  return Math.round(pixels);
}

/**
 * 生成 PDF 报告,将指定 HTML 元素转换为 PDF 文件。
 * @param {number} margin - PDF 页面的上下边距,单位为毫米。
 * @param {number} leftMargin - PDF 页面的左边距,单位为毫米。
 * @param {number} rightMargin - PDF 页面的右边距,单位为毫米。
 * @param {string} fileName - 生成的 PDF 文件的名称。
 * @param {string} pdfId - 要导出的 HTML 元素的 ID。
 * @returns {Promise<void>} 一个 Promise,在导出完成或出错时 resolve 或 reject。
 */
export async function generateReport(
  margin: any,
  leftMargin: any = 0,
  rightMargin: any = 0,
  fileName: any,
  pdfId: any,
) {
  const element = document.getElementById(pdfId);
  if (!element) {
    console.log('未找到要导出的元素');
    return;
  }
  try {
    // 设置 PDF 页面参数
    const a4WidthMm = 210;
    const a4HeightMm = 297;
    const marginTopBottom = Number(margin);
    const leftMarginMm = Number(leftMargin);
    const rightMarginMm = Number(rightMargin);

    // 计算可用区域
    const availableHeightMm = a4HeightMm - 2 * marginTopBottom;
    const availableWidthMm = a4WidthMm - leftMarginMm - rightMarginMm;

    // 获取 DPI 和像素转换
    const dpi = getDPI();
    const availableWidthPx = mmToPixel(availableWidthMm, dpi);
    const availableHeightPx = mmToPixel(availableHeightMm, dpi);

    // 渲染 HTML 元素到 Canvas,使用更完整的配置
    const canvas = await html2canvas(element, {
      scale: 2,
      useCORS: true,
      logging: false,
      backgroundColor: '#ffffff',
      removeContainer: true,
      width: element.offsetWidth,
      height: element.scrollHeight,
      windowWidth: Math.max(element.scrollWidth, element.offsetWidth),
      windowHeight: Math.max(element.scrollHeight, element.offsetHeight),
      x: 0,
      y: 0,
      scrollX: 0,
      scrollY: 0,
      allowTaint: true,
      // letterRendering: true,
    });
    // const ctx: any = canvas.getContext('2d');
    const pdf = new jsPDF('p', 'mm', 'a4');

    let yOffset = 0;
    const totalHeight = canvas.height;
    const pages: Array<{ imgData: string; imgHeightInPdf: number }> = [];

    // 拆分图片并存储到数组
    while (yOffset < totalHeight) {
      let sliceHeight = availableHeightPx;

      if (yOffset + sliceHeight > totalHeight) {
        sliceHeight = totalHeight - yOffset;
      }

      // 创建切片画布并绘制内容
      const sliceCanvas = document.createElement('canvas');
      sliceCanvas.width = availableWidthPx;
      sliceCanvas.height = sliceHeight;
      const sliceCtx: any = sliceCanvas.getContext('2d');
      sliceCtx.drawImage(
        canvas,
        0,
        yOffset,
        Math.min(availableWidthPx, canvas.width),
        sliceHeight,
        0,
        0,
        Math.min(availableWidthPx, canvas.width),
        sliceHeight,
      );

      // 转换为图片数据
      const imgData = sliceCanvas.toDataURL('image/png');
      // 正确计算PDF中的图片高度,保持原始宽高比
      const imgWidthPx = Math.min(availableWidthPx, canvas.width);
      const imgHeightInPdf = (sliceHeight * availableWidthMm) / imgWidthPx;
      pages.push({ imgData, imgHeightInPdf });

      yOffset += sliceHeight;
    }

    const totalPages = pages.length;

    // 将所有页面添加到 PDF
    pages.forEach((page, index) => {
      if (index > 0) {
        pdf.addPage();
      }
      // 计算实际可用宽度,确保图片不会超出页面
      const actualWidth = Math.min(availableWidthMm, 210 - leftMarginMm - rightMarginMm);
      // 计算水平居中位置
      const centerX = (210 - actualWidth) / 2;
      pdf.addImage(page.imgData, 'PNG', centerX, marginTopBottom, actualWidth, page.imgHeightInPdf);

      // 添加页码
      pdf.setFontSize(10);
      // 页码居中显示
      pdf.text(`${index + 1} / ${totalPages}`, 105, a4HeightMm - 10, {
        align: 'center',
      });
    });

    pdf.save(fileName + '.pdf');
  } catch (error) {
    console.log('导出 PDF 时出错!');
    throw error;
  }
}

/**
 * 根据 HTML 元素 ID 获取生成的 PDF 文件对象
 * @param {number} margin - PDF 页面的上下边距,单位为毫米
 * @param {number} leftMargin - PDF 页面的左边距,单位为毫米
 * @param {number} rightMargin - PDF 页面的右边距,单位为毫米
 * @param {string} elementId - 要转换的 HTML 元素的 ID
 * @param {string} returnType - 返回类型:'blob' 或 'file'
 * @param {string} fileName - 当 returnType 为 'file' 时的文件名
 * @returns {Promise<Blob | File>} 返回 PDF 文件的 Blob 或 File 对象
 */
export async function getPdfFileByElementId(
  margin: number = 0,
  leftMargin: number = 0,
  rightMargin: number = 0,
  elementId: string,
  returnType: 'blob' | 'file' = 'blob',
  fileName: string = 'document.pdf',
): Promise<Blob | File> {
  const element = document.getElementById(elementId);
  if (!element) {
    throw new Error(`未找到 ID 为 "${elementId}" 的 HTML 元素`);
  }

  try {
    // 设置 PDF 页面参数
    const a4WidthMm = 210;
    const a4HeightMm = 297;
    const marginTopBottom = Number(margin);
    const leftMarginMm = Number(leftMargin);
    const rightMarginMm = Number(rightMargin);

    // 计算可用区域
    const availableHeightMm = a4HeightMm - 2 * marginTopBottom;
    const availableWidthMm = a4WidthMm - leftMarginMm - rightMarginMm;

    // 获取 DPI 和像素转换
    const dpi = getDPI();
    const availableWidthPx = mmToPixel(availableWidthMm, dpi);
    const availableHeightPx = mmToPixel(availableHeightMm, dpi);

    // 渲染 HTML 元素到 Canvas,使用更完整的配置
    const canvas = await html2canvas(element, {
      scale: 2,
      useCORS: true,
      logging: false,
      backgroundColor: '#ffffff',
      removeContainer: true,
      width: element.offsetWidth,
      height: element.scrollHeight,
      windowWidth: Math.max(element.scrollWidth, element.offsetWidth),
      windowHeight: Math.max(element.scrollHeight, element.offsetHeight),
      x: 0,
      y: 0,
      scrollX: 0,
      scrollY: 0,
      allowTaint: true,
      // letterRendering: true,
    });
    // const ctx: any = canvas.getContext('2d');
    const pdf = new jsPDF('p', 'mm', 'a4');

    let yOffset = 0;
    const totalHeight = canvas.height;
    const pages: Array<{ imgData: string; imgHeightInPdf: number }> = [];

    // 拆分图片并存储到数组
    while (yOffset < totalHeight) {
      let sliceHeight = availableHeightPx;

      if (yOffset + sliceHeight > totalHeight) {
        sliceHeight = totalHeight - yOffset;
      }

      // 创建切片画布并绘制内容
      const sliceCanvas = document.createElement('canvas');
      sliceCanvas.width = availableWidthPx;
      sliceCanvas.height = sliceHeight;
      const sliceCtx: any = sliceCanvas.getContext('2d');
      sliceCtx.drawImage(
        canvas,
        0,
        yOffset,
        Math.min(availableWidthPx, canvas.width),
        sliceHeight,
        0,
        0,
        Math.min(availableWidthPx, canvas.width),
        sliceHeight,
      );

      // 转换为图片数据
      const imgData = sliceCanvas.toDataURL('image/png');
      // 正确计算PDF中的图片高度,保持原始宽高比
      const imgWidthPx = Math.min(availableWidthPx, canvas.width);
      const imgHeightInPdf = (sliceHeight * availableWidthMm) / imgWidthPx;
      pages.push({ imgData, imgHeightInPdf });

      yOffset += sliceHeight;
    }

    const totalPages = pages.length;

    // 将所有页面添加到 PDF
    pages.forEach((page, index) => {
      if (index > 0) {
        pdf.addPage();
      }
      // 计算实际可用宽度,确保图片不会超出页面
      const actualWidth = Math.min(availableWidthMm, 210 - leftMarginMm - rightMarginMm);
      // 计算水平居中位置
      const centerX = (210 - actualWidth) / 2;
      pdf.addImage(page.imgData, 'PNG', centerX, marginTopBottom, actualWidth, page.imgHeightInPdf);

      // 添加页码
      pdf.setFontSize(10);
      // 页码居中显示
      pdf.text(`${index + 1} / ${totalPages}`, 105, a4HeightMm - 10, {
        align: 'center',
      });
    });

    // 生成 PDF
    const pdfBlob = pdf.output('blob');

    // 根据返回类型选择返回 Blob 或 File
    if (returnType === 'file') {
      // 创建 File 对象
      const pdfFile = new File([pdfBlob], fileName, { type: 'application/pdf' });
      return pdfFile;
    } else {
      // 返回 Blob 对象
      return pdfBlob;
    }
  } catch (error) {
    console.error('生成 PDF 文件对象时出错:', error);
    throw error;
  }
}

3-在 Vue 组件中使用导出方法

javascript 复制代码
<template>
    <div
      id="supervisionNoticeModel"
      style="
        position: absolute;
        left: -9999px;
        top: -9999px;
        width: 100%;
        height: auto;
        overflow: visible;
      "
    >
      <div class="max-w-3xl mx-auto bg-white p-8" style="width: 210mm; padding: 10mm">
        <!-- 标题区域 -->
        <div class="text-center mb-8">
          <h1 class="text-2xl font-bold mb-2">你的内容</h1>
          <h2 class="text-xl font-bold mb-4">你的内容</h2>
          <p class="text-lg font-semibold mt-10">你的内容</p>
          <div class="flex justify-between mt-6">
            <div class="justify-center w-full">
              <p>你的内容</p>
              <p>你的内容</p>
              <p>你的内容</p>
              <p>你的内容</p>
            </div>
          </div>
        </div>
    </div>
</template>
<script lang="ts" setup>
  import { ref } from 'vue';
  import { generateReport, getPdfFileByElementId } from '你的路径';

  // 你的事件
  async function submitForm() {
    // 对应传递参数请在你生成的ts文件中查看
    // 导出pdf
    generateReport(20, 8, 0, '测试文件.pdf', 'supervisionNoticeModel');
    // 导出文件流
    getPdfFileByElementId(20, 8, 0, 'supervisionNoticeModel', 'file', '测试文件.pdf')
  }
</script>

以上通过使用 jspdf 和 html2canvas 库,我们可以在 Vue 项目中轻松实现 HTML 导出为 PDF 的功能以及转换为文件流。

相关推荐
郝学胜-神的一滴2 小时前
Vue国际化(i18n)完全指南:原理、实践与最佳方案
前端·javascript·vue.js·程序人生·前端框架
幻云20102 小时前
Python深度学习:从筑基与巅峰
前端·javascript·vue.js·人工智能·python
334554322 小时前
vue表格遍历根据表头里的类型和每行的状态来判断
javascript·vue.js·chrome
幻云201011 小时前
Python深度学习:从筑基到登仙
前端·javascript·vue.js·人工智能·python
爱吃泡芙的小白白13 小时前
Vue 3 核心原理与实战:从响应式到企业级应用
前端·javascript·vue.js
JosieBook15 小时前
【Vue】12 Vue技术—— Vue 事件修饰符详解:掌握事件处理的高级技巧
前端·javascript·vue.js
刘一说17 小时前
Vue3 模块语法革命:移除过滤器(Filters)的深度解析与迁移指南
前端·vue.js·js
Trae1ounG18 小时前
这是什么dom
前端·javascript·vue.js
5134959218 小时前
在Vue.js项目中使用docx和file-saver实现Word文档导出
前端·vue.js·word