Web网页打印单据, 别再引用框架了

573 阅读2分钟

背景

无论在什么系统中,大多都会涉及到打印单据的功能,而很少有人会直接调用window.print来打印整个网页,大多都有着自己的特定需求。如果只是单纯的打印表格,使用第三方插件也是一种选择,比如锐浪报表。但是,如果你的打印没那么复杂,你研究怎么使用第三方库的时间,都已经把打印功能做出来了。另外,这篇文件介绍的是使用window.print()来打印,而非是打印小票热敏标签打印。

打印预览

web网页打印.png

打印流程

打印流程:

  1. 放置打印模板组件到根组件,将数据通过pinia设置为全局数据, 方便后面更改
  2. 修改打印数据,由于修改是异步,所以下面打印的时候需要延迟一定的时间
  3. 获取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)

如果你有更好的实现方式,欢迎留言交流。

参考

stackoverflow.com/questions/5…