技术:htmlcanvas和jspdf
可实现效果:不切割pdf
效果图:
<template>
<div style="background: #f5f5f5;">
<div class="flex-end button-wrap">
<el-button
:disabled="loading"
@click="handlePrint">
打印
</el-button>
</div>
<div
v-loading.body.fullscreen="loading"
class="printForm"
>
<div id="form-main">
<div style="margin-top:32px" />
<template v-for="item in 25">
<div
:key="item"
class="item">
<div
class="line"
:style="{'min-height':item %2 ?'160px' : '40px'}">
<span style="color:red">{{ item }}</span>
<span> {{ deptLeaderSignTime }}{{ deptLeaderSignTime }}</span>
<div
v-if="item==11"
style="min-height:800px">
{{ deptLeaderSignTime }}{{ deptLeaderSignTime }}
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script>
import zhengli from "./zhengli.js";
export default {
mixins: [zhengli],
data() {
return {
isPrint: false,
loading: false,
deptLeaderSignTime:'广东省工业和信息化厅发文表广东省工业和信息化厅发文表广东省工业和信息化厅发文表广东省工业和信息化厅发文表'
};
},
computed: {},
async created() {
this.loading = true
this.$nextTick(async()=>{
this.convertPdf("打印单", document.querySelector("#form-main"), false);
});
},
async mounted() {
window.addEventListener("afterprint", e => {
location.reload();
});
window.addEventListener("message", (e) => {
this.isPrint = false
this.$forceUpdate()
});
},
beforeDestroy() {
window.removeEventListener("afterprint",'');
},
methods: {
}
};
</script>
<style scoped>
.flex-end {
display: flex;
justify-content: flex-end;
}
.button-wrap {
height: 60px;
box-sizing: border-box;
padding: 8px 24px;
background: #fff;
}
#form-main {
width: 1200px;
margin: 0 auto;
padding: 24px;
box-sizing: border-box;
position: relative;
font-size: 16px;
line-height: 24px;
}
.printForm {
background: #ffff;
margin: 16px 24px;
height:calc(100vh - 100px);
overflow:auto
}
.line {
padding: 10px;
border: 1px solid #000;
}
.item+.item .line{
border-top: 0px solid transparent;
}
</style>
// zhengli.js
import html2Canvas from 'html2canvas';
import JsPDF from 'jspdf';
const zhengli = {
data() {
return {
pdfFile: null,
canvasHeight: 0,
imgHeight:0,
setPadingIndex: [], //由于分页导致需要增加padding的节点的序号,后续为了把padding改回去
};
},
methods: {
async convertPdf(title, html, isBlob) {
// 2400是需要打印的html表单的宽度一半(我这里设置了1200px,这个值得根据具体情况修改),因为html2Canvas增加了分辨力一倍
this.imgHeight = Math.floor((280 * 2400) / 180) - 30
// res:这里记录最后一页的节点距离顶部的值,只要用到改数组的长度来决定是否需要新增一页pdf
let res = []
if( this.isPrint ) {
res =this.setNodeStyle( )
}
return new Promise((resolve) => {
html2Canvas(html, {
//allowTaint:是否允许图片跨域
allowTaint: false,
taintTest: false,
logging: false,
//useCORS:是否尝试从服务器中使用cors来加载图片
useCORS: true,
windowWidth: '820px',
// dpi:将分辨率提高到特定的DPI
dpi: window.devicePixelRatio * 2,
scale: 2 // 按比例增加分辨率
}).then((canvas) => {
this.pdfFile = new JsPDF('p', 'mm', 'a4'); // A4纸,纵向
const ctx = canvas.getContext('2d');
const a4w = 180;
const a4h = 280;
// imgHeight:按A4显示比例换算一页图像的像素高度
const imgHeight = this.imgHeight;
let top = canvas.height
let renderedHeight = 0;
res.forEach((item,index) =>{
const page = document.createElement('canvas');
page.width = canvas.width;
page.height = Math.min(imgHeight, top - renderedHeight);
// 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
let imageData = ctx.getImageData(
0,
renderedHeight,
canvas.width,
Math.min(imgHeight, top - renderedHeight)
)
for(var i = 0; i < imageData.data.length; i += 4) {
// 当该像素是透明的,则设置成白色
if(imageData.data[i + 3] == 0) {
imageData.data[i] = 255;
imageData.data[i + 1] = 255;
imageData.data[i + 2] = 255;
imageData.data[i + 3] = 255;
}
}
page.getContext('2d').putImageData(
imageData,
0,
0,
);
/* addImage:
1、image:表示要插入的图片资源,可以是图片文件的路径或者base64编码字符串。
2、format:表示要插入的图片格式,包括:‘JPEG’, ‘PNG’, ‘GIF’, ‘BMP’, ‘TIFF’, ‘RAW’, ‘JPEG2000’。
3、x:图片在PDF中的x轴坐标,单位为pt(点)。
4、y:图片在PDF中的y轴坐标,单位为pt(点)。
5、width:图片在PDF中的宽度,单位为pt(点)。
6、height:图片在PDF中的高度,单位为pt(点)。
7、alias(可选):指定图片资源的别名。
8、compression(可选):指定图片的压缩质量,取值为0-1之间的浮点数。
9、rotation(可选):指定图片的旋转角度,取值范围为0-360之间的整数。
*/
this.pdfFile.addImage(
page.toDataURL('image/jpeg', 1),
'JPEG',
15,
10,
a4w,
Math.min(a4h, (a4w * page.height) / page.width)
);
renderedHeight += imgHeight
if(res[index+1]) {
this.pdfFile.addPage(); // 如果后面还有内容,添加一个空页
}
})
// 保存文件
if (isBlob) {
this.pdfFile.save(`${title}.pdf`);
}
// 获取 base64
const pdfData = this.pdfFile.output('datauristring');
this.loading = false
resolve(pdfData);
});
});
},
// 设置分页地方的边框和padding
setNodeStyle() {
let height = 0
let data = []
let count = 1
// element: items是每一行的样式名,作为不可被切割的部分。
let element = document.getElementsByClassName("item")
this.setPadingIndex = []
for(let i = 0; i<element.length;i++) {
height = (element[i].offsetTop+element[i].offsetHeight)*2
if( height > this.imgHeight*count) {
let bott = (this.imgHeight*count -(element[i-1].offsetTop+element[i-1].offsetHeight)*2)/2
element[i-1].style.paddingBottom = bott+15 +'px'
// 这里需要依照自己的样式设置需要增加边框的地方
element[i].style.borderTop= '1px solid #000'
// setPadingIndex:记录这个是为了出现打印弹框之后能及时的把表单样式复原
this.setPadingIndex.push(i-1)
data.push((element[i-1].offsetTop+element[i-1].offsetHeight)*2)
count+=1
if( i == element.length-1 ) {
data.push((element[i].offsetTop+element[i].offsetHeight)*2)
}
} else if( i == element.length-1 ) {
data.push((element[i].offsetTop+element[i].offsetHeight)*2)
}
}
return data
},
async handlePrint() {
this.isPrint = true
this.loading = true
this.$nextTick( async()=>{
this.pdfFile = null
// #form-main 是需要打印的html区域
await this.convertPdf("打印单", document.querySelector("#form-main"), false)
let pdfFlie = this.pdfFile.output('datauristring');
let printFile = this.dataURLtoFile(pdfFlie, 'pdf文件.pdf');
this.pdfUrl = window.URL.createObjectURL(new Blob([printFile], { type: 'application/pdf' }));
var date = new Date().getTime();
var ifr = document.createElement('iframe');
ifr.style.frameborder = 'no';
ifr.style.display = 'none';
ifr.style.pageBreakBefore = 'always';
ifr.setAttribute('id', 'printPdf' + date);
ifr.setAttribute('name', 'printPdf' + date);
ifr.src = this.pdfUrl;
document.body.appendChild(ifr);
this.loading = false
// 打印弹窗
this.doPrint('printPdf' + date);
// 释放URL 对象
window.URL.revokeObjectURL(ifr.src);
setTimeout(()=>{
//把分页的位置复原回去
let element = document.getElementsByClassName("item")
for(let i = 0; i<element.length;i++) {
if(this.setPadingIndex.includes(i)) {
element[i].style.paddingBottom = 0
element[i+1].style.borderTop= '0px solid #000'
}
}
this.isPrint = false
this.$forceUpdate()
},1000)
})
},
doPrint(val) {
var ordonnance = document.getElementById(val).contentWindow;
setTimeout(() => {
this.isPrint = true
ordonnance.print();
this.pdfLoading = false;
while(true) {
// 判断,找到要找的父window,可以通过在父的window上绑定属性来实现
if (ordonnance.isParent = true) {
ordonnance.postMessage('isPrint', "*")
}
if(ordonnance == window.top) {
break;// 防止死循环
} else {
ordonnance = ordonnance.parent;
}
}
}, 100);
},
// 转成文件流
dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
},
}
};
export default zhengli;