vue中CSS打印实现及问题记录汇总(window.print、浏览器打印)
1、print.vue中的样式主要是class进行编写使用的,通过3-4获取到的元素,在打印预览时class设置的样式会不生效,行内style设置的样式是正常的,这是因为创建的iframe中无对应style文件样式,对应class也就无效果。原理、背景:通过浏览器的window.print()调出打印预览设置,而该方法是默认直接打印当前浏览器界面的document.body中的界面,我们的需求是先
背景:需求是先用HTML+CSS对打印界面进行排版,然后填入后端数据,实现最终打印界面。实现是通过浏览器的window.print()。不能改变当前界面样式、不能跳转界面,需要直接在当前界面打开打印界面预览弹窗(通过iframe来进行操作)。
总体过程:
1、创建打印文件并调整好样式(print.vue),为该文件最外层元素绑定唯一ID(print-iframe)
2、在父组件中导入并使用print.vue文件,并使用display: none;将该组件隐藏不显示
3、编写打印方法:
(3-1)将后端数据导入至print.vue中
(3-2)创建iframe标签(iframe),隐藏iframe标签(防止在打印预览时显示在界面上)
(3-3)创建link标签,设置link标签属性,设置link标签href
(3-4)通过document.getElementById(打印组件id).innerHTML获取print.vue组件的元素
(3-5)将获取的HTML数据写入至iframe的document中
(3-6)获取iframe的head,将设置后的link标签添加到head里
(3-7)调用iframe的print()方法: iframe.contentWindow.print()
(3-8)移除iframe
// 打印
handleClickPrint() {
// 3-1 数据导入
const item = {
goodsNm: "冰红茶",
num: 1,
notes: "11222",
}
const data = []
for (let i = 1; i < 24; i++) {
data.push(item)
}
this.$refs.printRef.print(data)
this.$nextTick(() => {
// 3-2
const iframe = document.createElement('iframe');
iframe.style.display = 'none'
document.body.appendChild(iframe)
const doc = iframe.contentWindow.document;
// 3-3
// 新建link标签
const linkTag = document.createElement('link')
// 设置lin标签属性
linkTag.setAttribute('rel', 'stylesheet')
// 设置style标签内容
linkTag.href = this.getPrintPageCss()
// 3-4
// 获取需要打印区域数据
let wrap = document.getElementById("print-iframe").innerHTML;
// 3-5
// 数据写入
doc.write("<div>" + wrap + "</div>");
doc.close();
//3-6
// 获取iframe html文件head(必须在写入数据之后进行,写入数据时,会将head中的数据置空)
const head = doc.head
// link标签添加进iframe html文件的head里
head.appendChild(linkTag)
// 延时200ms,为了等css资源加载完
setTimeout(() => {
// 3-7
iframe.contentWindow.focus();
iframe.contentWindow.print();
// 3-8
document.body.removeChild(iframe);
}, 200
})
}
遇到主要问题:
1、print.vue中的样式主要是class进行编写使用的,通过(3-4)获取到的元素,在打印预览时class设置的样式会不生效,行内style设置的样式是正常的,这是因为创建的iframe中无对应style文件样式,对应class也就无效果。所以需要在(3-3)时通过document.styleSheets方式获取到print.vue文件下style中的标签内容,再进行插入。
// 获取界面css样式
getPrintPageCss() {
const cssBlock = document.styleSheets
// 当前界面样式会排在后面,所以先进行一次倒序操作
const styleData = [...cssBlock].reverse().find((item, index) => {
return [...item.cssRules].find((rule) => {
if(!rule.selectorText) return false
return rule.selectorText.includes('.topinfo')
})
})
return styleData.ownerNode.href
}
2、(3-5)、(3-6)这两步不要更换顺序,(3-5)进行操作时,会将iframe下head标签中的内容置空。
3、大部分浏览器目前对@page的语法支持不太好,主要能用的是改margin(外边距)、size(纸张、布局)少数几个。
4、样式部分,本地调试可以使用style标签,上线后,需要改为link标签。
总的代码如下:
父组件代码:
<template>
<div class="container">
<el-button @click="handleClickPrint">打印</el-button>
<Print ref="printRef" style="display: none;" />
</div>
</template>
<script>
import Print from './components/Print.vue'
export default {
components: {
Print,
},
data() {
return {}
},
methods: {
// 获取界面css样式
getPrintPageCss() {
const cssBlock = document.styleSheets
// 当前界面样式会排在后面,所以先进行一次倒序操作
const styleData = [...cssBlock].reverse().find((item, index) => {
// 如果样式是通过link,则排除(更正,不能排除link,发布到线上后文件都是link,排除会找不到文件)
// if(item.href) return false
return [...item.cssRules].find((rule) => {
if(!rule.selectorText) return false
return rule.selectorText.includes('.topinfo')
})
})
return styleData.ownerNode.href
},
// 打印
handleClickPrint() {
// 数据导入
const item = {
goodsNm: "冰红茶",
num: 1,
notes: "11222",
}
const data = []
for (let i = 1; i < 24; i++) {
data.push(item)
}
this.$refs.printRef.print(data)
this.$nextTick(() => {
const iframe = document.createElement('iframe');
iframe.style.display = 'none'
document.body.appendChild(iframe)
const doc = iframe.contentWindow.document;
// 新建style标签(style标签用于本地调试,线上的话需要使用link标签)
// const styleTag = document.createElement('style')
const styleTag = document.createElement('link')
// 设置style标签属性
// styleTag.setAttribute('type', 'text/css')
styleTag.setAttribute('rel', 'stylesheet')
// 设置style标签内容
// styleTag.innerHTML = this.getPrintPageCss()
styleTag.href = this.getPrintPageCss()
// 获取需要打印区域数据
let wrap = document.getElementById("print-iframe").innerHTML;
// 数据写入
doc.write("<div>" + wrap + "</div>");
doc.close();
// 获取iframe html文件head(必须在写入数据之后进行,写入数据时,会将head中的数据置空)
const head = doc.head
// style标签添加进iframe html文件的head里
head.appendChild(styleTag)
iframe.contentWindow.focus();
iframe.contentWindow.print();
document.body.removeChild(iframe);
})
}
}
}
</script>
<style lang="scss" scoped>
</style>
打印组件代码:
<template>
<div id="print-iframe" class="print-iframe">
<div class="topinfo">
<div class="title-form textc center">{{ printData.subjNm }}接收单</div>
<div class="bar">
<div class="left" style="width: 100%;">
客户名称: {{ printData.custNm }}
</div>
</div>
</div>
<table style="margin: 10px 0">
<tbody class="part">
<tr class="header">
<th style="width: 60px">序号</th>
<th>物品名称</th>
<th style="width: 60px">数量</th>
<th>物品备注</th>
</tr>
<tr v-for="(item, index) in printData.tableData" :key="index">
<td class="textc">{{ index + 1 }}</td>
<td class="abstr">{{ item.goodsNm }}</td>
<td class="textc">{{ item.num }}</td>
<td class="abstr">{{ item.notes }}</td>
</tr>
</tbody>
</table>
<div class="part2">
<div class="bar">
<div class="left" style="width: 100%;">
接收说明:{{ printData.receiverNotes }}
</div>
</div>
<div class="bar" style="margin-top: 30px;">
<div class="left" style="width: 40%;">接收人:{{ printData.receiver }}</div>
<div class="left">预计归还日期:{{ printData.returnTm }}</div>
<div class="left" style="width: 30%;">客户签字:</div>
</div>
<div class="bar" style="margin-top: 25px;">
<div class="left" style="width: 40%;"></div>
<div class="left"></div>
<div class="left" style="width: 30%;">日 期:</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Print",
data() {
return {
printData: {
// 单据数据
subjNm: "某某",
custNm: "某某某",
tableData: [],
receiverNotes: "receiver",
receiver: "某某某(11111111111)",
returnTm: "2023-01-29",
},
};
},
methods: {
// 打印数据传入
print(data) {
this.printData.tableData = data;
console.log(data, '>>>');
},
},
};
</script>
<style lang="scss" scoped>
body {
font-family: "Microsoft YaHei";
// width:192mm; /*取7/8*/
// height:116mm; /*取7/8/100mm*/
text-align: left;
margin: 2mm;
}
.amount {
text-align: right;
}
.textl {
text-align: left;
}
.textc {
text-align: center;
}
.textr {
text-align: right;
}
.outer {
display: inline-block;
width: 186mm;
box-sizing: border-box;
}
.topinfo {
width: 100%;
line-height: 20px;
margin-bottom: 10px;
}
.title-corp {
line-height: 24px;
font-size: 14px;
}
.title-form {
line-height: 32px;
font-size: 26px;
margin-bottom: 10px;
}
.bar {
display: table;
table-layout: fixed;
width: 100%;
}
.bar .center {
display: table-cell;
text-align: center;
}
.bar .right {
display: table-cell;
text-align: right;
width: 35%;
}
.bar .left {
display: table-cell;
text-align: left;
width: 35%;
}
tr.summary {
height: 30px;
}
tr.header {
height: 30px;
}
table tr {
height: 11mm;
}
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
}
th {
border: 1px solid #000;
font-weight: normal;
text-align: center;
}
td {
border: 1px solid #000;
text-align: left;
}
.abstr {
white-space: nowrap; /*规定段落中的文本不进行换行 */
overflow: hidden; /*超出部分隐藏*/
text-overflow: ellipsis; /* 超出部分显示省略号 */
}
.footer {
width: 99%;
margin-top: 40px;
height: 20px;
}
.footer div {
float: left;
width: 33%;
}
.part tr {
// page-break-inside: always;
page-break-inside: avoid; // 在元素后插入分页符
}
.part2 {
page-break-inside: avoid; // 避免在元素后插入分页符
}
@media print {
@page {
// size: A5;
// 上下外边距设为0,会导致打印内容挤压在纸张上下边缘,不推荐这样清除,直接在浏览器打印预览界面关闭页眉页脚即可
// margin-top: 0mm; //去掉页眉
// margin-bottom: 0mm; //去掉页脚
}
}
</style>
最终效果:
更多推荐
所有评论(0)