vite-electron 静默打印功能实现

系列文章目录

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:

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标签
    }
  })

1、属性

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

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

如下:

javascript 复制代码
     <webview
      :src="webviewUrl"
      webpreferences="contextIsolation=false"
      nodeintegration
    />

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

javascript 复制代码
主进程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)发出消息

javascript 复制代码
      <webview
      :src="webviewUrl"
      webpreferences="contextIsolation=false"
      nodeintegration
      @ipc-message="onWebviewIpcMessage"
    />
javascript 复制代码
 <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类型

javascript 复制代码
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

渲染进程:

javascript 复制代码
      <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):

javascript 复制代码
<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):

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

渲染进程:

javascript 复制代码
 <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

javascript 复制代码
<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(打印内容):

javascript 复制代码
<!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路径

javascript 复制代码
  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

javascript 复制代码
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等

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

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

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

踩坑说明

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

javascript 复制代码
npm install electron@24
相关推荐
To_OC8 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC9 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic9 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy9 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志9 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble10 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长11 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯11 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜12 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司12 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能