vue项目页面的打印和下载PDF加loading效果的实现(加水印)
作者:牛先森家的牛奶
这篇文章主要介绍了vue项目页面的打印和下载PDF加loading效果的实现(加水印),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
vue页面的打印和下载PDF(加水印)
vue项目页面的打印
打印的不用说,调用 window.print()
的方法即可;
注意点:如果用到背景图的话,需要CSS中添加设置;
// 标签看哪些地方用到背景图就加哪些,不然调打印机会把背景图隐藏掉 div { // webkit 为Google Chrome Safari 等浏览器内核 -webkit-print-color-adjust: exact; print-color-adjust: exact; color-adjust: exact; }
vue项目页面下载PDF
封装代码如下:
新建utils/pdf.js文件;
import html2canvas from "html2canvas" import jsPDF from "jspdf" export const downloadPDF = (el, title) => { html2canvas(el, { allowTaint: true, useCORS: true, dpi: 120, // 图片清晰度问题 background: '#FFFFFF', //如果指定的div没有设置背景色会默认成黑色 }).then(canvas => { // 未生成pdf的html页面高度 let leftHeight = canvas.height //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277 let a4Width = 595.28 let a4Height = 841.89 //一页pdf显示html页面生成的canvas高度; let a4HeightRef = Math.floor((canvas.width / a4Width) * a4Height) //pdf页面偏移 let position = 0 // canvas.toDataURL() 返回一个包含图片展示的 数据URL。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。 // 返回值是一个数据url,是base64组成的图片的源数据、可以直接赋值给图片的src属性。 let pageData = canvas.toDataURL('image/jpeg', 1.0) let pdf = new jsPDF('p', 'pt', 'a4') //A4纸,纵向 // let index = 1 let createCanvas = document.createElement('canvas') let height pdf.setDisplayMode('fullwidth', 'continuous', 'FullScreen') let pdfName = title || "个人简历" function createImpl(canvas) { console.log(leftHeight, a4HeightRef) 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 } createCanvas.width = canvas.width createCanvas.height = height // console.log(index, 'height:', height, 'pos', position) console.log('height:', height, 'pos', position) // getContext()方法可返回一个对象,该对象提供了用于在画布上绘图的方法和属性。 let ctx = createCanvas.getContext('2d') ctx.drawImage( canvas, 0, position, canvas.width, height, 0, 0, canvas.width, height, ) // let pageHeight = Math.round((a4Width / canvas.width) * height) // pdf.setPageSize(null, pageHeight) if (position != 0) { pdf.addPage() } pdf.addImage( createCanvas.toDataURL('image/jpeg', 1.0), 'JPEG', 10, 20, a4Width -30, (a4Width / createCanvas.width) * height -30, ) leftHeight -= height position += height if (leftHeight > 0) { setTimeout(createImpl, 100, canvas) } else { pdf.save(pdfName + '.pdf') } } } //当内容未超过pdf一页显示的范围,无需分页 if (leftHeight < a4HeightRef) { pdf.addImage( pageData, 'JPEG', 10, 20, a4Width - 30, (a4Width / canvas.width) * leftHeight -30, ) pdf.save(pdfName + '.pdf') } else { try { pdf.deletePage(0) setTimeout(createImpl, 100, canvas) } catch (err) { console.log(err) } } }) } // 页面水印 小密 // export const previewWater = (str, str2) => { // console.log(str) // let ctx = document.createElement("canvas") // ctx.width = 800 // ctx.height = 1200 // ctx.style.display = "block" // let cans = ctx.getContext("2d") // cans.rotate((-45 * Math.PI) / 180) // cans.font = "16px Microsoft YaHei" // cans.fillStyle = "rgba(0, 0, 0, 0.3)" // cans.textAlign = "left" // cans.textBaseline = "Middle" // cans.fillText(str, 0, 100) // cans.fillText(str2, 0, 120) // 第二行字体 // cans.save() // return ctx.toDataURL() // } // 页面水印 大中 export const previewWater = (strName, strLink) => { // 创建一个画布 const can = document.createElement('canvas') // 设置画布的长宽 can.width = 500 can.height = 750 const cans = can.getContext('2d') // 旋转角度 canvas旋转不是以图片旋转,而是以画布左上角为原点旋转 cans.rotate((-45 * Math.PI) / 180) cans.translate(0, 0) const txtLen = strName.length // 水印如果都短设置为50px字体,长水印则30px字体 const fontSize = txtLen > 12 ? '30px Simsun' : '40px Simsun' cans.font = fontSize // 设置填充绘画的颜色、渐变或者模式 cans.fillStyle = 'rgba(0, 0, 0, 0.3)' // 设置文本内容的当前对齐方式 cans.textAlign = 'center' // 设置在绘制文本时使用的当前文本基线 cans.textBaseline = 'Middle' // 在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置) cans.fillText(strName, -txtLen * 12, 400) cans.fillText(strLink, -txtLen * 12, 440) // save()方法就是保存你在 cans 中设置的各种样式以及属性 // save()方法相当于将设置的内容隔离出来,不会对外面的任何内容造成影响 cans.save() return can.toDataURL() } /* let createCanvas = document.createElement('canvas') createCanvas.width = width createCanvas.height = height let ctx = createCanvas.getContext('2d') ctx.drawImage( canvas, 0, position, canvas.width, height, 0, 0, canvas.width, height, ) drawImage(image, dx, dy) 在画布指定位置绘制原图 drawImage(image, dx, dy, dw, dh) 在画布指定位置上按原图大小绘制指定大小的图 drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) 剪切图像,并在画布上定位被剪切的部分 image 规定要使用的图像、画布或视频 sx 可选。开始剪切图片的 x 坐标位置 sy 可选。开始剪切图片的 y 坐标位置 sw 可选。被剪切图像的宽度(就是裁剪之前的图片宽度,这里的宽度若小于图片的原宽。则图片多余部分被剪掉;若大于,则会以空白填充) sh 可选。被剪切图像的高度(就是裁剪之前的图片高度) dx 在画布上放置图像的 x 坐标位置 dy 在画布上放置图像的 y 坐标位置 dw 可选。要使用的图像的宽度(就是裁剪之后的图片高度,放大或者缩放) dh 可选。要使用的图像的高度(就是裁剪之后的图片高度,放大或者缩放) */
注意要提前下载 jspdf
和 html2canvas
这两个包,项目中要用到;
项目里面是用另一种方式是把js包下载到本地,动态新建script标签引入,注意这样的方式可以在window的环境下调用方法,要用 window.html2canvas
或者 window.jspdf
等等;
handleCreateScript() { // 动态生成script引入js文件 let html2Canvas = document.createElement('script') html2Canvas.src = `${process.env.VUE_LOCAL_PATH}js/html2canvas.min.js` document.body.appendChild(html2Canvas) let jspdf = document.createElement('script') jspdf.src = `${process.env.VUE_LOCAL_PATH}js/jspdf.umd.min.js` document.body.appendChild(jspdf) } // vue 的 created 钩子函数中调用 created(){ this.handleCreateScript(); } // 封装的jspdf文件中调方法,就不用import 方式 // 可以使用window.html2canvas 或者 window.jspdf
注意说一下水印,因为需求要求水印必须放到页面的上面,而不是页面的下方,只能另辟蹊径了,我用的方法是直接canvas的创建生成方式,用个div定位到页面上面,动态获取内容的高度,然后给这个div加高度、加背景图的方式添加页面水印;
效果如下:
具体代码如下:
<template> <div class="preview-wrapper"> <div class="preview-btn"> <div class="content"> <div class="download-btn"> <div><span @click="handlePrint">打印</span></div> <div><span @click="handleExport">下载</span></div> </div> </div> </div> <div class="user-info" ref="pdfWrapper"> <!-- 内容自己定义 --> ... .... ..... ...... ....... <!-- 水印要放到页面上面 用个div 定位到页面上方--> <div class="water-wrapper" :style="{ backgroundImage: `url(${orgBackground})` }" ></div> </div> </div> </template> <script> //工具方法,导出操作 import { downloadPDF, previewWater } from '@/utils/pdf.js' export default { name: 'userinfo', components: { }, props: {}, data() { return { orgBackground: '', } }, computed: {}, created() {}, mounted() { this.orgBackground = previewWater('XXXX网站', 'http://xxxxxxx.xxx.xx') // 打印的当前元素的内容区域的高度 let userInfoWrapper = document.querySelector('.user-info') console.log(userInfoWrapper, userInfoWrapper.offsetHeight) // 获取水印的遮罩层,因为水印要放到内容区域的上方,而背景图默认是放下方 let waterWrapper = document.querySelector('.water-wrapper') waterWrapper.style.cssText = `height: ${userInfoWrapper.offsetHeight}px` }, methods: { handlePrint() { window.print() }, handleExport() { downloadPDF(this.$refs.pdfWrapper, this.pfdName) } } } </script> <style scoped lang="scss"> div { // webkit 为Google Chrome Safari 等浏览器内核 -webkit-print-color-adjust: 'exact'; print-color-adjust: 'exact'; color-adjust: 'exact'; } .preview-wrapper { // position: relative; z-index: 999; } .user-info { width: 1000px; background: #fff; position: relative; top: 0; left: 0; right: 0; bottom: 0; margin: 0 auto; padding: 0 50px 100px; z-index: 1; } // 水印的样式 .water-wrapper { position: absolute; top: 0; left: 0; width: 1000px; // height: 2237px; // 水印这里的高度不能写死,需要获取上面内容的高度 z-index: 999; } </style>
注意:之前写页面考虑不周到,发现对接接口后,添加水印的高度可能会有问题,尝试发现是父元素高度问题,因为我子元素都是组件的形式,请求数据前高度和请求后高度可能存在偏差和不一样,考虑到vue渲染的问题,导致水印获取父元素的高度不对,大家在对接接口后尝试会发现这个问题,现在改下mounted里面的调用,需要在获取数据后去获取父元素的高度
具体看如下代码:
<template> <div class="preview-wrapper"> <div class="preview-btn"> <div class="content"> <div class="download-btn"> <div><span @click="handlePrint">打印</span></div> <div><span @click="handleExport">下载</span></div> </div> </div> </div> <div class="user-info" ref="pdfWrapper"> <!-- 内容自己定义 --> ... .... ..... ...... ....... <!-- 水印要放到页面上面 用个div 定位到页面上方--> <div class="water-wrapper" :style="{ backgroundImage: `url(${orgBackground})` }" ></div> </div> </div> </template> <script> //工具方法,导出操作 import { downloadPDF, previewWater } from '@/utils/pdf.js' import { dataInfo } from '@/api/userinfo.js' export default { name: 'userinfo', components: {}, props: {}, data() { return { orgBackground: '', // 接口数据 dataInfo: {} } }, computed: {}, watch: { // 注意这里的dataInfo 是调接口查询后的数据 变化时去添加水印 dataInfo(){ // 等待 DOM 渲染后处理 this.$nextTick(() => { this.orgBackground = previewWater('XXXX网站', 'http://xxxxxxx.xxx.xx') // 打印的当前元素的内容区域的高度 let userInfoWrapper = document.querySelector('.user-info') console.log(userInfoWrapper, userInfoWrapper.offsetHeight) // 获取水印的遮罩层,因为水印要放到内容区域的上方,而背景图默认是放下方 let waterWrapper = document.querySelector('.water-wrapper') waterWrapper.style.cssText = `height: ${userInfoWrapper.offsetHeight}px;` }) } }, created() {}, mounted() {}, methods: { handlePrint() { window.print() }, handleExport() { downloadPDF(this.$refs.pdfWrapper, this.pfdName) }, // 调接口数据 handleDatainfo(){ dataInfo().then((res) => { this.dataInfo = res.data }).catch((error) => { console.log(error) }) } } } </script> <style scoped lang="scss"> div { // webkit 为Google Chrome Safari 等浏览器内核 -webkit-print-color-adjust: 'exact'; print-color-adjust: 'exact'; color-adjust: 'exact'; } .preview-wrapper { // position: relative; z-index: 999; } .user-info { width: 1000px; background: #fff; position: relative; top: 0; left: 0; right: 0; bottom: 0; margin: 0 auto; padding: 0 50px 100px; z-index: 1; } // 水印的样式 .water-wrapper { position: absolute; top: 0; left: 0; width: 1000px; // height: 2237px; // 水印这里的高度不能写死,需要获取上面内容的高度 z-index: 999; } </style>
友情提示:对于处理这部分类型的功能,要考虑的地方要注意,比如获取页面后的高度处理,图片的高度问题等等,希望程序猿们遇到问题解决问题!
封装异步PDF下载函数并添加loading效果
因产品需求的需要,当用户下载PDF时发现可能需要等待一段时间,所以加个loading效果;
export const downloadPDFTwo = (el, title) => { return new Promise(async (resolve, reject) => { try { html2canvas(el, { allowTaint: true, useCORS: true, dpi: 120, // 图片清晰度问题 background: '#FFFFFF', //如果指定的div没有设置背景色会默认成黑色 }).then(canvas => { //未生成pdf的html页面高度 let leftHeight = canvas.height console.log(leftHeight) //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277 let a4Width = 595.28 let a4Height = 841.89 //一页pdf显示html页面生成的canvas高度; let a4HeightRef = Math.floor((canvas.width / a4Width) * a4Height) //pdf页面偏移 let position = 0 // canvas.toDataURL() 返回一个包含图片展示的 数据URL。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。 // 返回值是一个数据url,是base64组成的图片的源数据、可以直接赋值给图片的src属性。 let pageData = canvas.toDataURL('image/jpeg', 1.0) let pdf = new jsPDF('p', 'pt', 'a4') //A4纸,纵向 // let index = 1 let createCanvas = document.createElement('canvas') let height pdf.setDisplayMode('fullwidth', 'continuous', 'FullScreen') let pdfName = title || "个人简历" function createImpl(canvas) { console.log(leftHeight, a4HeightRef) 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 } createCanvas.width = canvas.width createCanvas.height = height // console.log(index, 'height:', height, 'pos', position) console.log('height:', height, 'pos', position) // getContext()方法可返回一个对象,该对象提供了用于在画布上绘图的方法和属性。 let ctx = createCanvas.getContext('2d') ctx.drawImage( canvas, 0, position, canvas.width, height, 0, 0, canvas.width, height, ) // let pageHeight = Math.round((a4Width / canvas.width) * height) // pdf.setPageSize(null, pageHeight) if (position != 0) { pdf.addPage() } pdf.addImage( createCanvas.toDataURL('image/jpeg', 1.0), 'JPEG', 10, 20, a4Width - 30, (a4Width / createCanvas.width) * height - 30, ) leftHeight -= height position += height if (leftHeight > 0) { setTimeout(createImpl, 100, canvas) } else { pdf.save(pdfName + '.pdf') pdf.successFlag = true resolve(pdf) } } } //当内容未超过pdf一页显示的范围,无需分页 if (leftHeight < a4HeightRef) { pdf.addImage( pageData, 'JPEG', 10, 20, a4Width - 30, (a4Width / canvas.width) * leftHeight - 30, ) pdf.save(pdfName + '.pdf') pdf.successFlag = true resolve(pdf) } else { try { pdf.deletePage(0) setTimeout(createImpl, 100, canvas) } catch (err) { console.log(err) reject(err) } } }) } catch (error) { reject(error) } }) }
页面里面使用时添加loading效果即可
async handleExport() { try { this.fullscreenLoading = true let pdf = await downloadPDFTwo(this.$refs.pdfWrapper, this.pfdName) // 对 封装的函数添加成功的属性 这里去判断是否成功 console.log(pdf, pdf.successFlag) if (pdf.successFlag) { this.fullscreenLoading = false } else { this.fullscreenLoading = false console.log('下载打印失败,请重新尝试'); } } catch (error) { console.log(error); } },
页面代码如下:
<template> <div class="preview-wrapper"> <div class="preview-btn"> <div class="content"> <div class="download-btn"> <div><span @click="handlePrint">打印</span></div> <div><span @click="handleExport" v-loading.fullscreen.lock="fullscreenLoading">下载</span></div> </div> </div> </div> <div class="user-info" ref="pdfWrapper"> <!-- 内容自己定义 --> ... .... ..... ...... ....... <!-- 水印要放到页面上面 用个div 定位到页面上方--> <div class="water-wrapper" :style="{ backgroundImage: `url(${orgBackground})` }" ></div> </div> </div> </template> <script> //工具方法,导出操作 import { downloadPDF, previewWater } from '@/utils/pdf.js' import { dataInfo } from '@/api/userinfo.js' export default { name: 'userinfo', components: {}, props: {}, data() { return { orgBackground: '', // 接口数据 dataInfo: {}, fullscreenLoading: false, } }, computed: {}, watch: { // 注意这里的dataInfo 是调接口查询后的数据 变化时去添加水印 dataInfo(){ // 等待 DOM 渲染后处理 this.$nextTick(() => { this.orgBackground = previewWater('XXXX网站', 'http://xxxxxxx.xxx.xx') // 打印的当前元素的内容区域的高度 let userInfoWrapper = document.querySelector('.user-info') console.log(userInfoWrapper, userInfoWrapper.offsetHeight) // 获取水印的遮罩层,因为水印要放到内容区域的上方,而背景图默认是放下方 let waterWrapper = document.querySelector('.water-wrapper') waterWrapper.style.cssText = `height: ${userInfoWrapper.offsetHeight}px;` }) } }, created() {}, mounted() {}, methods: { handlePrint() { window.print() }, async handleExport() { try { this.fullscreenLoading = true let pdf = await downloadPDFTwo(this.$refs.pdfWrapper, this.pfdName) // 对 封装的函数添加成功的属性 这里去判断是否成功 console.log(pdf, pdf.successFlag) if (pdf.successFlag) { this.fullscreenLoading = false } else { this.fullscreenLoading = false console.log('下载打印失败,请重新尝试'); } } catch (error) { console.log(error); } }, // 调接口数据 handleDatainfo(){ dataInfo().then((res) => { this.dataInfo = res.data }).catch((error) => { console.log(error) }) } } } </script> <style scoped lang="scss"> div { // webkit 为Google Chrome Safari 等浏览器内核 -webkit-print-color-adjust: 'exact'; print-color-adjust: 'exact'; color-adjust: 'exact'; } .preview-wrapper { // position: relative; z-index: 999; } .user-info { width: 1000px; background: #fff; position: relative; top: 0; left: 0; right: 0; bottom: 0; margin: 0 auto; padding: 0 50px 100px; z-index: 1; } // 水印的样式 .water-wrapper { position: absolute; top: 0; left: 0; width: 1000px; // height: 2237px; // 水印这里的高度不能写死,需要获取上面内容的高度 z-index: 999; } </style>
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。