vite-electron 静默打印功能实现

本文介绍了如何在基于electron-vite和vue3的项目中,使用webview标签实现静默打印功能,包括设置webview属性、监听事件、双向通信以及解决打印样式问题。实例以热敏打印机打印二维码小票功能为核心,详细展示了代码实现和可能遇到的坑点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系列文章目录

electron+vite+vue3 快速入门教程



前言

本文将介绍基于electron-vite构建工具下vue3项目内如何实现打印机静默打印功能,并以热敏打印机打印二维码为示例用代码实现该功能。


一、实现方案

electron实现打印方案有2种:
1、webContents.print({deviceName}):主线程内创建一个新窗口打印整个窗口内容
2、< webview />标签引入本地静态html,通过webview dom对象调用 print({deviceName})方法打印html内容

ps:deviceName为打印机设备名称,可通过webContents.getPrintersAsync()获取

webview 主要在渲染进程进行操作相对于webContents方案来说传值比较方便,用起来比较简单顺手,本文将采用webview 方案进行讲解

二、< webview />讲解

< webview />标签类似iframe,内容内嵌窗口显示,Electron >= 5默认禁用 webview 标签,开启需要在主进程创建窗口设置webPreferences.webviewTag为ture

主进程main.js:

  mainWindow = new BrowserWindow({
    width,
    height,
    show: false,
    maximizable: true,
    autoHideMenuBar: true,
    minHeight: height * 0.9,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false,
      webviewTag: true,//开启webview标签
    }
  })

1、属性

src:html文件本地路径或网络url,由于开发环境和生产环境加载资源方式不同,本地html文件打包后要生效需要通过主进程获取
nodeintegration:允许使用node APIs
webpreferences:是一个由逗号分割的字符串列表,其中指定了要设置在 webview 上的 Web 首选项

实现打印功能需要webview跟渲染进程双向通信,所以首选项需要关闭隔离并使用nodeApi
如下:

     <webview
      :src="webviewUrl"
      webpreferences="contextIsolation=false"
      nodeintegration
    />

不仅需要在webview标签上设置,同时需要在主进程上设置才会生效:

主进程main.js:

```javascript
  mainWindow = new BrowserWindow({
    width,
    height,
    show: false,
    maximizable: true,
    autoHideMenuBar: true,
    minHeight: height * 0.9,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false,
      webviewTag: true,//开启webview标签
      
      nodeIntegration: true,//使用node Api
      contextIsolation: false,//关闭隔离
    }
  })

2、 监听事件

dom-ready:webview渲染完成回调。

ipc-message:监听webview(html)发出消息

      <webview
      :src="webviewUrl"
      webpreferences="contextIsolation=false"
      nodeintegration
      @ipc-message="onWebviewIpcMessage"
    />
 <script setup>
 const onWebviewIpcMessage=event=>{
      if(event.channel == 'startPrint'){//startPrint  消息自定义标识符
         console.log("来自webview消息")
      }
 }
 </script>

3、方法

< webview>.print([options]):开始打印

options:
silent:是否静默打印,值Boolean类型
deviceName:打印设备名称,值String类型
margins: 设置纸张边距,值为对象marginType :{top,bottom.left,right}分别设置上下左右边距
pageSize:设置纸张尺寸,可选值A4,A5,A6等或者对象包含宽高属性{height:210000,width:58000} ,单位微米
header : 设置页眉内容,值String类型
footer: 设置页脚内容,值String类型

webviewRef.value.print({
      silent: true,//静默打印
      deviceName: 'XP-58',//设备名称
      margins: { marginType: 'none' },//无边距
      pageSize:{
         height:210000
         width:58000
        }
    })

更多options属性可查看< webview>.print([options])


三、 webview与渲染进程通信

1.渲染进程—>webview

webview.send + ipcRenderer.on

渲染进程:

      <webview
      ref="webviewRef"
      :src="webviewUrl"
      webpreferences="contextIsolation=false"
      nodeintegration
      @dom-ready="onReady"
    />
 <script setup>
   import {ref} from 'vue';
   const webviewRef=ref();
   const onReady=()=>{
     //给webview发送消息
      webviewRef.value.send("message","from renderer")
   }
</script>   

webview(print.html):

<script>
  const { ipcRenderer } = require('electron')
  //接收渲染进程消息
   ipcRenderer.on("message",res=>{
    console.log(res)// from renderer  
})
</script>

2.webview—>渲染进程:

ipcRenderer.sendToHost + < webview/>标签ipc-message事件

webview(print.html):

<script>
  const { ipcRenderer } = require('electron')
  //给渲染进程发送消息
  ipcRenderer.sendToHost('startPrint')
})
</script>

渲染进程:

 <webview
      ref="webviewRef"
      :src="webviewUrl"
      webpreferences="contextIsolation=false"
      nodeintegration
      @ipc-message="onWebviewIpcMessage"
    />
 <script setup>
   import {ref} from 'vue';
   const webviewRef=ref();
   //接收webview发送的消息
    const onWebviewIpcMessage = (event) => {
     if (event.channel == 'startPrint') {
       console.log("收到webview消息")
    }
   }
</script> 

四、代码实战

下面实现如下场景:热敏打印机打印产品二维码小票功能,从接口动态获取产品规格和产品型号写入数据并打印出来

要实现效果图:

请添加图片描述

请添加图片描述

渲染进程文件index.vue

<template>
  <div class="container">
    <webview
    class="webview"
      ref="webviewRef"
      :src="webviewUrl"
      webpreferences="contextIsolation=false"
      nodeintegration
      @ipc-message="onWebviewIpcMessage"
    />
    <button class="btn" @click="handlePrint">打印</button>
  </div>
</template>
<script setup>
import { ref} from 'vue'

const webviewRef = ref()
const webviewUrl = ref()

//从主进程获取html文件路径
electron.ipcRenderer.invoke('getWebviewFile').then((file) => {
    webviewUrl.value = file
})


//接收webview发送的消息
const onWebviewIpcMessage = (event) => {
//webviwe数据设置完毕开始打印
  if (event.channel == 'startPrint') {
    webviewRef.value.print({
      silent: true,//静默打印
      deviceName: 'XP-58',//演示直接写死,实际开发可从主进程webContents.getPrintersAsync()获取
      margins: { marginType: 'none' },//无边距
    })
  }
}

//打印
const handlePrint = () => {
  //规格和批号参数演示写死,实际可从接口请求获取
  webviewRef.value && webviewRef.value.send('render', { sp:'H4254c',no:'n700a025' })
}

</script>
<style lang="scss" scoped>
.container{
    padding: 30px;
    box-sizing: border-box
}
.webview{
    height: 50vh;
    width:300px;
    padding: 0px;
    background-color: #fff;
}
.btn{
    margin-top: 30px;
    width: 200px;

}
</style>

webiview:
renderer/public目录(没有就新建)新建html目录和print.html文件
在这里插入图片描述

print.html(打印内容):

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      * {
        margin: 0;
        padding: 0;
      }
    
      @page {
        margin: 0;
        size: 58mm 210mm;/**设置纸张尺寸,也可以填A4、A5等*/
      }
      .page {
        width: 100%;
        display: flex;
        flex-direction: column;
        padding: 0;
        box-sizing: border-box;
        margin: 0;
       
      }
      .img {
        width: 100%;
        height: auto;
      }
      .content {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        font-size: 20px;
        margin-top: 10px;
      }
      .txt {
        line-height: 30px;
      }

      /**打印样式增加内边距*/
      @media print {
        .page {
          width: 100%;
          display: flex;
          flex-direction: column;
          box-sizing: border-box;
          margin: 0;
          padding: 3mm;
          box-sizing: border-box;
        }
      }
    </style>
  </head>
  <body>
    <div  class="page">
      <img class="img" src="data:image/png;base64,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" />
      <div class="content">
        <span id="sp" class="txt"></span>
        <span id="no" class="txt"></span>
      </div>
    </div>
  </body>
  <script>
    const { ipcRenderer } = require('electron')

    //监听渲染进程发送过来的数据
    ipcRenderer.on('render', (e, res) => {
      //从渲染进程获取规格、批号数据更新到页面
       document.getElementById('sp').innerHTML=`产品规格:${res.sp}`
       document.getElementById('no').innerHTML=`产品批号:${res.no}`
       //通知渲染进程开始打印
      ipcRenderer.sendToHost('startPrint')
    })
  </script>
</html>

主进程main/index.js:

添加获取print.html路径

  ipcMain.handle('getWebviewFile', () => {
    if (is.dev && process.env['ELECTRON_RENDERER_URL']) {//开发环境
      return process.env['ELECTRON_RENDERER_URL'] + '/html/print.html'
    } else {//生产环境
      return join(__dirname, '../renderer/html/print.html')
    }
  })

开启webview标签、关闭隔离、使用nodeApi

function createWindow() {
  const primaryDisplay = screen.getPrimaryDisplay()
  const { width, height } = primaryDisplay.workAreaSize
  // Create the browser window.
  mainWindow = new BrowserWindow({
    width,
    height,
    show: false,
    maximizable: true,
    // resizable:false,
    autoHideMenuBar: true,
    minHeight: height * 0.9,
    // frame: false,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false,
      webSecurity: false, //跨域处理,
      
      webviewTag: true,//开启webview标签
      nodeIntegration: true,//使用node Api
      contextIsolation: false,//关闭隔离

    }
  })

打印样式说明

1.除了从打印函数设置参数也可以通过@page设置打印页面样式,size支持A4,A5等常见纸张尺寸或者自定义长宽,单位支持mm、cm等

      @page {
        size: 58mm 210mm;/**设置纸张尺寸 58mm*210mm*/
        margin:0,/**无边距*/
      }
 @page {
        size: A4;/**设置纸张大小A4*/
      }

2.@media print 单独设置打印样式

 <style>
   /**web渲染样式*/
   .img{
      width:200px
     }
 /**打印样式**/
 @media print {
   .img{
      width:30mm
     }
 }
</style>

踩坑说明

当前electron最新版本为28,此版本有bug设置纸张尺寸和样式会无效,导致打印出来的样式很大或者很小,可以回退到24版本解决

npm install electron@24
### 前端实现静默打印的方法 #### 背景说明 在现代 Web 开发中,前端实现静默打印的需求较为常见,尤其是在企业级应用中。通过特定的技术手段,开发者可以让用户的浏览器自动调用本地打印机完成打印操作而无需手动干预。 --- #### 使用 HTML 和 CSS 的基础方法 一种简单的方式是利用 `@media print` 样式来隐藏页面上不需要打印的内容,并仅显示目标区域。这种方法适用于简单的场景,但无法完全满足“静默”的需求,因为仍然会弹出打印对话框[^4]。 ```css @media print { body * { visibility: hidden; } #print-content, #print-content * { visibility: visible; } #print-content { position: absolute; top: 0; left: 0; } } ``` 上述代码定义了一个名为 `#print-content` 的容器作为打印的目标内容,在触发打印时其他部分会被隐藏。 --- #### 结合 JavaScript 实现更高级的功能 为了进一步减少用户交互并模拟“静默”效果,可以通过 JavaScript 手动控制打印行为: ```javascript function silentPrint() { const contentToPrint = document.getElementById('print-content').innerHTML; // 获取要打印的内容 const originalContent = document.body.innerHTML; document.body.innerHTML = contentToPrint; // 替换整个页面为需要打印的部分 window.print(); // 触发打印事件 document.body.innerHTML = originalContent; // 还原原始页面内容 } // 绑定到按钮或其他触发器 document.getElementById('print-button').addEventListener('click', silentPrint); ``` 此方式虽然能够简化流程,但仍需依赖浏览器内置的打印机制,因此可能仍会出现打印预览窗口。 --- #### 利用 Electron 或第三方库增强能力 对于更加复杂的业务场景(如 SAP UI5 中提到的企业内部解决方案),可以考虑借助框架或工具扩展功能。以下是两种常见的技术路径: 1. **Electron 应用中的静默打印** 如果应用程序运行环境支持 Node.js,则可以直接访问操作系统级别的 API 来配置默认打印机参数以及跳过确认步骤。例如,基于 vite-electron 构建的应用程序可通过以下逻辑实现真正的无提示打印[^2]: ```javascript const { app, BrowserWindow, ipcMain } = require('electron'); const path = require('path'); let win; function createWindow () { win = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js') } }); win.loadFile('index.html'); ipcMain.on('silent-print', (event, options) => { win.webContents.print(options, (success, errorType) => { if (!success) console.log(errorType); }); }); } app.whenReady().then(createWindow); ``` 上述代码片段展示了如何监听来自渲染进程的消息 (`silent-print`) 并执行静默打印命令。注意这里的选项对象允许指定是否展示界面以及其他细节设置。 2. **Vue 插件集成** 对于采用 Vue 框架开发的项目来说,也可以参考 electron+vue 的实践指南引入专门处理打印事务的服务模块[^3]。具体而言就是创建自定义指令或者组件封装底层复杂度,最终对外暴露统一接口供视图层调用即可。 --- ### 总结 综上所述,从前端角度出发解决静默打印问题可以从多个层面入手:如果只是希望优化用户体验那么调整样式配合脚本就足够;要是追求极致自动化则建议结合桌面平台特性深入定制化服务。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pixle0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值