背景
无论在什么系统中,大多都会涉及到打印单据的功能,而很少有人会直接调用window.print来打印整个网页,大多都有着自己的特定需求。如果只是单纯的打印表格,使用第三方插件也是一种选择,比如锐浪报表。但是,如果你的打印没那么复杂,你研究怎么使用第三方库的时间,都已经把打印功能做出来了。另外,这篇文件介绍的是使用window.print()来打印,而非是打印小票热敏标签打印。
打印预览
打印流程
打印流程:
- 放置打印模板组件到根组件,将数据通过pinia设置为全局数据, 方便后面更改
- 修改打印数据,由于修改是异步,所以下面打印的时候需要延迟一定的时间
- 获取id为printcontent的dom内容和样式,写入到新的window或者iframe中,调用自定义print函数打印
也可以定义一个全局组件,在每个需要打印的页面,添加一个该组件,通过props传入数据,然后再打印
具体实现
实现的过程中需要注意两个问题:
- 打印页面或组件不展示,只用来打印,怎么设置
- 调用打印时,样式丢失,如何解决
下面就以vue3来做示例,使用react也是一样的思路。这里使用pinia来存储全局各种单据数据,创建 stores/print.ts
export const usePrintStore = defineStore({
id: 'print',
state: (): PrintState => ({
title: '',
// ...
}),
actions: {
updatePrintState(newState: PrintState) {
this.title = newState.title
// ...
},
cleanPrintState() {
this.title=''
// ...
}
},
})
创建一个打印模板组件 components/print-bill-template.vue,将其放到根组件内
<script setup lang="ts">
import { usePrintStore } from '@/stores/print'
const pstore = usePrintStore()
</script>
<template>
<div class="container" id="printcontent">
<!-- 打印内容... -->
</div>
</template>
<style scoped>
/* 组件样式... */
/* 打印内容不展示 */
#printcontent {
display: none;
}
@media print {
#printcontent {
display: block;
}
}
</style>
上面的代码,创建一个id为printcontent的组件,并且通过media来控制,正常页面不显示,打印模式下显示。
接下来,创建一个工具类 utils/print.ts,来更新数据,并创建一个用于打印的window,来打印内容
const pstore = usePrintStore()
const printBill = (state: PrintState) => {
// 更改需要打印的单据数据
pstore.updatePrintState(state)
// 开始打印
setTimeout(print, 100);
}
const print = () => {
// Get all stylesheets HTML
let stylesHtml = ''
for (const node of [...document.querySelectorAll('link[rel="stylesheet"], style')]) {
stylesHtml += node.outerHTML
}
const printableContent = document.getElementById('printcontent')
const printWindow = window.open('', 'Print')
if (printableContent === null || printWindow === null) return
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>${stylesHtml}</head>
<body>${printableContent.innerHTML}</body>
</html>`)
printWindow.document.close()
printWindow.focus()
printWindow.print()
printWindow.close()
pstore.cleanPrintState()
}
setTimeout(print, 100);
更新数据为异步,这里延迟了100毫秒
<head>${stylesHtml}</head>
新开的window会丢失样式,这里把获取到的样式放在了新页面的head里,这样将printBill函数导出,就可以打印了。
不使用window,使用iframe形式
使用window会新开一个tab,如果不想这样,也可以使用iframe,在当前页面处理。将上面的window部分替换下即可
let printContent = document.getElementById('printcontent')?.outerHTML
let iframe = document.createElement('iframe')
document.getElementsByTagName('body')[0].appendChild(iframe)
// let doc = iframe.contentDocument || iframe.contentDocument.document;
let doc = iframe.contentDocument
if (doc === null) return
doc.write(`
<!DOCTYPE html>
<html>
<head>${stylesHtml}</head>
<body>${printContent}</body>
</html>`)
iframe.contentWindow?.focus()
iframe.contentWindow?.print()
doc.close()
document.body.removeChild(iframe)
如果你有更好的实现方式,欢迎留言交流。