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 的功能以及转换为文件流。