Electron 17 + Vue 2 实战:搞定医院/商超小票打印的完整流程与避坑指南
Electron 17 + Vue 2 实战:搞定医院/商超小票打印的完整流程与避坑指南
在医疗挂号、超市结账、政务办事等线下业务场景中,小票打印功能往往是终端设备的刚需。传统方案依赖外接POS机或专用打印设备,而基于Electron的跨平台桌面应用,能够以更低的成本实现一体化解决方案。本文将带你从零构建一个稳定可靠的小票打印模块,涵盖从打印机检测到静默输出的全流程,并针对实际业务中的坑点提供解决方案。
1. 环境准备与基础配置
1.1 技术栈选型考量
选择Electron 17 + Vue 2的组合主要基于以下实际考量:
- 长期支持版本:Electron 17属于LTS版本,维护周期长,适合商业项目
- Vue 2的稳定性:在需要快速上线的业务系统中,Vue 2的成熟生态更可靠
- 打印兼容性:经测试,该组合对热敏打印机、针式打印机等常见小票设备支持良好
基础环境配置:
# 项目初始化 vue init simulatedgreg/electron-vue electron-print-demo # 依赖安装 npm install --save-dev electron@17.2.0 npm install vue@2.6.141.2 打印相关依赖配置
需要在electron-builder.json中添加以下配置,确保打包后能正确访问打印机:
{ "extraResources": [ { "from": "static/", "to": "static" } ] }2. 打印机交互全流程实现
2.1 动态获取打印机列表
在Electron主进程(src/main/index.js)中添加打印机监听:
ipcMain.on('fetch-printers', (event) => { const win = BrowserWindow.getFocusedWindow() const printerList = win.webContents.getPrinters() event.sender.send('printer-list', printerList) })渲染进程调用示例:
methods: { async loadPrinters() { const printers = await new Promise(resolve => { ipcRenderer.send('fetch-printers') ipcRenderer.once('printer-list', (_, data) => resolve(data)) }) this.printerOptions = printers.map(p => ({ label: `${p.name} (${p.description})`, value: p.name })) } }2.2 Webview打印方案优化
改进后的webview配置方案:
<webview id="printView" :src="printTemplatePath" nodeintegration webpreferences="contextIsolation=no, enableRemoteModule=true" style="display: none;" />对应的打印模板(static/print.html)增强版:
<!DOCTYPE html> <html> <head> <style> @page { size: 80mm auto; margin: 0 } body { font-family: 'Arial'; width: 80mm; padding: 5mm; font-size: 14px; } .header { text-align: center; margin-bottom: 10px } .divider { border-top: 1px dashed #000; margin: 10px 0 } </style> </head> <body> <div id="content"></div> <script> require('electron').ipcRenderer.on('render-content', (_, html) => { document.getElementById('content').innerHTML = html window.print() }) </script> </body> </html>3. 业务场景深度适配
3.1 医疗场景特殊处理
针对医院挂号小票的典型需求:
| 要素 | 处理方案 | 代码示例 |
|---|---|---|
| 患者隐私信息 | 自动脱敏处理 | replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') |
| 药品清单 | 自动分页打印 | page-break-after: always |
| 急诊标识 | 红色字体强调 | color: red; font-weight: bold |
3.2 商超小票优化方案
超市小票的典型结构实现:
function generateReceipt(items) { let html = ` <div class="header">${storeName}</div> <div>时间:${new Date().toLocaleString()}</div> <div class="divider"></div> <table width="100%"> ${items.map(item => ` <tr> <td>${item.name}</td> <td>×${item.quantity}</td> <td align="right">${item.price.toFixed(2)}</td> </tr> `).join('')} </table> <div class="divider"></div> <div align="right">总计:${total.toFixed(2)}元</div> ` return html }4. 稳定性保障与异常处理
4.1 打印状态监控
增强的打印状态管理方案:
const printJob = webview.print({ silent: true, deviceName: selectedPrinter }) printJob.on('completed', () => { this.$notify({ type: 'success', message: '打印成功' }) }) printJob.on('failed', (_, error) => { this.$notify({ type: 'error', message: `打印失败:${error}`, duration: 0, showClose: true }) })4.2 常见故障排查指南
高频问题解决方案对照表:
| 故障现象 | 可能原因 | 解决方案 |
|---|---|---|
| 打印内容空白 | webview未加载完成 | 添加did-finish-load事件监听 |
| 打印机无响应 | 设备名称不匹配 | 使用getPrinters()验证设备名 |
| 样式错乱 | CSS单位不兼容 | 使用mm替代px |
| 打包后失效 | 静态资源路径错误 | 使用path.join(__static, 'file') |
5. 性能优化实战技巧
5.1 预加载方案
在src/main/index.js中预加载打印模板:
const preloadWindow = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }) preloadWindow.loadURL(`file://${path.join(__static, 'print.html')}`)5.2 批量打印队列实现
打印任务队列管理器:
class PrintQueue { constructor() { this.queue = [] this.isPrinting = false } add(task) { this.queue.push(task) this.process() } async process() { if (this.isPrinting) return this.isPrinting = true while (this.queue.length) { const task = this.queue.shift() try { await this.executePrint(task) } catch (error) { console.error('打印失败:', error) } } this.isPrinting = false } }6. 安全与权限管理
6.1 打印权限控制
基于角色的打印权限方案:
// 主进程权限验证 ipcMain.handle('request-print', (event, ...args) => { if (!checkUserPermission(event.sender)) { throw new Error('无打印权限') } return printHandler(...args) })6.2 打印日志审计
完整的打印日志记录:
function logPrintJob(details) { const logEntry = { timestamp: new Date(), user: currentUser, printer: details.deviceName, contentHash: createHash('md5').update(details.html).digest('hex') } fs.appendFileSync('print.log', JSON.stringify(logEntry) + '\n') }7. 跨平台兼容方案
7.1 Windows特有问题处理
Windows平台专用配置:
if (process.platform === 'win32') { printOptions.printBackground = false // Windows下背景打印容易出错 printOptions.marginsType = 0 // 使用默认边距 }7.2 macOS打印服务集成
macOS系统打印服务集成:
function checkMacPrintService() { if (process.platform !== 'darwin') return true return new Promise(resolve => { exec('lpstat -p', (error) => { resolve(!error) }) }) }8. 项目部署与更新策略
8.1 打印驱动自动检测
驱动检测方案实现:
function checkPrinterDrivers() { const { exec } = require('child_process') return new Promise((resolve) => { exec('lpinfo -v', (error, stdout) => { resolve(stdout.includes('usb') || stdout.includes('network')) }) }) }8.2 增量更新机制
打印模板热更新方案:
const templateUpdater = new AutoUpdater({ checkUrl: 'https://your-server.com/print-template/version', downloadUrl: 'https://your-server.com/print-template/latest', localPath: path.join(__static, 'print.html') }) templateUpdater.on('update-downloaded', () => { this.reloadPrintTemplate() })