说明
jsPDF可以将H5转成PDF(其他文件流也可)
代码
/**
target 容器
pdfName 文件名
*/
function exportPdf(target,pdfName){
// 解决背景黑色问题
$(target).css({'display':'block','background':'#FFFFFF'})
// 解决滚动条之外,也就是不是可视区域黑屏问题
var copyDom = $(target).clone();
copyDom.width( $(target).width() + "px");
copyDom.height( $(target).height() + "px");
$('body').append(copyDom);
// 转PDF
html2canvas(copyDom, {
onrendered: function(canvas) {
var contentWidth = canvas.width;
var contentHeight = canvas.height;
// 一页pdf显示html页面生成的canvas高度;
var pageHeight = contentWidth / 592.28 * 841.89;
// 未生成pdf的html页面高度
var leftHeight = contentHeight;
// 页面偏移
var position = 0;
// a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
var imgWidth = 595.28;
var imgHeight = 592.28/contentWidth * contentHeight;
var pageData = canvas.toDataURL('image/jpeg', 1.0);
// l表示横版,p:纵向 (默认纵向)
var pdf = new jsPDF('', 'pt', 'a4');
// 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
// 当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < pageHeight) {
pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
} else {
while (leftHeight > 0) {
pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
leftHeight -= pageHeight;
position -= 841.89;
//避免添加空白页
if (leftHeight > 0) {
pdf.addPage();
}
}
}
// 对pdf操作,根据业务需求来,注意1与2不能同时存在,否则后执行的导出的pdf在wps无法打开,显示文件损坏
// 如果需要对同一个pdf既需要本地下载,又需要发给后台,请导出两次,一次本地下载,一次发给后台
// 1.本地下载
pdf.save(`${pdfName}.pdf`,pdf)
// 2.传给后台 文件流形式
const pdfFile = pdf.output('blob');
// 解决blob给文件命名问题 暂时不知道其他方法
var file = new File([pdfFile], `${pdfName}.pdf`, {type: 'application/pdf', lastModified: Date.now()});
formDataPDF.append('files', file);
// 2. 传给后台 base64
const datauri = pdf.output('dataurlstring');
// 去掉前面的字符串后,就是文件的加密字符串
const base64 = datauri.substring(28);
// 然后把这个字符串送到后台解密
后台:
主要用BASE64Decoder解密,放到文件流里
// 2.传给后台
// 将base64转成二进制
copyDom.remove();
}
})
注意点
1.导出的pdf空白情况
因为容器盒子宽高获取不对,且该盒子不能设置display:none; 保证其显示在页面
2.可视区域外黑屏情况
代码中有解决办法,此外注意导出pdf时,jsPDF会将页面回到顶部,我们可以先执行回到顶部,再去导出PDF(先回到顶部,再执行exportPdf反函数)
页面:html body 设置scrollTop = 0
如果是div盒子等容器: 给css overflow:auto 的元素设置scrollTop = 0,这种情况一般是容器的父元素
3.导出pdf是异步事件
4.将css写在需要导出成pdf的元素内,写在更外层则识别不了,会导致导出的pdf样式不生效
5.本地下载传给后台功能如果要同时实现,那么只能分开,及对同一个需要导出的html盒子导出两次,一次本地下载,一次传给后台,如果写在一起了,那么有一方的pdf在wps打不开,浏览器可以打开
项目中实际场景对应的特殊情况及解决
场景:
tab动态生成,点击已阅表单一键导出PDF,不确定需要具体导出的表单 只能根据循环tab来明确其对应需要导出的pdf,这里还得保证表单可视,以及注意导出是异步事件

解决
思路:根据tab数组,生成一个记录tab对应的表单及其是否已经完成导出的状态数组,for循环判断状态,如果未导出则执行导出函数,并break;此时在导出函数结束出更改对应状态信息并且判断是否完成所有导出,没有则继续,有则ok
// 最后一步
function getPDF(){
let target = '';
let local = ''; // 0 是本地下载 1是append formData
for(let i = 0; i<pdfArr.length;i++){
if (!pdfArr[i].finished0[0]) {
arrIndex[0]=i;
arrIndex[1]='finished0';
if(pdfArr[i].rootBox!='fxzl') {
$('.level1nav li').eq(i).click();
$(`.${pdfArr[i].rootBox} .level2nav li`).eq(0).click();
target = document.querySelectorAll(`.${pdfArr[i].rootBox} .table1`)[0];
local = 0;
} else {
$('.zlBtn').click();
target = document.querySelector('.fxzl.table1');
local = 0;
}
break;
} else if (!pdfArr[i].finished0[1]) {
arrIndex[0]=i;
arrIndex[1]='finished0';
if(pdfArr[i].rootBox!='fxzl') {
$('.level1nav li').eq(i).click();
$(`.${pdfArr[i].rootBox} .level2nav li`).eq(0).click();
target = document.querySelectorAll(`.${pdfArr[i].rootBox} .table1`)[0];
local = 1;
} else {
$('.zlBtn').click();
target = document.querySelector('.fxzl.table1');
local = 1;
}
break;
} else if (!pdfArr[i].finished1[0]) {
arrIndex[0]=i;
arrIndex[1]='finished1';
if(pdfArr[i].rootBox!='fxtzd') {
$('.level1nav li').eq(i).click();
$(`.${pdfArr[i].rootBox} .level2nav li`).eq(1).click();
target = document.querySelectorAll(`.${pdfArr[i].rootBox} .table1`)[1];
local = 0;
} else {
$('.tzdBtn').click();
target = document.querySelector('.fxtzd.table1');
local = 0;
}
break;
} else if (!pdfArr[i].finished1[1]) {
arrIndex[0]=i;
arrIndex[1]='finished1';
if(pdfArr[i].rootBox!='fxtzd') {
$('.level1nav li').eq(i).click();
$(`.${pdfArr[i].rootBox} .level2nav li`).eq(1).click();
target = document.querySelectorAll(`.${pdfArr[i].rootBox} .table1`)[1];
local = 1;
} else {
$('.tzdBtn').click();
target = document.querySelector('.fxtzd.table1');
local = 1;
}
break;
}
}
// 获取文件名
let pdfName = target.querySelector('td.pdfName').innerText;
pdfName = pdfName.replace(/[ ]/g,"");
// 先回到页面顶部
$(target).parent().animate({scrollTop:0},60);
setTimeout(function(){
exportPdf(target,pdfName,local)
},100)
}
function exportPdf(target,pdfName,local){
$(target).css({'display':'block','background':'#FFFFFF'})
var copyDom = $(target).clone();
copyDom.width( $(target).width() + "px");
copyDom.height( $(target).height() + 1 + "px");
$('body').append(copyDom);
html2canvas(copyDom, {
onrendered: function(canvas) {
// debugger
var contentWidth = canvas.width;
var contentHeight = canvas.height;
//一页pdf显示html页面生成的canvas高度;
// var pageHeight = contentWidth / 841.89 * 592.28;
var pageHeight = contentWidth / 592.28 * 841.89;
//未生成pdf的html页面高度
var leftHeight = contentHeight;
//页面偏移
var position = 0;
//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
var imgWidth = 595.28;
var imgHeight = 592.28/contentWidth * contentHeight;
// var imgWidth = 841.89;
// var imgHeight = 841.89 / contentWidth * contentHeight;
var pageData = canvas.toDataURL('image/jpeg', 1.0);
//l表示横版,p:纵向 (默认纵向)
var pdf = new jsPDF('', 'pt', 'a4');
//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
//当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < pageHeight) {
pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
} else {
while (leftHeight > 0) {
pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
leftHeight -= pageHeight;
// leftHeight = pageHeight;
position -= 841.89;
// position -= 595.28;
//避免添加空白页
if (leftHeight > 0) {
pdf.addPage();
}
}
}
if (local){
// 传给后台
const pdfFile = pdf.output('blob');
var file = new File([pdfFile], `${pdfName}.pdf`, {type: 'application/pdf', lastModified: Date.now()});
formDataPDF.append('files', file);
} else {
// 本地下载
pdf.save(`${pdfName}.pdf`,pdf)
}
// 状态改变及判断
numPDF += 0.5;
pdfArr[arrIndex[0]] [arrIndex[1]][local] = true
copyDom.remove();
if(numPDF != allPDF) getPDF()
else submitPdf()
}
})
}
2021-07-21补充
1.给导出的PDF添加指定边距
function exportPdf(target){
// target 为需要导出PDF的DOM元素
$(target).css({'background':'#FFFFFF'})
var copyDom = $(target).clone();
copyDom.width( $(target).width() + "px");
copyDom.height( $(target).height() + "px");
$('body').append(copyDom);
html2canvas(copyDom, {
onrendered: function(canvas) {
var pdf = new jsPDF('p', 'mm', 'a4'); // A4纸,纵向
var ctx = canvas.getContext('2d'),
a4w = 170, a4h = 237, // A4大小,210mm x 297mm,保留左右20mm边距 上下30mm的边距,显示区域170x237
imgHeight = Math.floor(a4h * canvas.width / a4w), //按A4显示比例换算一页图像的像素高度
renderedHeight = 0;
while(renderedHeight < canvas.height) {
var page = document.createElement("canvas");
page.width = canvas.width;
page.height = Math.min(imgHeight, canvas.height - renderedHeight);//可能内容不足一页
//用getImageData剪裁指定区域,并画到前面创建的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', 20, 30, a4w, Math.min(a4h, a4w * page.height / page.width)); // 添加图像到页面,保留左右20mm边距 上下30mm
renderedHeight += imgHeight;
if(renderedHeight < canvas.height)
pdf.addPage();//如果后面还有内容,添加一个空页
delete page;
}
}
})
}
2.设置其导出的PDF刚好在一张A4内
需要将targetDOM元素宽高比例设置成与上述方法显示区域相同比例 即:170/237
3.遗留问题 如何设置左右上下4个不同的边距呢?(上:30mm 下:28mm 左:25mm 右:20mm )
由上1得到启发
210mm = 左边距 + 显示区域宽a4w + 右边距;
297mm = 上边距 + 显示区域宽a4h + 下边距。
那么
210mm = 25mm + a4w(165) + 20mm;
297mm = 30mm + a4h(239) + 28mm。
代码
a4w = 165, a4h = 239,
pdf.addImage(page.toDataURL('image/jpeg', 1.0),
'JPEG', 25, 30, a4w, Math.min(a4h, a4w * page.height / page.width));
// 左边距25 上边距30 那么右边距与下边距 jspdf会根据a4w-左边距 a4h-上边距 得到