系列文章目录
文章目录
- 系列文章目录
- 前言
- 一、实现方案
- [二、< webview />讲解](#二、< webview />讲解)
- [三、 webview与渲染进程通信](#三、 webview与渲染进程通信)
- 四、代码实战
前言
本文将介绍基于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