Electron + Vue CLI 3 实现小票打印:从原理到 electron-vue-print-demo 实战
简介electron-vue-print-demo 是一款基于 Electron 与 Vue CLI 3 的桌面端小票打印示例项目面向需要快速实现静默打印、打印机配置与本地状态保存的 JavaScript 开发者。项目完整演示了从用户点击打印、读取 electron-store 本地存储、判断是否已设置打印机名称到未设置时弹出配置框、确认后自动打印的整个业务闭环代码结构清晰适合直接借鉴或二次开发。压缩包共 25 个文件以 14 个 JS 文件为主涵盖主进程、后台任务与业务逻辑另有 3 个 Vue 组件负责界面交互2 个 JSON 文件用于项目配置以及 HTML 模板、说明文档和图标素材整体仅 117KB轻量易读。已有 3660 人学习/下载。通过该项目可以掌握 electron-store 的用法、渲染进程与主进程通信方式以及设置打印机并静默打印小票的完整实现思路对开发收银系统、订单打印等场景有直接参考价值。1. electron-vue-print-demo 在解决什么问题把浏览器打印换成可控的小票输出做收银机、门店订单、外卖接单这类系统时小票打印是上线前最容易翻车的环节。浏览器里的 window.print() 只能弹系统打印对话框纸张宽度、静默打印、指定打印机全都不可控换成 electronvuecli3 实现打印小票功能之后Electron 主进程可以直接调 webContents.print()指定 58mm 或 80mm 小票纸、静默输出、拿到打印结果回调。标题里的 electron-vue-print-demo 就是这类需求的最小可运行样本Vue CLI 3 搭建界面Electron 负责打印通道前端只管拼装小票内容。适合正在做桌面收银软件、门店终端工具或者打算把 Web 端订单系统改造成客户端形态的开发者。2. 打印链路怎么选webContents.print 与隐藏打印窗口的取舍2.1 浏览器打印为什么不适合小票场景小票纸很窄58mm 小票纸的有效打印宽度通常只有 48mm。浏览器打印面向的是 A4 这类标准幅面就算网页里写了 media print系统打印对话框给出的纸张列表、用户默认纸张都不受网页控制。也就是说小票功能无法保证打印出来的内容宽度对上纸宽。更深一层的问题是静默。收银场景里用户点一次打印程序希望直接出票不要让用户面对“选择打印机”和“确定”两次确认。传统 Web 应用做不到这一点因为网页权限模型禁止网页直接访问打印机设备。即使浏览器有打印预览它也是系统级 UI网页拿不到打印成功或失败的状态。做生产工具时这是硬伤。2.2 webContents.print() 与 window.print() 的本质区别window.print() 是渲染进程里由浏览器实现的打印它的行为是唤起打印对话框返回值永远是 void。开发者看到的打印过程其实是用户在对话框里确认后完成的程序无法干涉打印机选择、无法静默执行、也无法拿到成功失败状态。webContents.print(options, callback) 是 Electron 主进程的 API直接操作的是 Chromium 打印栈数据从渲染管线出来后可以直接进入系统打印机队列。它支持 silent 参数为 true 时绕过打印对话框支持 deviceName 指定打印机回调里返回成功标志和失败原因。差别最大的一点window.print() 是渲染进程的单向请求webContents.print() 是主进程的双向控制。做小票打印这类需要确定性的功能只能走主进程 API。2.3 两条实现路线当前页打印与隐藏窗口打印实现时有两条路线。路线 A 是直接在 Vue 应用当前页面里写打印模板用 media print 设置样式再调用 webContents.print() 打印当前窗口。好处是改动量小本页数据直接可用问题是打印模板和操作界面混在一起按钮、侧边栏都要用 CSS 隐藏偶尔会出现打印内容带上操作界面残留或者样式互相污染。路线 B 是开一个隐藏的 BrowserWindow单独加载一个打印模板页面主进程在 did-finish-load 之后对该窗口调用 print。我一般选 B原因是小票模板可以单独调试数据从主进程传进去模板本身不依赖业务页面状态打印坏了不会影响主界面。electron-vue-print-demo 这类项目的典型结构也是 B主窗口负责业务操作打印窗口是独立页面。缺点是数据要通过一次 IPC但打印本身是低频操作这点性能损耗可以忽略。路线 B 的数据传递方式是渲染进程通过 IPC 把要打印的数据发给主进程主进程新开一个隐藏窗口加载打印模板的 URL等页面渲染完成后再把数据注入模板窗口。这样模板里只用一段脚本读取注入的数据拼完 HTML 交给 CSS 排版打印逻辑和业务界面完全解耦。3. 把 electron vuecli3 打印小票的项目跑起来初始化到第一个打印弹窗3.1 用 Vue CLI 3 初始化项目并接入 electron-builder常见做法是用 vue add electron-builder 把 Electron 接入 Vue CLI 3。命令如下npm install -g vue/cli vue create electron-vue-print-demo cd electron-vue-print-demo vue add electron-builder第一行安装 Vue CLI如果你已经装过可以跳过。第二行创建 Vue CLI 3 项目模板选 default 就能继续不用急着上 TypeScript。vue add electron-builder 会自动安装 Electron、修改 package.json、生成一个主进程入口文件一般是 background.js。我建议在执行 vue add 时注意它的提示它会问“如何安装 Electron”选 npm 即可。如果下载慢把 Electron 的二进制下载地址换成国内镜像这一步不换会卡很久。装完之后 package.json 里会多出两个脚本electron:serve 用于开发模式启动 Electron 窗口electron:build 用于打包安装包。开发时只要跑 npm run electron:serveElectron 会加载当前 dev server。3.2 主进程创建打印窗口并接收渲染进程请求electron-builder 生成的主进程入口是 background.js里面已经有创建主窗口的逻辑。我们在此基础上加上打印窗口和 IPC 监听const { app, BrowserWindow, ipcMain } require(electron) const path require(path) let mainWindow null let printWin null function createMainWindow() { mainWindow new BrowserWindow({ width: 1200, height: 800, webPreferences: { preload: path.join(__dirname, preload.js), contextIsolation: true, nodeIntegration: false } }) if (process.env.WEBPACK_DEV_SERVER_URL) { mainWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL) } else { mainWindow.loadFile(path.join(__dirname, index.html)) } } ipcMain.handle(print-receipt, async (event, payload) { const { url, deviceName, silent, ticketData } payload printWin new BrowserWindow({ width: 240, height: 600, show: false, webPreferences: { contextIsolation: true, nodeIntegration: false } }) await printWin.loadURL(url) await printWin.webContents.executeJavaScript( window.__ticketData ${JSON.stringify(ticketData)} ) printWin.webContents.on(did-finish-load, () { printWin.webContents.print( { silent: silent || false, deviceName: deviceName || , printBackground: true }, (success, failureReason) { console.log(print result:, success, failureReason) printWin.close() printWin null } ) }) return true }) app.whenReady().then(createMainWindow)代码逻辑渲染进程用 ipcRenderer.invoke 发起 print-receipt 请求主进程收到后先开一个隐藏窗口加载打印模板的 URL然后用 executeJavaScript 把要打印的数据挂到模板页面的 window 对象上。等 did-finish-load 触发再对该窗口调用 print。回调里能拿到 success 和 failureReason出纸后关闭窗口。参数说明width 和 height打印窗口的尺寸直接决定渲染内容宽度58mm 小票我常用 240px 宽对应约 48mm 打印区。silent为 true 时不弹系统打印对话框直接把作业送进打印机队列为 false 时弹对话框方便调试。deviceName传打印机设备名不传就用系统默认打印机。ticketData通过 executeJavaScript 注入的数据放在 loadURL 之后执行避免页面先渲染出空数据。3.3 渲染进程触发打印的最小代码与 preload 桥接为了在 Vue 组件里安全调用 Electron 能力我用 preload 暴露一个最小接口// preload.js const { contextBridge, ipcRenderer } require(electron) contextBridge.exposeInMainWorld(receiptPrinter, { print: (payload) ipcRenderer.invoke(print-receipt, payload) })contextIsolation 开启时网页上下文和 Electron 主进程隔离Vue 里不能用 require(electron)所以要用 contextBridge 暴露白名单方法。主窗口和打印窗口都指向同一个 preload后续打印模板如果要读取系统信息也能复用同一个能力。Vue 组件里的触发代码// PrintButton.vue script export default { methods: { async onClickPrint() { try { const res await window.receiptPrinter.print({ url: process.env.BASE_URL #/receipt-template, deviceName: 58mm 热敏打印机, silent: true, ticketData: { shopName: 某店, orderNo: NO.20250101, items: [ { name: 咖啡, qty: 1, price: 28.00 } ], total: 28.00 } }) console.log(print resolved, res) } catch (err) { console.error(print failed, err) } } } } /scripturl 是打印模板的 hash 路由地址。如果用 history 模式路由加载 #/receipt-template 时就要带路由 base 的完整地址更省事的做法是小票模板单独做一个静态页面不依赖 Vue Router减少加载时间。ticketData 在按钮事件里构造实际项目里一般是从订单状态对象里提取。4. 小票打印必调参数58mm 纸张、打印机名与静默打印的边界4.1 print 参数逐个拆解silent、deviceName、printBackgroundprint 方法最常用的参数如下表参数类型默认值说明silentbooleanfalsetrue 时静默送纸不弹打印对话框deviceNamestring系统默认打印机指定打印机设备名printBackgroundbooleanfalse是否打印背景色和背景图colorbooleantrue是否按彩色方式输出landscapebooleanfalse是否横版marginsobject默认边距可传 top / bottom / left / right小票场景必调的是前三个。printBackground 必须打开否则模板里的深色 header、logo 会变白块。color 对热敏小票意义不大但如果你用标签纸打印带色二维码保持 true。margins 也容易被忽略。热敏小票纸本身打印区域左右已经很窄默认边距再加上 5mm 到 10mm最后输出和模板预览会差很多。我一般把 margins 全部置 0用 CSS padding 来控制内容位置。这样排版更可控也避免不同打印机驱动对边距理解不一致。4.2 用 CSS 控制小票宽度page 与容器宽度的配合只靠 print 接口参数还不够渲染层必须匹配小票纸宽。我常用的 CSS 模板media print { page { size: 58mm 120mm; margin: 0; } body { margin: 0; width: 48mm; background: #fff; } .receipt { width: 48mm; padding: 2mm 1mm; font-size: 12px; line-height: 1.4; } .receipt table { width: 100%; } }page 里的 size 是告诉打印驱动期望的纸张规格这里的边长决定打印机走纸位置。.receipt 容器宽度 48mm 对应 58mm 小票纸的有效打印区。不同热敏驱动对 page 的支持不完全一致有些老驱动直接忽略 size只认驱动里预设纸张所以要同时在打印接口里控制窗口宽度和内容宽度两条腿走路才能最大概率对齐。小票字体有个经验值最小不要小于 10px。12px 正文字号在热敏纸上比较清楚低于 10px 容易糊成一片。同字号在白色不干胶标签纸上会更清晰因为热敏纸高速走纸时会晕色。二维码尺寸不要小于 36px太小的二维码在高速打印时容易断码、扫不出来。4.3 静默打印的边界驱动、默认打印机与切纸指令silent: true 做到点按钮直接出票但有三个边界必须清楚。第一silent 不等于绕过打印队列。作业还是会进系统假脱机如果打印机卡纸、缺纸任务会排在那里界面没有取消机会。生产环境里最好在打印前做一次设备状态检查或者至少给出“已送打”的提示。第二deviceName 必须和系统里显示的打印机名完全一致。空格也会导致找不到打印机最常见的翻车是设备名带后缀“副本 1”或“(Copy 1)”拷贝驱动后名称变了代码没更新。第三小票机切纸有两种方式驱动自动切纸和指令切纸。Electron 的 webContents.print 只负责把文档内容送到打印机切纸指令由驱动和打印机协议处理。如果换了打印机后任务显示成功但不出票去打印机驱动设置里找“切纸模式”改成自动切纸。关于纸张尺寸print 接口里的 pageSize 单位是微米。58mm 宽是 5800080mm 宽是 80000高度可以给一个较大值因为走纸长度由内容实际高度决定。单位传错会导致打印出异常大的白纸这个坑我见过不止一次。5. 打印翻车现场排查常见问题定位与虚拟打印机验收5.1 打印空白先查加载完成时机现象打印出纸纸上空白或 PDF 空白。原因多是 did-finish-load 触发时异步渲染还没完成隐藏窗口就开始打印。解决在 did-finish-load 后轮询模板页面的渲染完成标志再调 print。await printWin.webContents.executeJavaScript( new Promise(resolve { if (window.__renderDone) resolve(true) else setTimeout(() resolve(window.__renderDone), 500) }) )5.2 样式错乱直奔纸张定义三件套现象内容被左右裁切或留大片白边。先检查三件事打印接口 pageSize、CSS page 尺寸、打印机驱动里的纸张定义。容器宽度超过有效打印区也会裁切把 48mm 写死到模板宽度层。5.3 打印机不响应设备名与队列流水线现象silent: true 点击后无反应。先调用 getPrintersAsync 列出系统识别到的设备名核对 deviceName再把 silent 改 false 手动打一次能弹框说明驱动和队列正常问题只在设备名。如果手动也卡住去系统打印队列清掉积压任务。5.4 用虚拟打印机做验收低成本回归方法Windows 自带的“Microsoft Print to PDF”可以当验收工具。每次改打印模板先用虚拟 PDF 打印机跑一遍确认排版后再对真实热敏机出票。检查三件事单票内容完整、左右留白均匀、二维码能扫出内容。我最早做小票打印时连续几次遇到“打印空白”最后发现都是加载时机太早。后来养成习惯改一次模板先用 PDF 虚拟打印机回归一次再真机出票。Electron 的小票打印其实不算玄学把加载完成时机控制住、打印机名核对准稳定度比纯浏览器方案高一个数量级。希望这篇能帮你把 electron-vue-print-demo 跑通再改成自己的店铺模板。本文还有配套的精品资源点击获取

相关新闻

制造业RAG开源框架:从文档解析到故障诊断的完整落地实践

制造业RAG开源框架:从文档解析到故障诊断的完整落地实践

写在前面说实话,制造业的 RAG(检索增强生成)做起来比互联网场景要棘手得多。互联网做问答,文档多半是排版规范、语言通用的网页,切片、召回、重排的套路都能直接复用。但制造业的文档是什么样?设备手册、维…

2026/10/14 22:26:51 阅读更多 →
Electron+Vue实现小票打印:从方案选型到避坑实践全解析

Electron+Vue实现小票打印:从方案选型到避坑实践全解析

简介:面向 Electron 桌面应用开发者,这份代码演示了如何用 Electron Vue CLI 3 实现小票打印:用户点击打印后,程序从 electron-store 读取本地打印机配置,未设置时弹出设置窗口,确认后即可静默打印。压缩包…

2026/10/14 22:26:51 阅读更多 →
为 Etherpad 协作文档添加标题结构:ep_headings2 插件全解析

为 Etherpad 协作文档添加标题结构:ep_headings2 插件全解析

简介:ep_headings2 是一个面向 Etherpad 的标题插件,适合协作文档平台的普通用户、管理员和前端开发者。它通过编辑栏中的快捷按钮,将标题层级应用到 pad 内容,避免手工输入 HTML 标签,提升长文档导航体验。资源为开源…

2026/10/14 22:26:51 阅读更多 →

最新新闻

2S串联电芯主动均衡方案:基于MP2642与STM32的BMS设计

2S串联电芯主动均衡方案:基于MP2642与STM32的BMS设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 2:15:17 阅读更多 →
单片机C库运行时:libspace、多任务重入与中断安全实战

单片机C库运行时:libspace、多任务重入与中断安全实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 2:15:17 阅读更多 →
Apache配置HTTP跳转HTTPS:两种稳定方案与常见坑解析

Apache配置HTTP跳转HTTPS:两种稳定方案与常见坑解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 2:15:17 阅读更多 →
DES安全性复盘与3DES续命逻辑:从暴力破解到中间相遇攻击

DES安全性复盘与3DES续命逻辑:从暴力破解到中间相遇攻击

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 2:15:17 阅读更多 →
隐式类型转换导致索引失效终结:从字符串与数字比较的底层逻辑切入

隐式类型转换导致索引失效终结:从字符串与数字比较的底层逻辑切入

在 MySQL 与各类关系型数据库调优中,有一个让无数开发和 DBA 既熟悉又抓狂的经典翻车案例:开发同学信誓旦旦地说:“大喜姐,我明明在 phone(手机号)字段上建了二级索引,为什么一跑生产查询&#…

2026/10/15 2:15:17 阅读更多 →
从零开始做SRC白帽:挖漏洞拿赏金,这些法律红线千万不要踩

从零开始做SRC白帽:挖漏洞拿赏金,这些法律红线千万不要踩

从零开始做SRC白帽:挖漏洞拿赏金,这些法律红线千万不要踩 很多新手入坑网络安全,初衷都很简单:想学一门硬核技术、靠挖漏洞赚赏金、做合法合规的白帽黑客。 我从纯零基础入门白帽,从零了解SRC规则、练习漏洞挖掘、提交…

2026/10/15 2:14:17 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →