首先项目中用的是elementui,需要导出的是一个table和一些固定的头部信息组成的一个页面
页面大概长这样:
废话不多说,上代码,大佬求轻喷~
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import html2Canvas from "html2canvas";
import jsPdf from "jspdf";
@Component({ name: "Download2pdf" })
export default class Download2pdf extends Vue {
@Prop({ type: Boolean }) plain?: true;
@Prop({ type: String }) btnText?: "Download PDF";
@Prop({ type: String }) cardId?: ""; //整个dom id
@Prop({ type: String }) tableId?: ""; //需要动态计算的dom id
@Prop({ type: String }) topFixedId?: ""; //上方固定高度的dom id
@Prop({ type: String }) bottomFixedId?: ""; //底部固定高度的dom id
@Prop({ type: String }) fileName?: "fileName";
private scale = 2;
download() {
//querySelector("tr") className
const trs = (
document.getElementById(this.tableId as string) as HTMLElement
).querySelectorAll("tr");
const fixedWidth = (
document.getElementById(this.topFixedId as string) as HTMLElement
).offsetWidth;
// 28 / 45 margin高度 ---- 固定高度
const topFixedHeight =
(document.getElementById(this.topFixedId as string) as HTMLElement)
.offsetHeight + 28;
const botFixedHeight =
(document.getElementById(this.bottomFixedId as string) as HTMLElement)
.offsetHeight + 45;
// return;
html2Canvas(document.getElementById(this.cardId as string) as HTMLElement, {
scale: this.scale, // 提升画面质量,但是会增加文件大小
useCORS: true
}).then((canvas: any) => {
const contentWidth = canvas.width;
const contentHeight = canvas.height;
console.log(canvas);
// console.log("contentWidth",contentWidth) = 2倍的页面元素宽高
//a4纸的尺寸pt [595.28,841.89]
const a4Width = 595.28;
const a4Height = 841.89;
//一页pdf能显示多大的canvas元素
var pageHeight = (contentWidth / a4Width) * a4Height; // 单位px
console.log("pageHeight", pageHeight);
//未生成pdf的html页面高度
let leftHeight = contentHeight;
//针对单个页面偏移
let position = 0;
//针对整个图片的偏移
let hasPos = 0;
//html页面生成的canvas在pdf中图片的宽高
const imgWidth = a4Width;
const imgHeight = (a4Width / contentWidth) * contentHeight; //单位pt
let pad = 60; //a4纸上间距60
const pad2 = 20; //a4纸下间距20, 为了分页截取的图片有上边距,下面有个15的偏移量
const empty = 60; //表格无数据 nodata高度60
let dom2a4 = (h: number): number => { // px转pt
return (h * this.scale) / (pageHeight / a4Height);
};
let pt2canvasH = (h: number): number => { //pt转px
return (contentWidth / a4Width) * h;
};
const topFixedImgHeight = dom2a4(topFixedHeight); //页面上方固定的高
const botFixedImgHeight = dom2a4(botFixedHeight); //页面下方固定的高
const pageData = canvas.toDataURL("image/jpeg", 1.0);
let pdf: any = new jsPdf("p", "pt", "a4");
//分页
if (leftHeight < pageHeight) {
console.log(1);
pdf.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
console.log("addImage");
} else {
console.log(2);
let nowH = topFixedImgHeight;
for (let i = 0; i < trs.length; i++) {
let _h = trs[i].offsetHeight;
//当前tr和下一个tr都是空样式--代表此表格是空, 或者当前tr是空,没有下一个tr了,也代表此表格是空
if (
trs[i].className === "" &&
((trs[i + 1] && trs[i + 1].className === "") ||
i === trs.length - 1)
) {
_h = _h + empty;
}
if (nowH + dom2a4(_h) < a4Height - dom2a4(pad + pad2)) {
nowH += dom2a4(_h);
if (i === trs.length - 1) {
if (nowH + botFixedImgHeight > a4Height - dom2a4(pad + pad2)) {
let clipImgData = this.cropCanvas(
canvas,
contentWidth,
contentHeight,
pt2canvasH(nowH + dom2a4(pad)), //针对整个dom生成的canvas 需要截取的高度
pt2canvasH(hasPos) //截取的起始纵坐标位置
);
pdf.addImage(clipImgData, "JPEG", 0, 15, imgWidth, imgHeight);
pdf.addPage();
position = nowH + dom2a4(pad);
hasPos += position;
pdf.addImage(pageData, "JPEG", 0, -hasPos, imgWidth, imgHeight);
} else {
let clipImgData = this.cropCanvas(
canvas,
contentWidth,
contentHeight,
pt2canvasH(nowH + dom2a4(pad) + botFixedImgHeight), //针对整个dom生成的canvas 需要截取的高度
pt2canvasH(hasPos) //截取的起始纵坐标位置
);
pdf.addImage(clipImgData, "JPEG", 0, 15, imgWidth, imgHeight);
console.log("addImage");
}
}
} else {
position = nowH + dom2a4(pad);
let clipImgData = this.cropCanvas(
canvas,
contentWidth,
contentHeight,
pt2canvasH(nowH + dom2a4(pad)), //针对整个dom生成的canvas 需要截取的高度
pt2canvasH(hasPos) //截取的起始纵坐标位置
);
pdf.addImage(clipImgData, "JPEG", 0, 15, imgWidth, imgHeight);
console.log("addImage");
hasPos += position;
nowH = dom2a4(_h);
console.log(3);
pdf.addPage();
pad = 0;
console.log("addPage");
if (i === trs.length - 1) {
pdf.addImage(pageData, "JPEG", 0, -hasPos, imgWidth, imgHeight);
}
}
}
}
pdf.save(`${this.fileName} Payslip.pdf`);
});
}
cropCanvas(
canvas: any,
contentWidth: any,
contentHeight: any,
positionTop: any,
hasPos: any
) {
var ctx = canvas.getContext("2d");
// 新canvas控件- 保存裁剪后的图片
var newCanvas = document.createElement("canvas");
var newCtx: any = newCanvas.getContext("2d");
newCanvas.setAttribute("width", contentWidth);
newCanvas.setAttribute("height", contentHeight);
//导出的pdf默认黑色背景,需要用白色填充
newCtx.fillStyle = "#FFFFFF";
newCtx.fillRect(0, 0, contentWidth, contentHeight);
//hasPos-3 防止截取掉表格title的最上方1px
var imgRgbData = ctx.getImageData(0, hasPos > 0 ? (hasPos-3) : hasPos, contentWidth, positionTop);
console.log("positionTop=", positionTop, " hasPos=", hasPos);
// 把裁剪后的像素数据渲染到新canvas
newCtx.putImageData(imgRgbData, 0, 0);
// 获取裁剪后图片的base64数据
var imgBase64 = newCanvas.toDataURL("image/jpeg", 1.0);
return imgBase64;
}
}
</script>
整体分页思想是调用cropCanvas函数去动态截取canvas的内容。
tips:为什么要截取呢?因为分页的时候可能会出现一行文字被截断了
注意点:
1、截取函数中canvas转base64时用toDataURL会出现跨域情况,服务端处理一下就好。
2、代码里面FixHeight都是业务相关布局端固定高度。
3、导入到pdf里面其实每一页都是一个图片,是通过putImageData截取的,content高度应该维持不变,否则会出现图像尺寸拉伸/扁平的情况。
4、代码注释都有写,欢迎交流~