Technology: htmlcanvas and jspdf
Achievable effect: no cutting of pdf
Rendering:
<template> <div style="background: #f5f5f5;"> <div class="flex-end button-wrap"> <el-button :disabled="loading" @click="handlePrint"> Print </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:'The Guangdong Provincial Department of Industry and Information Technology issued the document form The Guangdong Provincial Department of Industry and Information Technology issued the document form The Guangdong Provincial Department of Industry and Information Technology issued the document form The Guangdong Provincial Department of Industry and Information Technology issued the document form' }; }, computed: {}, async created() { this.loading = true this.$nextTick(async()=>{ this.convertPdf("Print form", 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: [], //Due to paging, the serial number of the padding node needs to be increased. In order to change the padding back later, }; }, methods: { async convertPdf(title, html, isBlob) { // 2400 is half the width of the html form that needs to be printed (I set 1200px here, which is worth modifying according to the specific situation), because html2Canvas doubles the resolution this.imgHeight = Math.floor((280 * 2400) / 180) - 30 // res: The distance between the node of the last page and the top is recorded here. You only need to change the length of the array to determine whether you need to add a new page of pdf. let res = [] if( this.isPrint ) { res =this.setNodeStyle( ) } return new Promise((resolve) => { html2Canvas(html, { //allowTaint: whether to allow images to cross domains allowTaint: false, tainTest: false, logging: false, //useCORS: Whether to try to use cors from the server to load images useCORS: true, windowWidth: '820px', // dpi: Increase the resolution to a specific DPI dpi: window.devicePixelRatio * 2, scale: 2 // Increase resolution proportionally }).then((canvas) => { this.pdfFile = new JsPDF('p', 'mm', 'a4'); // A4 paper, portrait const ctx = canvas.getContext('2d'); const a4w = 180; const a4h = 280; // imgHeight: Convert the pixel height of a page image according to the A4 display ratio 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); //Use getImageData to crop the specified area and draw it into the canvas object created previously let imageData = ctx.getImageData( 0, renderedHeight, canvas.width, Math.min(imgHeight, top - renderedHeight) ) for(var i = 0; i < imageData.data.length; i + = 4) { // When the pixel is transparent, set it to white 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: Indicates the image resource to be inserted, which can be the path of an image file or a base64 encoded string. 2. format: Indicates the image format to be inserted, including: JPEG’, PNG’, GIF’, BMP’, TIFF’, RAW’, JPEG2000’. 3. x: The x-axis coordinate of the image in PDF, in pt (point). 4. y: The y-axis coordinate of the image in PDF, in pt (point). 5. Width: The width of the image in PDF, in pt (points). 6. Height: The height of the image in PDF, in pt (points). 7. alias (optional): Specify the alias of the image resource. 8. Compression (optional): Specify the compression quality of the image, the value is a floating point number between 0-1. 9. Rotation (optional): Specify the rotation angle of the image, the value range is an integer between 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 there is content later, add an empty page } }) // save document if (isBlob) { this.pdfFile.save(`${title}.pdf`); } // Get base64 const pdfData = this.pdfFile.output('datauristring'); this.loading = false resolve(pdfData); }); }); }, //Set the border and padding of the paging place setNodeStyle() { let height = 0 let data = [] let count = 1 // element: items is the style name of each row, as a non-cuttable part. 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' //Here you need to set the places where you need to add borders according to your own style. element[i].style.borderTop= '1px solid #000' // setPadingIndex: This is recorded so that the form style can be restored in time after the print pop-up box appears. 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 is the html area that needs to be printed await this.convertPdf("Print form", document.querySelector("#form-main"), false) let pdfFlie = this.pdfFile.output('datauristring'); let printFile = this.dataURLtoFile(pdfFlie, 'pdf file.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 //Print pop-up window this.doPrint('printPdf' + date); // Release the URL object window.URL.revokeObjectURL(ifr.src); setTimeout(()=>{ //Restore the paging position back 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) { // Determine and find the parent window you are looking for. This can be achieved by binding attributes on the parent window. if (ordonnance.isParent = true) { ordonnance.postMessage('isPrint', "*") } if(ordonnance == window.top) { break; // Prevent infinite loop } else { ordonnance = ordonnance.parent; } } }, 100); }, // Convert to file stream 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;