vue項目頁面的打印和下載PDF加loading效果的實現(xiàn)(加水印)
vue頁面的打印和下載PDF(加水?。?/h2>
vue項目頁面的打印
打印的不用說,調(diào)用 window.print() 的方法即可;
注意點:如果用到背景圖的話,需要CSS中添加設(shè)置;
// 標簽看哪些地方用到背景圖就加哪些,不然調(diào)打印機會把背景圖隱藏掉
div {
// webkit 為Google Chrome Safari 等瀏覽器內(nèi)核
-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沒有設(shè)置背景色會默認成黑色
}).then(canvas => {
// 未生成pdf的html頁面高度
let leftHeight = canvas.height
//A4大小,210mm x 297mm,四邊各保留10mm的邊距,顯示區(qū)域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() 返回一個包含圖片展示的 數(shù)據(jù)URL??梢允褂?type 參數(shù)其類型,默認為 PNG 格式。圖片的分辨率為96dpi。
// 返回值是一個數(shù)據(jù)url,是base64組成的圖片的源數(shù)據(jù)、可以直接賦值給圖片的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')
}
}
} //當內(nèi)容未超過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) => {
// 創(chuàng)建一個畫布
const can = document.createElement('canvas')
// 設(shè)置畫布的長寬
can.width = 500
can.height = 750 const cans = can.getContext('2d')
// 旋轉(zhuǎn)角度 canvas旋轉(zhuǎn)不是以圖片旋轉(zhuǎn),而是以畫布左上角為原點旋轉(zhuǎn)
cans.rotate((-45 * Math.PI) / 180)
cans.translate(0, 0) const txtLen = strName.length
// 水印如果都短設(shè)置為50px字體,長水印則30px字體
const fontSize = txtLen > 12 ? '30px Simsun' : '40px Simsun'
cans.font = fontSize
// 設(shè)置填充繪畫的顏色、漸變或者模式
cans.fillStyle = 'rgba(0, 0, 0, 0.3)'
// 設(shè)置文本內(nèi)容的當前對齊方式
cans.textAlign = 'center'
// 設(shè)置在繪制文本時使用的當前文本基線
cans.textBaseline = 'Middle'
// 在畫布上繪制填色的文本(輸出的文本,開始繪制文本的X坐標位置,開始繪制文本的Y坐標位置)
cans.fillText(strName, -txtLen * 12, 400)
cans.fillText(strLink, -txtLen * 12, 440)
// save()方法就是保存你在 cans 中設(shè)置的各種樣式以及屬性
// save()方法相當于將設(shè)置的內(nèi)容隔離出來,不會對外面的任何內(nèi)容造成影響
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 規(guī)定要使用的圖像、畫布或視頻
sx 可選。開始剪切圖片的 x 坐標位置
sy 可選。開始剪切圖片的 y 坐標位置
sw 可選。被剪切圖像的寬度(就是裁剪之前的圖片寬度,這里的寬度若小于圖片的原寬。則圖片多余部分被剪掉;若大于,則會以空白填充)
sh 可選。被剪切圖像的高度(就是裁剪之前的圖片高度)
dx 在畫布上放置圖像的 x 坐標位置
dy 在畫布上放置圖像的 y 坐標位置
dw 可選。要使用的圖像的寬度(就是裁剪之后的圖片高度,放大或者縮放)
dh 可選。要使用的圖像的高度(就是裁剪之后的圖片高度,放大或者縮放)
*/注意要提前下載 jspdf 和 html2canvas 這兩個包,項目中要用到;
項目里面是用另一種方式是把js包下載到本地,動態(tài)新建script標簽引入,注意這樣的方式可以在window的環(huán)境下調(diào)用方法,要用 window.html2canvas 或者 window.jspdf等等;
handleCreateScript() {
// 動態(tài)生成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 鉤子函數(shù)中調(diào)用
created(){
this.handleCreateScript();
}// 封裝的jspdf文件中調(diào)方法,就不用import 方式
// 可以使用window.html2canvas 或者 window.jspdf
注意說一下水印,因為需求要求水印必須放到頁面的上面,而不是頁面的下方,只能另辟蹊徑了,我用的方法是直接canvas的創(chuàng)建生成方式,用個div定位到頁面上面,動態(tài)獲取內(nèi)容的高度,然后給這個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">
<!-- 內(nèi)容自己定義 -->
...
....
.....
......
.......
<!-- 水印要放到頁面上面 用個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網(wǎng)站', 'http://xxxxxxx.xxx.xx')
// 打印的當前元素的內(nèi)容區(qū)域的高度
let userInfoWrapper = document.querySelector('.user-info')
console.log(userInfoWrapper, userInfoWrapper.offsetHeight)
// 獲取水印的遮罩層,因為水印要放到內(nèi)容區(qū)域的上方,而背景圖默認是放下方
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 等瀏覽器內(nèi)核
-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; // 水印這里的高度不能寫死,需要獲取上面內(nèi)容的高度
z-index: 999;
}
</style>注意:之前寫頁面考慮不周到,發(fā)現(xiàn)對接接口后,添加水印的高度可能會有問題,嘗試發(fā)現(xiàn)是父元素高度問題,因為我子元素都是組件的形式,請求數(shù)據(jù)前高度和請求后高度可能存在偏差和不一樣,考慮到vue渲染的問題,導致水印獲取父元素的高度不對,大家在對接接口后嘗試會發(fā)現(xiàn)這個問題,現(xiàn)在改下mounted里面的調(diào)用,需要在獲取數(shù)據(jù)后去獲取父元素的高度
具體看如下代碼:
<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">
<!-- 內(nèi)容自己定義 -->
...
....
.....
......
.......
<!-- 水印要放到頁面上面 用個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: '',
// 接口數(shù)據(jù)
dataInfo: {}
}
},
computed: {},
watch: {
// 注意這里的dataInfo 是調(diào)接口查詢后的數(shù)據(jù) 變化時去添加水印
dataInfo(){
// 等待 DOM 渲染后處理
this.$nextTick(() => {
this.orgBackground = previewWater('XXXX網(wǎng)站', 'http://xxxxxxx.xxx.xx')
// 打印的當前元素的內(nèi)容區(qū)域的高度
let userInfoWrapper = document.querySelector('.user-info')
console.log(userInfoWrapper, userInfoWrapper.offsetHeight)
// 獲取水印的遮罩層,因為水印要放到內(nèi)容區(qū)域的上方,而背景圖默認是放下方
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)
},
// 調(diào)接口數(shù)據(jù)
handleDatainfo(){
dataInfo().then((res) => {
this.dataInfo = res.data
}).catch((error) => {
console.log(error)
})
}
}
}
</script><style scoped lang="scss">
div {
// webkit 為Google Chrome Safari 等瀏覽器內(nèi)核
-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; // 水印這里的高度不能寫死,需要獲取上面內(nèi)容的高度
z-index: 999;
}
</style>友情提示:對于處理這部分類型的功能,要考慮的地方要注意,比如獲取頁面后的高度處理,圖片的高度問題等等,希望程序猿們遇到問題解決問題!
封裝異步PDF下載函數(shù)并添加loading效果
因產(chǎn)品需求的需要,當用戶下載PDF時發(fā)現(xiàn)可能需要等待一段時間,所以加個loading效果;
export const downloadPDFTwo = (el, title) => {
return new Promise(async (resolve, reject) => {
try {
html2canvas(el, {
allowTaint: true,
useCORS: true,
dpi: 120, // 圖片清晰度問題
background: '#FFFFFF', //如果指定的div沒有設(shè)置背景色會默認成黑色
}).then(canvas => {
//未生成pdf的html頁面高度
let leftHeight = canvas.height
console.log(leftHeight) //A4大小,210mm x 297mm,四邊各保留10mm的邊距,顯示區(qū)域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() 返回一個包含圖片展示的 數(shù)據(jù)URL??梢允褂?type 參數(shù)其類型,默認為 PNG 格式。圖片的分辨率為96dpi。
// 返回值是一個數(shù)據(jù)url,是base64組成的圖片的源數(shù)據(jù)、可以直接賦值給圖片的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)
}
}
} //當內(nèi)容未超過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)
// 對 封裝的函數(shù)添加成功的屬性 這里去判斷是否成功
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">
<!-- 內(nèi)容自己定義 -->
...
....
.....
......
.......
<!-- 水印要放到頁面上面 用個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: '',
// 接口數(shù)據(jù)
dataInfo: {},
fullscreenLoading: false,
}
},
computed: {},
watch: {
// 注意這里的dataInfo 是調(diào)接口查詢后的數(shù)據(jù) 變化時去添加水印
dataInfo(){
// 等待 DOM 渲染后處理
this.$nextTick(() => {
this.orgBackground = previewWater('XXXX網(wǎng)站', 'http://xxxxxxx.xxx.xx')
// 打印的當前元素的內(nèi)容區(qū)域的高度
let userInfoWrapper = document.querySelector('.user-info')
console.log(userInfoWrapper, userInfoWrapper.offsetHeight)
// 獲取水印的遮罩層,因為水印要放到內(nèi)容區(qū)域的上方,而背景圖默認是放下方
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)
// 對 封裝的函數(shù)添加成功的屬性 這里去判斷是否成功
console.log(pdf, pdf.successFlag)
if (pdf.successFlag) {
this.fullscreenLoading = false
} else {
this.fullscreenLoading = false
console.log('下載打印失敗,請重新嘗試');
}
} catch (error) {
console.log(error);
}
},
// 調(diào)接口數(shù)據(jù)
handleDatainfo(){
dataInfo().then((res) => {
this.dataInfo = res.data
}).catch((error) => {
console.log(error)
})
}
}
}
</script><style scoped lang="scss">
div {
// webkit 為Google Chrome Safari 等瀏覽器內(nèi)核
-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; // 水印這里的高度不能寫死,需要獲取上面內(nèi)容的高度
z-index: 999;
}
</style>總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Vue淺析axios二次封裝與節(jié)流及防抖的實現(xiàn)
axios是基于promise的HTTP庫,可以使用在瀏覽器和node.js中,它不是vue的第三方插件,vue-axios是axios集成到Vue.js的小包裝器,可以像插件一樣安裝使用:Vue.use(VueAxios,?axios),本文給大家介紹axios的二次封裝和節(jié)流與防抖2022-08-08
vue3+vite assets動態(tài)引入圖片的三種方法及解決打包后圖片路徑錯誤不顯示的問題
這篇文章主要介紹了vue3+vite assets動態(tài)引入圖片的幾種方式,解決打包后圖片路徑錯誤不顯示的問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
Vue3使用hook封裝媒體查詢和事件監(jiān)聽的代碼示例
這篇文章主要給大家詳細介紹Vue3如何使用hook封裝媒體查詢和事件監(jiān)聽,使得Vue的開發(fā)更加絲滑,文中通過代碼示例給大家介紹的非常詳細,感興趣的同學跟著小編一起來學習吧2023-07-07

