最近一个业务,要实现局部打印功能,原本我用三个"el-row"标签写了两行内容一个数据表格。首先尝试了, window.print(),可想而知是失败的,直接打印了全部网页,获取局部的代码没有样式。依次尝试了其他插架比如说:print-js、vue3-print-nb都不适用,不是说插件不行,这些插件打印element写出来的页面样式,打印的时候多多少少都是有瑕疵的,要么表格显示不全,要么样式没有,我的解决方案是适用了原生的表格,代码如下。
改成原生表格
<!-- 打印的内容 -->
<div id="printContent" style="width: 100%" class="margin-top-15 report-font">
<table cellspacing="0" border="1" style="width: 100%" class="margin-top-15 table-width-100">
<tr>
<td class="center bold" style="width: 5%" v-text="$t('report.number')"></td>
<td class="center bold" style="width: 29%" v-text="$t('report.name')"></td>
<td class="center bold" style="width: 18%" v-text="$t('report.lastYearFinalAccounts')"></td>
<td class="center bold" style="width: 18%" v-text="$t('report.thisYearFinalAccountsDetail')"></td>
<td class="center bold" style="width: 30%" v-text="$t('report.explain')"></td>
</tr>
<template v-if="tableData.length">
<template v-for="(item, index) in tableData" :key="index">
<tr class="table-width-100 report-row-height-20">
<td class="center" style="width: 5%">{
{ item.num }}</td>
<td class="left" style="width: 29%" v-html="stringFormatter(item)"></td>
<td class="left" style="width: 18%">
{
{ amountFormatter(item.lastYearFinalAccounts) }}
</td>
<td class="left" style="width: 18%">{
{amountFormatter(item.currentBudget)}}
</td>
<td class="left" style="width: 30%">{
{ item.remark }}</td>
</tr>
</template>
</template>
<template v-else>
<tr class="table-width-100 report-row-height-20">
<td colspan="5" style="text-align: center;">
暂无数据
</td>
</tr>
</template>
</table>
</div>
这里有两种打印方案:首先最外层套了一个div设置了一个id为printContent的标签。
- 第一种:用iframe打印,页面也不会改变,也不用刷新,也不用重新打开页面。
首先在template中加入一个隐藏的iframe
<!--打印用的隐藏的iframe-->
<iframe width="0" height="0" frameborder="0" id="printIframe"></iframe>
然后就做打印的处理函数
// 处理打印按钮点击事件
const handlePrint = () => {
//判断表格是否有数据
if (tableData.value.length === 0) {
$message.warning(t('general.noData'))
return
}
//因为TypeScript 强制执行更严格的空值检查。所以一些地方加了?或者判断
//获取打印内容
const printHtml = document?.getElementById("printContent")?.innerHTML;
//获取iframe 标签
const syfPrint = document?.getElementById("printIframe") as HTMLIFrameElement | null;
if (syfPrint) {
//获取原页面里的head标签
const documentHead = document.getElementsByTagName("head")[0];
//获取iframe 页面里的head标签
const iframeHead = syfPrint.contentDocument?.getElementsByTagName("head")[0];
//把原页面的里的head标签内容给到iframe 页面里的head
if (iframeHead) {
iframeHead.innerHTML = documentHead.innerHTML;
}
//获取iframe 页面里的body,然后把dom结构赋值给iframe
const syfPrintBody = syfPrint.contentDocument?.body;
if (syfPrintBody) {
syfPrintBody.innerHTML = printHtml || "";
//执行打印
syfPrint.contentDocument.execCommand("Print");
}
} else {
//错误提示
$message.error(t('general.link.error'))
}
}
这种方式直接在本页面打印,没毛病。
2. 第二中新打开了一个标签页,代码如下:
// 处理打印按钮点击事件
const handlePrint = () => {
//判断表格是否有数据
if (tableData.value.length === 0) {
$message.warning(t('general.noData'))
return
}
//获取本页面样式
const styles = Array.from(document.styleSheets)
.map(sheet => Array.from(sheet.cssRules)
.map(rule => rule.cssText)
.join('\n'))
.join('\n');
// 创建一个新的窗口,以打开打印预览
const printWindow = window.open('', '_blank');
printWindow?.document.open();
// 将当前页面的样式信息添加到新窗口中
printWindow?.document.write(`
<html>
<head>
<style>
${
styles}
</style>
</head>
<body>
${
document?.getElementById("printContent")?.innerHTML}
</body>
</html>
`);
printWindow?.document.close();
printWindow?.print();
}
当然,你用原生代码写的,完全可以使用插件,我这里还以我安装的vue3-print-nb为例,直接可以不写打印事件,只要一个打印按钮就行,如下:
<el-button type="primary" :icon="Printer" v-print="'#printContent'">打印</el-button>
v-print="‘#printContent’"直接指定要打印的标签ID就可以了,其他插架没有尝试,有兴趣可以试试。