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

被折叠的 条评论
为什么被折叠?



