如何实现最真实的web打印
公司组织了体检,在医院发现可以使用身份证快捷打印体检单,觉得很方便,但仔细一看,打印效果极差。
注:如直接调用Ctrl + P,则是打印当前的视口,可以选择打印机及打印机纸张,这显然不满足业务要求;若调用window.print(),则只能将css写在html上一起传到打印机,且无法预设打印机及纸张和其他设置,也不能保存模板。翻阅了一下大部分打印,都是调用window.print()去实现,都存在一下问题:
打印样式、排版问题。
无法限定预设打印机、打印机纸张及保存为模板。
自定义纸张问题。
浏览器显示与打印结果不一致。
恰巧需要做打印功能,于是决定重构,解决上面的问题。我采用的是第二种方案并集成PAZU云打印,使用c++开发的一个exe脚本,在运行时与本机Websocket连接返回打印信息。需要详细了解的同学请移步:PAZU官网
打印结果在测试后趋向于真实打印,与web显示几乎一致。
总体的思路是将打印机设备参数可视化,可配置,可预设到点击打印时传到打印设备上。不支持自定义就先同步到打印机,然后选择。
比较典型的ERP详情页面,包含主表Form和子表Table明细
GetSlideInfo () { let CondiData = [] let SlideInfoVnode = this.$refs.SlideInfo.$children[0] let FormItems = findComponentsDownward(SlideInfoVnode, 'FormItem') FormItems.forEach((item, itemIndex) => { let field = item.$children if (field.length) { const fieldVnode = item.$children[0] let fieldValue = this.GetExportFieldValue(fieldVnode) let formItem = { key: item.prop, label: item.label.split(':')[0], value: fieldValue, index: itemIndex } Object.assign(formItem, fieldVnode.print) CondiData.push(formItem) } }) return CondiData } 复制代码
使用findComponentsDownward获取所有的FormItem,然后获取FormItem的
VueComponment
,在组件实例里获取到打印信息。
为了支持列表批量打印详情,于是注册了一个v-print:
import { typeOf } from '@/libs/util' export default { inserted: (el, { value, arg }, vnode) => { if (!value) return if (typeOf(value) != 'object') value = {} vnode.componentInstance.print = Object.assign(value, { renderType: arg || 'input' }) }, update: (el, { value, arg }, vnode) => { if (!value) return if (typeOf(value) != 'object') value = {} vnode.componentInstance.print = Object.assign(value, { renderType: arg || 'input' }) } } 复制代码
然后在页面这样书写,即可在inserted时挂载到组件实例data上。
v-print:pick="{id:'orderBranchId',code:'orderBranchCode',name:'orderBranchName'}" 复制代码
在构造数据之前,我们要造好动态路由并加入前端路由白名单:
{ path: '/print', name: 'print', component: Main, children: [{ path: 'print-setting/:sheetType', name: 'print-setting', meta: { title: '打印设置' }, component: () => import('@/view/main-components/print-setting.vue') }] } 复制代码
要保持数据持久化,于是我将数据保存到store并同时set到sessionStorage:
import { setSession } from '@/libs/session.js' export default { state: { printTabs: {} }, mutations: { setPrintTab (state, tab) { state.printTabs[tab.route] = tab.data setSession(tab.route, tab.data) } } } 复制代码
构造好数据之后进入打印页面:
this.setPrintTab({ route: this.$route.meta.code, data: params }) this.$router.push({ name: 'print-setting', params: { sheetType: this.$route.meta.code } }) 复制代码
打印拖拽项由四个可拖拽的draggable组成,可相互拖拽,这里采用的是vuedraggable。
<Row style="height:100%"> <Form style="height:100%" justify :label-position="labelPositon" ref="formList"> <draggable id="formList" :list="formList" style="height:100%" group="people" :animation="150" :ghostClass="cls+'-left-item-chosen'"> <Col :span="8*item.span" :class="[cls+'-form-item',{'item-select':item.selected}]" v-for="(item, index) in formList" :key="index" @click.native="handleFormItemClick(item, index)" > <form-item v-if="!item.blank" :label="item.label" >{{item.value}}</form-item> <div :class="cls+'-blank'" v-else></div> </Col> </draggable> </Form> </Row> 复制代码
设置4个可拖拽group相同即可相互拖拽。
读取本机打印机:
PAZU.TPrinter.getPrinters() 复制代码
读取当前打印机纸张:
PAZU.TPrinter.getPaperForms() 复制代码
打印及打印校验:
validatePrintExe () { return new Promise((resolve, reject) => { PAZU.TPrinter.getPrinters(res => { if (res === 'err') { resolve(false) } else { resolve(true) } }) }) }, async handlePrint () { if (await this.validatePrintExe()) { this.doPageSetup() PAZU.print('printContent', null, null, true) api.updatePrintCount({ ids: Object.values(this.printData.ids).join('~&z') }, this.printData.action) } else { this.showPrintDownLoad = true } } 复制代码
同步自定义纸张到打印机:
handleSynchronizeComfirm () { this.$refs.synchronizeForm.validate(valid => { if (valid) { PAZU.TPrinter.createPaper(this.baseSetting.width, this.baseSetting.height, (res) => { if (res) { this.$Message.warning(`规格【${res}】的纸张已存在。`) } else { this.showPrintor = false this.$Message.info('同步成功。') } }, this.synchronize.printName, this.synchronize.pageName) } }) } 复制代码
一套打印下来问题重重,需求也巨大,但在写文档章时发现不知该如何把可移植的思想及技术难点单方面的开放出来。这之后的文章我也不知如何写起。
详细可以看我之前写的《修改vue源码实现对key的keep-alive》)
后面我会针对源码详细讲解为何要修改源码,为何动态路由keep-alive失败。vue3.0正式发布之后我会第一之间测试3.0keep-alive。