大致流程
1.首先找一个串口工具(sscom5.12.1)试试读取串口是否成功连上;
2.创建electron-vue的项目;
3.安装依赖,调整版本,启动项目;(在electron中使用串口_electron 串口_Jack_Kee的博客-CSDN博客)
4.学习SerialPort Usage | Node SerialPort工具;
5.开发入口文件,测试是否成功获取串口信息
6.成功获取串口后,接收台称数据
7.根据台称说明书与协议,解析台称数据(数值,单位,正负,精度...)
8.完成称重需求
9.完成打印需求
10.完成自动更新
11.防止开启多个应用
1.串口工具大概样子
通过切换端口号和波特率,可以查看哪个是台称串口及波特率,我本机的端口号COM3 波特率9600是台秤的串口信息。
2.创建electron项目(起步 · electron-vue)
跟着官方步骤 创建electron-vue项目,成功启动项目(启动,打包)
"electron": "^17.4.11",
3.引入serialport
npm install serialport --save
然后,安装electron-rebuild(用来重新编译serialport包):
npm install --save-dev electron-rebuild
重新编译serialport包
.\node_modules\.bin\electron-rebuild.cmd
提示Rebuild Complete,表示编译完成
4.按照该文章,实现最终效果注意安装依赖版本,
此注明我项目使用的各个版本
"dependencies": {
"@serialport/parser-byte-length": "^11.0.0",
"axios": "^0.18.0",
"electron-updater": "^5.3.0",
"element-ui": "^2.8.2",
"lodash": "^4.17.21",
"multispinner": "^0.2.1",
"qrcodejs2": "^0.0.2",
"serialport": "^11.0.0",
"vue": "^2.5.16",
"vue-electron": "^1.0.6",
"vue-router": "^3.0.1",
"vuex": "^3.0.1",
"vuex-electron": "^1.0.0",
"vuex-persistedstate": "^4.1.0"
},
"devDependencies": {
"ajv": "^6.5.0",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.4",
"babel-minify-webpack-plugin": "^0.3.1",
"babel-plugin-component": "^1.1.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.26.0",
"cfonts": "^2.1.2",
"chalk": "^2.4.1",
"copy-webpack-plugin": "^4.5.1",
"cross-env": "^5.1.6",
"css-loader": "^0.28.11",
"del": "^3.0.0",
"devtron": "^1.4.0",
"electron": "^17.4.11",
"electron-builder": "^23.6.0",
"electron-debug": "^1.5.0",
"electron-devtools-installer": "^2.2.4",
"electron-rebuild": "^3.2.9",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"listr": "^0.14.3",
"mini-css-extract-plugin": "0.4.0",
"node-loader": "^0.6.0",
"node-sass": "^4.9.2",
"sass-loader": "^7.0.3",
"style-loader": "^0.21.0",
"url-loader": "^1.0.1",
"vue-html-loader": "^1.2.4",
"vue-loader": "^15.2.4",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.15.1",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.4",
"webpack-hot-middleware": "^2.22.2",
"webpack-merge": "^4.1.3"
}
4. SerialPort (注意不同版本使用方式不同)
serialport:10.x.x
import { SerialPort } from 'serialport'
serialport:7.x.x
const SerialPort = require('serialport')
项目操作案例:
const { ByteLengthParser } = require("@serialport/parser-byte-length");
let ports = []; // 串口list
let mainWindow = null; // electron 窗口
function registerIpcEvent() {
// 发送可选串口
SerialPort.list().then((ports) => {
setTimeout(() => {
mainWindow.webContents.send("send-port-info", ports);
}, 1000);
});
// 关闭窗口
app.on("window-all-closed", function () {
if (process.platform !== "darwin") {
ports.forEach((e) => {
e && e.close();
});
app.quit();
}
});
ipcMain.on("closeApp", () => {
ports.forEach((e) => {
e && e.close();
});
app.quit();
});
// 重新初始化串口
ipcMain.on("init-port", (event, args) => {
initPort(args);
});
// 关闭串口
ipcMain.on("close-serialport", (event, args) => {
const currentPort = ports.find((i) => i.path === args.name);
currentPort && currentPort.close();
if (currentPort && !currentPort.isOpen) {
event.reply("close-serialport", { name: args.name });
}
ports = ports.filter((e) => e !== currentPort);
});
// 打开某个串口
ipcMain.on("open-serialport", (event, args) => {
const serialport = new SerialPort(
{
path: args.name,
baudRate: +args.baudRate,
},
(err) => {
console.log(err);
if (err) {
event.reply("open-serialport", {
hasError: true,
...args,
message: err,
});
} else {
event.reply("open-serialport", args);
ports.push(serialport);
}
}
);
// 根据说明书数据量字节17一次传输 也可以通过其他方式截取
const parser = serialport.pipe(new ByteLengthParser({ length: 17 }));
parser.on("data", (data) => {
// xxxxxxx
});
serialport.on("open", () => {});
});
}
function initPort(bool) {
if (bool) {
SerialPort.list().then((ports) => {
setTimeout(() => {
mainWindow.webContents.send("send-port-info", ports);
}, 1000);
});
}
}
还可以通过下面的形式截断串口数据
正则匹配:parser-regex
一种转换流,它使用正则表达式来拆分传入的文本。
要使用Regex解析器,请提供一个正则表达式来拆分传入的文本。数据作为可由编码选项控制的字符串发出(默认为utf8)
const { SerialPort } = require('serialport')
const { ReadlineParser } = require('@serialport/parser-readline')
const port = new SerialPort({ path: '/dev/ROBOT', baudRate: 14400 })
const parser = port.pipe(new ReadlineParser({ delimiter: '\r\n' }))
parser.on('data', console.log)
分隔符截取:parser-readline
在接收到换行符后发出数据的转换流。若要使用Readline分析器,请提供分隔符(默认为\n)。数据以可由编码选项控制的字符串形式发出(默认为utf8)。
const { SerialPort } = require('serialport')
const { RegexParser } = require('@serialport/parser-regex')
const port = new SerialPort({ path: '/dev/ROBOT', baudRate: 14400 })
const parser = port.pipe(new RegexParser({ regex: /[\r\n]+/ }))
parser.on('data', console.log)
5.开发入口文件
const { app, BrowserWindow, ipcMain, Menu, dialog } = require("electron");
const { SerialPort } = require("serialport");
const _ = require("lodash");
const { ByteLengthParser } = require("@serialport/parser-byte-length");
import { updateHandle } from "../renderer/utils/update.js";
let ports = [];
const path = require("path");
const fs = require("fs");
//读取本地更新配置json文件 包含是否调试 请求地址 更新地址...
const File_path = path
.join(path.resolve("./../"), "/config.json")
.replace(/\\/g, "/");
const config_res = JSON.parse(fs.readFileSync(File_path, "utf-8"));
let mainWindow = null;
const winURL =
process.env.NODE_ENV === "development"
? `http://localhost:9080`
: `file://${__dirname}/index.html`;
app.whenReady().then(() => {
createWindow();
registerIpcEvent();
// getPrinterList(); 注释打印
config_res.IS_DEBUG && createMenu();
app.on("activate", function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
frame: config_res.IS_DEBUG, // 是否有边框窗口
fullscreen: !config_res.IS_DEBUG, // 全屏
webPreferences: {
// preload: path.join(__dirname, "preload.js"),
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
webviewTag: true,
webSecurity: false,
},
});
config_res.IS_DEBUG ? mainWindow.webContents.openDevTools() : ""; // 生产模式调试开关
setTimeout(() => {
mainWindow.loadURL(winURL);
}, 1000);
// 监听崩溃
mainWindow.webContents.on("crashed", () => {
const options = {
type: "error",
title: "系统意外终止",
message: "可点击以下按钮",
buttons: ["点击重启", "直接退出"],
};
dialog.showMessageBox(options, (index) => {
if (index === 0) reloadWindow(mainWindow);
else app.quit();
});
});
}
function createMenu() {
// main
const template = [
{
label: "调试",
click: function (item, focusedWindow) {
if (focusedWindow) {
config_res.IS_DEBUG && focusedWindow.toggleDevTools();
}
},
},
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
function initPort(bool) {
if (bool) {
SerialPort.list().then((ports) => {
setTimeout(() => {
mainWindow.webContents.send("send-port-info", ports);
}, 1000);
});
}
}
function registerIpcEvent() {
// 发送可选串口
SerialPort.list().then((ports) => {
setTimeout(() => {
mainWindow.webContents.send("send-port-info", ports);
}, 1000);
});
// 关闭窗口
app.on("window-all-closed", function () {
if (process.platform !== "darwin") {
ports.forEach((e) => {
e && e.close();
});
app.quit();
}
});
ipcMain.on("closeApp", () => {
ports.forEach((e) => {
e && e.close();
});
app.quit();
});
// 重新初始化串口
ipcMain.on("init-port", (event, args) => {
initPort(args);
});
// 关闭串口
ipcMain.on("close-serialport", (event, args) => {
const currentPort = ports.find((i) => i.path === args.name);
currentPort && currentPort.close();
if (currentPort && !currentPort.isOpen) {
event.reply("close-serialport", { name: args.name });
}
ports = ports.filter((e) => e !== currentPort);
});
// 打开某个串口
ipcMain.on("open-serialport", (event, args) => {
const serialport = new SerialPort(
{
path: args.name,
baudRate: +args.baudRate,
},
(err) => {
console.log(err);
if (err) {
event.reply("open-serialport", {
hasError: true,
...args,
message: err,
});
} else {
event.reply("open-serialport", args);
ports.push(serialport);
}
}
);
// 数据量字节截取拉大 获取大区间集合
const parser = serialport.pipe(new ByteLengthParser({ length: 17 * 10 }));
parser.on("data", (data) => {
let dataString = data.toString("hex");
sendData(event, {
// xxxxx 需要发送给页面的信息
});
});
serialport.on("open", () => {});
});
}
function sendData(event, value) {
event.sender.send("send-data", value);
}
6.需要展示串口信息的页面
1. 发送init-port 初始化请求
const { ipcRenderer } = require("electron");
ipcRenderer.send("init-port", true);
2.页面接收解析后的串口信息函数
data() {
return {
portNameList: [], // 多称选择
openedPort: [], // 已经打开的称信息
currPortData: 0.0, // 显示重量
currName: "", // 称名称
currRate: "9600", // 波特率
sign: "", //秤 正 负
rateOption: rateOption,// 波特率选择
};
},
watch: {
currName(val, oldval) {
if (val !== "" && oldval !== "") {
this.openOrclose("open", oldval);
}
},
},
methods: {
listSerialPorts() {
ipcRenderer.on("open-serialport", (event, args) => {
console.log("open-serialport", args);
if (args.hasError) {
alert(args.message);
} else {
this.openedPort.push(args.name);
console.log("open", this.openedPort);
}
});
ipcRenderer.on("close-serialport", (event, args) => {
const index = this.openedPort.findIndex((i) => i === args.name);
this.openedPort.splice(index, 1);
console.log("close", this.openedPort);
});
ipcRenderer.on("send-port-info", (event, args) => {
for (let port of args) {
this.portNameList.push({
portName: `称台${this.portNameList.length + 1}`,
name: port.path,
path: port.path,
});
}
this.currName = this.portNameList && this.portNameList[0].name;
console.log("send-port-info获取的串口", this.portNameList);
this.openOrclose("open");
});
ipcRenderer.on("send-data", (event, args) => {
console.log("send-data", args, this.resData);
this.currPortData = args.value; // 接收传入的信息 用于展示
});
},
openOrclose(type, oldname = "") {
if (this.portNameList.length <= 0) {
this.$message.error("台称读取失败,请重新进入页面或重新进入系统!");
return;
}
const name = this.currName;
const baudRate = this.currRate;
if (oldname !== "" && this.openedPort.includes(oldname)) {
this.sendMessageToMain("close-serialport", { name: oldname });
}
if (type === "close") {
if (this.openedPort.includes(name)) {
this.sendMessageToMain("close-serialport", { name });
}
} else {
this.sendMessageToMain("open-serialport", {
name: this.currName,
baudRate: this.currRate,
});
}
},
}
7.根据台称说明书与协议,解析台称数据(数值,单位,正负,精度...)
先看说明书梅特勒托利多METTLER TOLEDO
1.从说明书的是数据含有17/18字节 我通过走数据查到是17个字节
const bufferArray = dataString.split("0d")[1];
// 02 34 30 20 20 20 20 32 30 34 20 20 20 30 30 30 0d
2.连续输出格式 5-10字节代表的称指示重量,11-16字节表示皮重重量
3.数据是没有小数点的要通过解析算出小数点的位置和正负号
4.状态A,状态B,状态C 需要解析分别表示一些内容
状态A说明,main.js里面具体操作
let dataString = data.toString("hex");
const bufferArray = dataString.split("0d")[1];
// 02 34 30 20 20 20 20 32 30 34 20 20 20 30 30 30 0d
const bufferfloat = bufferArray.slice(2, 4);
// 小数点位置 截取byte第二位 根据说明书转为二进制查看0,1,2位的值判断小数点位置
let float = parseInt(bufferfloat, 16).toString(2).substr(-3);
//16进制转为2进制 截取后三位;
let de = 1;
if (float === "100") {
de = 100;
} else if (float === "101") {
de = 1000;
} else if (float === "110") {
de = 10000;
} else if (float === "111") {
de = 100000;
}
var value = Number(weight) / de;
状态B说明,main.js里面具体操作
let dataString = data.toString("hex");
const bufferArray = dataString.split("0d")[1];
// 02 34 30 20 20 20 20 32 30 34 20 20 20 30 30 30 0d
const buffersign = bufferArray.slice(4, 6);
// 单位值位置 截取byte第2位 共2位
let signCode = parseInt(buffersign, 16).toString(2).substr(-2, 1);
//16进制转为2进制 截取倒数第2位; 从倒数第2位开始截取1个长度的字符
// signCode 0=正1=负
状态C说明,main.js里面具体操作
let dataString = data.toString("hex");
const bufferArray = dataString.split("0d")[1];
// 02 34 30 20 /20 20 20 32 30 34/ 20 20 20 30 30 30 0d
const bufferunit = bufferArray.slice(6, 8); // 单位值位置 截取byte第3位 共2位
let unitCode = parseInt(bufferunit, 16).toString(2).substr(-3);
let unit = null;
if (unitCode === "000") {
unit = "kg";
} else {
unit = "g";
}
8.称重需求 main.js 文件处理
// 打开某个串口
ipcMain.on("open-serialport", (event, args) => {
const serialport = new SerialPort(
{
path: args.name,
baudRate: +args.baudRate,
},
(err) => {
console.log(err);
if (err) {
event.reply("open-serialport", {
hasError: true,
...args,
message: err,
});
} else {
event.reply("open-serialport", args);
ports.push(serialport);
}
}
);
// 数据量字节17为一组 但发送太过频繁 所以增大10倍 减少页面渲染压力
const parser = serialport.pipe(new ByteLengthParser({ length: 17 * 10 }));
parser.on("data", (data) => {
let dataString = data.toString("hex");
const bufferArray = dataString.split("0d")[1];
// 02 34 30 20 /20 20 20 32 30 34/ 20 20 20 30 30 30 0d
const bufferfloat = bufferArray.slice(2, 4);
// 小数点位置 截取byte第二位 根据说明书转为二进制查看0,1,2位的值判断小数点位置
const buffersign = bufferArray.slice(4, 6);
// 正负号位置 截取byte第2位 共2位
const bufferunit = bufferArray.slice(6, 8);
// 单位值位置 截取byte第3位 共2位
const bufferweight = bufferArray.slice(8, 20);
// 重量值位置 截取byte第5-10位 共6位
const bufferpeel = bufferArray.slice(20, 32);
// 皮重值位置 截取byte第11-16位 共6位
let float = parseInt(bufferfloat, 16).toString(2).substr(-3);
//16进制转为2进制 截取后三位;
let signCode = parseInt(buffersign, 16).toString(2).substr(-2, 1);
//16进制转为2进制 截取倒数第2位; 从倒数第2位开始截取1个长度的字符。
let unitCode = parseInt(bufferunit, 16).toString(2).substr(-3);
let weight = hexToString(bufferweight.toString("hex"));
let peel = hexToString(bufferpeel.toString("hex"));
let de = 1;
let unit = null;
let sign = signCode; // sign 0=正1=负
if (float === "100") {
de = 100;
} else if (float === "101") {
de = 1000;
} else if (float === "110") {
de = 10000;
} else if (float === "111") {
de = 100000;
}
if (unitCode === "000") {
unit = "kg";
} else {
unit = "g";
}
var value = Number(weight) / de;
console.log("weights",value,float,weight,unit,peel,bufferArray,dataString,sign);
sendData(event, {
value,
float,
weight,
unit,
de,
peel,
bufferArray,
dataString,
sign
});
});
serialport.on("open", () => {});
});
function sendData(event, value) {
event.sender.send("send-data", value);
}
/**
* 将16进制字符串进行分组,每两个一组
* @param {[String]} str [16进制字符串]
* @return {[Array]} [16进制数组]
*/
//处理中文乱码问题
function utf8to16(str) {
var out, i, len, c;
var char2, char3;
out = "";
len = str.length;
i = 0;
while (i < len) {
c = str.charCodeAt(i++);
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
out += str.charAt(i - 1);
break;
case 12:
case 13:
char2 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
break;
case 14:
char2 = str.charCodeAt(i++);
char3 = str.charCodeAt(i++);
out += String.fromCharCode(
((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)
);
break;
}
}
return out;
}
function hexToString(str) {
// 30 空格 0d跨行
var val = "",
len = str.length / 2;
for (var i = 0; i < len; i++) {
val += String.fromCharCode(parseInt(str.substr(i * 2, 2), 16));
}
return utf8to16(val);
}
detail.vue 页面组装秤重量
console.log("weights",value,float,weight,unit,peel,bufferArray,dataString,sign);
// 接收到数据 处理数据展示样子 带上小数点,正负号,显示称设置的皮重
ipcRenderer.on("send-data", (event, args) => {
if (
this.resData &&
["kg", "千克", "公斤"].includes(this.resData.unit)
) {
if (args.unit === "kg") {
this.currPortData = args.value;
} else {
this.currPortData = args.value / 1000;
}
} else if (
["g", "克", "斤", "ml", "毫升"].includes(this.resData.unit)
) {
if (args.unit === "g") {
this.currPortData = args.value;
} else {
this.currPortData = args.value * 1000;
}
}
if (args.sign !== "") {
this.sign = args.sign === "0" ? "" : "-";
}
});
9.完成打印需求
1.主进程中初始化的时候请求打印机列表
2.发送事件到渲染进程,带上获取到的打印机列表参数
3.编写页面: webview 标签嵌入静态html文件path 获取打印机列表函数 触发打印的函数 监听渲染完成事件
4.外部触发打印组件的打印函数 传入需要打印的数据
5.打印组件 执行打印函数 获取列表 判断打印机状态 执行打印 获取打印参数 发起webview.send 传输数据
6.静态文件通过ipcRenderer.on("webview-print-render", xxx )接收打印组件发起的数据传输
7.静态文件通过传入的参数 渲染出正确的html页面 通过webview-print-do 发出渲染完成的消息
8.打印组件通过监听webview.addEventListener("ipc-message" ,xxx)执行
webview.print 打印成功
查询打印机列表方法
1.主进程中初始化的时候请求打印机列表
2.发送事件到渲染进程,带上获取到的打印机列表参数
function getPrinterList() {
// 在主线程下,通过ipcMain对象监听渲染线程传过来的getPrinterList事件
ipcMain.on("getPrinterList", async (event) => {
//在主线程中获取打印机列表
const list = await mainWindow.webContents.getPrintersAsync();
console.log(list, "getPrinterList");
//通过webContents发送事件到渲染线程,同时将打印机列表也传过去
mainWindow.webContents.send("getPrinterList", list);
});
}
app初始化的时候获取打印机列表
app.whenReady().then(() => {
getPrinterList();
});
打印机监控页面print.vue组件 等待发起打印需求
3.编写页面: webview 标签嵌入静态html文件path 获取打印机列表函数 触发打印的函数 监听渲染完成事件
<template>
<div class="container">
<webview
id="printWebview"
ref="printWebview"
:src="fullPath"
nodeintegration
plugins
webpreferences="contextIsolation=no"
/>
</div>
</template>
<script>
import { ipcRenderer } from "electron";
import path from "path";
export default {
name: "printer",
props: {
PrintEq: {
type: String,
default: "",
},
},
data() {
return {
printList: [],
printDeviceName: "",
fullPath: null, // 静态html地址 等到传入传输渲染完毕后 执行打印功能
messageBox: null,
htmlData: {},
};
},
async mounted() {
const { BQ_PRINT_NAME } = this.$rootConfig;
switch (this.PrintEq) {
case BQ_PRINT_NAME: // 标签打印机
this.fullPath = path.join(__static, "print/bq_print.html");
break;
}
const webview = this.$refs.printWebview;
webview.addEventListener("ipc-message", async (event) => {
if (event.channel === "webview-print-do") {
webview
.print({
silent: true,
printBackground: true,
deviceName: this.PrintEq,
})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
})
.finally(() => {
// this.messageBox.close();
});
}
});
},
methods: {
print(val) {
this.htmlData = val;
this.getPrintListHandle();
},
// 获取打印机列表
getPrintListHandle() {
// 改用ipc异步方式获取列表,解决打印列数量多的时候导致卡死的问题
ipcRenderer.send("getPrinterList");
ipcRenderer.once("getPrinterList", (event, data) => {
// 过滤可用打印机
this.printList = data.filter((element) => element.status === 0);
// 1.判断是否有打印服务
if (this.printList.length <= 0) {
this.$message({
message: "打印服务异常,请尝试重启电脑",
type: "error",
offset: 100,
});
this.$emit("cancel");
} else {
this.checkPrinter();
}
});
},
// 2.判断打印机状态
checkPrinter() {
// 本地获取打印机
const printerName = this.PrintEq;
this.PrintEq; // 设置默认打印机名称 小票POSXP、杯贴POSBT
const printer = this.printList.find(
(device) => device.name === printerName
);
// 有打印机设备并且状态正常直接打印
if (printer && printer.status === 0) {
this.printDeviceName = printerName;
this.printRender();
} else if (printerName === "") {
this.$message({
message: "请先设置其他打印机",
type: "error",
duration: 1500,
onClose: () => {
// this.dialogVisible = false
},
});
this.$emit("cancel");
} else {
this.$message({
message: "当前打印机不可用,请重新设置",
type: "error",
duration: 1500,
onClose: () => {
// this.dialogVisible = false
},
});
this.$emit("cancel");
}
},
printRender() {
// console.log(this.printDeviceName,'+',this.htmlData)
this.messageBox = this.$message({
message: "打印中,请稍后",
type: "success",
duration: 1500,
offset: 100,
});
// 获取<webview>节点
let webview = this.$refs.printWebview;
// 发送信息到<webview>里的页面
webview.send("webview-print-render", {
printName: this.printDeviceName,
htmlData: this.htmlData,
});
},
},
};
</script>
<style scoped>
.container {
position: absolute;
left: 0;
top: 7rem;
width: 180px;
height: 300px;
z-index: -1;
display: none;
}
webview {
width: 170px !important;
height: calc(100vh - 1rem) !important;
top: 0 !important;
right: 0;
bottom: 0;
left: 0;
position: relative !important;
visibility: visible !important;
}
</style>
发起打印请求页面,传入参数
4.外部触发打印组件的打印函数 传入需要打印的数据
<printer
ref="pos_bq_print"
:print-eq="BQ_PRINT_NAME"
:html-data="BQHtmlData"
@complete="clearBqHtmlData"
></printer>
goPrint() {
this.BQHtmlData = {
//xxxx
};
// 调用print组件的打印函数
this.$refs.pos_bq_print.print(this.BQHtmlData); // 打印杯贴
}
5.打印组件 执行打印函数 获取列表 判断打印机状态 执行打印 获取打印参数 发起webview.send 传输数据
6.静态文件通过ipcRenderer.on("webview-print-render", xxx )接收打印组件发起的数据传输
7.静态文件通过传入的参数 渲染出正确的html页面 通过webview-print-do 发出渲染完成的消息
静态html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<script src="../js/vue.js"></script>
<title>标签</title>
<style type="text/css">
@page {
margin: 0;
}
body {
width: 260px;
height: 130px;
}
* {
padding: 0;
margin: 0;
font-size: 12px;
font-family: PingFang SC;
color: #000000;
}
#app {
width: 260px;
height: 130px;
padding: 3px 10px;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
h3 {
text-align: center;
margin-bottom: 10px;
}
.bq-print {
display: flex;
justify-content: space-between;
}
.desc-box {
letter-spacing: 1px;
width: 55%;
margin-right: 0px;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.desc {
word-break: break-all;
display: flex;
justify-content: flex-start;
width: 100%;
flex-wrap: wrap;
margin-bottom: 4px;
}
.desc-date {
text-align: center;
}
.qrcode-box {
width: calc(100% - 55% - 20px);
}
.qrcode img {
width: 80%;
height: 80%;
box-sizing: border-box;
padding: 1px;
}
</style>
</head>
<body>
<div id="app">
<div class="bq-print">
<div class="desc-box">
<div class='desc'>产品: {{product.prodName }}</div>
<div class='desc'>xxxx</div>
<div class='desc'>xxxx</div>
<div class='desc'>xxxx</div>
</div>
<div class="qrcode-box">
<div class="qrcode" id='qrCodeUrl' ref="qrCodeUrl"></div>
<div class="desc-date">{{ product.date }}</div>
</div>
</div>
</body>
<script>
//引入ipcRenderer对象
const { ipcRenderer } = require("electron");
//生成二维码
let QRCode = require("qrcodejs2");
new Vue({
el: "#app",
data: {
product: {},
},
mounted() {
// 监听外部发送进来的数据 执行渲染
ipcRenderer.on("webview-print-render", (event, info) => {
this.product = info.htmlData;
if (info.htmlData.QRCODE) {
this.creatQrCode()
const date = info.htmlData.QRCODE.split('~')[2]
this.product.date = this.formatDate(Number(date), 2)
}
// 执行渲染
ipcRenderer.sendToHost("webview-print-do");
});
},
methods: {
// 创建二维码
creatQrCode(url) {
this.$refs.qrCodeUrl.innerHTML = ''
// this.product.QRCODE_INNERHTML;
let qrcode = new QRCode(this.$refs.qrCodeUrl, {
text: this.product.QRCODE, // 需要转换为二维码的内容
width: 90,
height: 90,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.L,
});
},
},
});
</script>
</html>
8.打印组件通过监听webview.addEventListener("ipc-message" ,xxx)执行
webview.print 打印成功
10.完成自动更新
自动更新监控文件
import { autoUpdater } from "electron-updater";
import { ipcMain } from "electron";
import { app } from "electron";
let mainWindow = null;
export function updateHandle(window, feedUrl) {
mainWindow = window;
let message = {
error: "检查更新出错",
checking: "正在检查更新……",
updateAva: "检测到新版本,正在下载……",
updateNotAva: "现在使用的就是最新版本,不用更新",
};
//设置更新包的地址
autoUpdater.setFeedURL(feedUrl);
//监听升级失败事件
autoUpdater.on("error", function (error) {
sendUpdateMessage({
cmd: "error",
message: error,
});
});
//监听开始检测更新事件
autoUpdater.on("checking-for-update", function (message) {
sendUpdateMessage({
cmd: "checking-for-update",
message: message,
});
});
//监听发现可用更新事件
autoUpdater.on("update-available", function (message) {
sendUpdateMessage({
cmd: "update-available",
message: message,
});
});
//监听没有可用更新事件
autoUpdater.on("update-not-available", function (message) {
sendUpdateMessage({
cmd: "update-not-available",
message: message,
});
});
// 更新下载进度事件
autoUpdater.on("download-progress", function (progressObj) {
sendUpdateMessage({
cmd: "download-progress",
message: progressObj,
});
});
//监听下载完成事件
autoUpdater.on(
"update-downloaded",
function (event, releaseNotes, releaseName, releaseDate, updateUrl) {
sendUpdateMessage({
cmd: "update-downloaded",
message: {
releaseNotes,
releaseName,
releaseDate,
updateUrl,
},
});
//退出并安装更新包
autoUpdater.quitAndInstall();
}
);
// 接收渲染进程消息,开始检查更新
ipcMain.on("checkForUpdate", (e, arg) => {
//执行自动更新检查
// sendUpdateMessage({cmd:'checkForUpdate',message:arg})
autoUpdater.checkForUpdates();
});
}
//给渲染进程发送消息
function sendUpdateMessage(text) {
mainWindow.webContents.send("message", text);
}
mian.js进程 引入文件
import { updateHandle } from "../renderer/utils/update.js";
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
frame: config_res.IS_DEBUG, // 是否有边框窗口
fullscreen: !config_res.IS_DEBUG, // 全屏
webPreferences: {
// preload: path.join(__dirname, "preload.js"),
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
webviewTag: true,
webSecurity: false,
},
});
/**
* 检测版本更新
* 设置版本更新地址,即将打包后的latest.yml文件和exe文件同时放在
* http://xxxx/test/version/对应的服务器目录下,该地址和package.json的publish中的url保持一致
*/
let feedUrl = config_res.UPDATE_URL; // 更新地址
updateHandle(mainWindow, feedUrl);
// 监听崩溃
}
以下两个文件为主要资源包 均需要放在在线更新地址里,检测到更新后才会拉去更新
yml文件是主要更新监控是否有更新 一定要替换该文件在在线更新地址里,不然检测不到更新
11.防止开启多个应用
已经开启后 再次双击打开会打开当前的窗口至于首层
// 防止开启多个应用
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on("second-instance", (event, commandLine, workingDirectory) => {
//输入从第二个实例中接收到的数据
//有人试图运行第二个实例,我们应该关注我们的窗口
if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.focus();
}
});
//创建myWindow,加载应用的其余部分,etc...