在想打印一個(gè)web頁面的時(shí)候,會(huì)用到window.print()。默認(rèn)會(huì)打印當(dāng)前document內(nèi)容,樣式/圖片還有<body>里面的html標(biāo)簽.一般我們打印當(dāng)前頁面,不會(huì)是整個(gè)頁面,而只是打印當(dāng)前頁面部分內(nèi)容而已。一般是寫樣式,打印之前把不需要打印的標(biāo)簽通過display:none隱藏掉。
如果東西不多也可以這樣做,但是如果要打印其他頁面,還是得重新寫一下這樣的隱藏完了還得顯示回來,有點(diǎn)麻煩。這時(shí)候就想,能不能只打印一個(gè)組件這個(gè)組件里面的內(nèi)容都是我們要打印的東西。于是有了封裝一個(gè)打印組件的想法。
思路是這樣的,通過一個(gè)iframe 把當(dāng)前需要打印的組件內(nèi)容給到iframe的body,樣式呢,我們就拿當(dāng)前document里面的樣式。因?yàn)関ue組件用的也是當(dāng)前頁面的樣式,當(dāng)運(yùn)行npm run dev(npm run serve)的時(shí)候,css-loader /style-loader 會(huì)把組件的樣式打包到document的head標(biāo)簽下頁面。
所以就可以這樣寫 PrintView.vue
<template>
<div class="print">
<iframe id="iframe" style="display: none;"></iframe>
</div>
</template>
<script>
export default {
name: "print",
props: {
html: {
type: String,
default: ""
}
},
watch: {
html(val) {
if (val) {
this.setBodyHtml(val);
}
}
},
methods: {
setBodyHtml(html) {
const document = window.document;
const iframe = window.frames[0];
iframe.document.head.innerHTML = document.head.innerHTML; // 獲取當(dāng)前文檔的頭部給iframe
iframe.document.body.innerHTML = html; // 把傳過來的html給iframe <body>
// 打印
iframe.window.print();
}
};
</script>
組件中的iframe默認(rèn)不顯示的,因?yàn)椴恍枰吹剿?打印預(yù)覽有電腦的打印預(yù)覽.
然后通過props下的html這個(gè)屬性接收需要打印的組件的html.通過watch監(jiān)聽html的變化,當(dāng)有變化我們就調(diào)用自定義的
setBodyHtml方法打印,這個(gè)方法也很簡(jiǎn)單,把當(dāng)前頁面的頭部樣式給到當(dāng)前組件的iframe,把需要打印的html給
到當(dāng)前組件的iframe<body> 通過調(diào)用iframe.window.print();打印當(dāng)前組件。

這樣會(huì)有一個(gè)問題,重新獲取到內(nèi)容的iframe,它自己需要重新加載css跟img,有時(shí)候會(huì)出現(xiàn)需要打印的組件樣式不對(duì)或者圖片不顯示的問題。所以在打印之前還需要監(jiān)聽下iframe加載的圖片跟樣式是否加載完成.
所以組件完整的寫法是這樣
<template>
<div class="print">
<iframe id="iframe" style="display: none;"></iframe>
</div>
</template>
<script>
export default {
name: "print",
props: {
html: {
type: String,
default: ""
}
},
watch: {
html(val) {
if (val) {
this.setBodyHtml(val);
}
}
},
methods: {
setBodyHtml(html) {
const document = window.document;
const iframe = window.frames[0];
iframe.document.head.innerHTML = document.head.innerHTML; // 獲取當(dāng)前文檔的頭部給iframe
iframe.document.body.innerHTML = html; // 把傳過來的html給iframe頭部
// 圖片和樣式加載完成
Promise.all([this.loadStyle(), this.loadImage()]).then(res => {
// 打印
iframe.window.print();
});
},
loadStyle() {
const iframe = window.frames[0];
const styles = iframe.document.head.getElementsByTagName("style"); // <style>
const links = iframe.document.head.getElementsByTagName("link"); // <link>
let arrs = [];
arrs = arrs.concat(...styles, ...links);
return new Promise((resolve, reject) => {
for (let i = 0; i < arrs.length; i++) {
arrs[i].onload = function() {
if (i === arrs.length - 1) {
console.log("style 樣式加載完成");
resolve("style 樣式加載完成");
}
};
}
});
},
loadImage() {
const iframe = window.frames[0];
const imgs = iframe.document.body.getElementsByTagName("img"); // <img>
return new Promise((resolve, reject) => {
for (let i = 0; i < imgs.length; i++) {
imgs[i].onload = function() {
if (i === imgs.length - 1) {
console.log("img 加載完成");
resolve("img 加載完成");
}
};
}
});
}
}
};
</script>
通過loadStyle與loadImage這兩個(gè)方法,別找到iframe中的樣式和圖片. 樣式會(huì)有兩種,一種是head的<style>標(biāo)簽
打包生產(chǎn)的項(xiàng)目里,樣式是一個(gè)css文件,所以還需要找到<link>標(biāo)簽.通過Promise.all() 當(dāng)圖片和樣式onload完成后
就可以打印了.
用的時(shí)候 需要打印的頁面引入 PrintView組件
<print-view :html="printHtml" />
需要打印的組件打上ref屬性,例如ref="print",當(dāng)點(diǎn)擊打印按鈕的時(shí)候,
this.printHtml = this.$refs.printElement.innerHTML;
這樣就把需要打印的組件傳到我們打印組件的iframe,調(diào)出打印面板.

至于打印成功不成功,只能通過肉眼判斷,因?yàn)槟氵@個(gè)電腦可能沒連打印機(jī)也是有可能的.

要打印的這個(gè)圖呢,只是頁面上的一個(gè)組件,而不是完整的頁面。當(dāng)點(diǎn)擊打印按鈕,會(huì)彈出一個(gè)dialog,顯示出要打印的組件,然后指定打印的組件內(nèi)容。