最近团队把桌面客户端项目的分摊给我,于是着手学研究electron逻辑了。
我的架构
原来的项目,代码管理混乱,要加一点内容可能会影响到其他内容,代码冗余,一个 main.js
写了全部代码,于是我创建了自己的架构。
Main Process模块
Main Process
分为以下模块,查找文本、更新、系统托盘、socket 通信
、小红点、dock
、菜单、快捷键模块,每个模块都是独立模块,通过 config
配置文件就可以直接启用或关闭某个模块。
js
const index = {
/**
* 模式 window/iframe/webview
*/
mode: "webview",
/**
* 是否启用证书认证
*/
certificate: false,
/**
* 是否启用查找文本
*/
findText: true,
/**
* 是否启用更新
*/
updateApp: true,
/**
* 系统托盘
*/
tray: true,
/**
* 系统托盘是否闪烁
*/
trayFlash: true,
/**
* 是否启用 socket 通信
*/
socket: false,
/**
* 应用菜单栏
*/
applicationMenu: true,
/**
* 应用更新路径
*/
updateUrl: '',
/**
* 外部项目地址
*/
url: '',
name: '测试项目',
};
module.exports = index;
Render Process 模块
同时也把 Render Process
改成了 vue3
项目,只不过我这里没有用脚手架,当然你可以使用任何框架。
目录
我的目录结构如下图,目前现这样,可以在细分优化
难点
更新
由于应用没有签名,所以在 windows
上使用 electron-updater
自动更新是没有问题的,但是 mac
就无法更新,所以 mac
需要用户手动安装项目。 我们的更新流程是这样的,
- 在主进程调用更新的方法
- 使用
electron
的net
模块去请求updateUrl
链接 - 判断远程版本是否大于本地版本,小于等于则什么都不做,大于则进入步骤 4
- 弹出检测到新版提示窗口
- 用户点击立即更新后
- 判断是 windows/mac,是 mac 走步骤 7, 是windows 走步骤 11
- 选择下载位置
- 进行下载,弹出下载进度窗口
- 下载完成后,点击立即安装
- 自动弹出安装界面
- 触发 electron-updater 相关方法
- 弹出下载进度窗口
- 下载完成后,点击立即安装就立即安装,否则下载启动是自动安装
主进程和 webview 通信
如果只是 iframe,那么直接用 postMessage
就行了,但是当你的 Render Process
中使用了 web-view
标签时,主进程想和webview
这个外部项目通信时,使用了网上的各种办法都不行,都无法直接获取到 electron
里的方法,后来就直接使用 socket
实现双方通信。于是封装了 socket 相关代码,最后使用者只需要在 electron/socket/api.js
文件中写相关代码就行,如何连接的就不用关心了。
js
const sockets = {
prdListMounted(socket, data) {
socket.emit('welcome', `欢迎${socket.id}连接1`)
},
exportExcel(socket, data) {
console.log('点击了导出商品');
},
};
module.exports = sockets;
我外部项目使用的是 vue2
,所以需要使用 npm
安装两个包 vue-socket.io
和 socket.io-client
。
创建 socket/index.js
js
import Vue from 'vue';
import VueSocketIO from 'vue-socket.io';
import socketIO from 'socket.io-client';
import store from '@/store';
const PORT = 31596;
const URL = '127.0.0.1';
const isElectron = navigator.userAgent.includes('Electron');
// 只有是 Electron 的时候才能进行 socket 通信
if (isElectron) {
let Socket = new VueSocketIO({
debug: true, // true开启
connection: socketIO(`ws://${URL}:${PORT}`), // 里面写socket服务器地址
// 使用vuex
vuex: {
store,
// 定义vuex函数的时候,用来区分普通函数还是socket函数。
actionPrefix: 'SOCKET_',
mutationPrefix: 'SOCKET_',
},
options: {
autoConnect: false, // 关闭自动连接,一般是在用户登录过后才进行手动连接
},
});
Vue.use(Socket);
}
创建 socket/emit.js
,这个文件用来做个兼容
js
import Vue from 'vue';
const defaultData = {
emit() {},
};
let socket = Vue.prototype.$socket || defaultData;
const emit = (name, data) => {
socket.emit(name, data);
};
export default emit;
创建 socket/api.js
js
import emit from './emit';
// 在 prdList 页面 Mounted 的时候调用这个方法
export function prdListMounted(data = {}) {
emit('prdListMounted', data);
}
// 点击 导出表格按钮 调用这个方法
export function exportExcel(data = {}) {
emit('exportExcel', data);
}
prdList页面 demo
html
<template>
<div>
<el-button @click="click">export</el-button>
</div>
</template>
<script>
import { prdListMounted, exportExcel } from '@/utils/socket/api';
export default {
mounted() {
prdListMounted('我 mounted')
},
// 此处为接收 electron 主进程数据的地方
sockets: {
welcome(data) {
console.log('welcome data', data);
},
},
methods: {
click() {
exportExcel({
a: 1,
b: 2,
})
}
},
};
</script>