一、基本概念
文档搬就完事了,建议全文背诵。啊不,是通读。
流程模型 | Electron (electronjs.org)
二、创建Vue+Electron项目
1. 创建vue项目
vue create 项目名称
注: 这边vue create完可以通过 vue add electron-builder 直接创建electron项目,完成后直接跳到步骤4就行了。
2. 运行
yarn serve
3. 确保项目成功运行后安装electron-builder
ctrl+c后选择Y停止项目
vue add electron-builder // 安装依赖
安装electron-builder的过程中会选择安装electron,选择合适的版本等待安装完成。
安装完成后可以发现项目结构中多了background.js文件,这是electron项目的入口文件,也是主进程文件。
同时package.json中的脚本命令也增加了。
4. 运行vue+electron项目
yarn electron:serve
5. 可能遇到的问题:Failed to fetch extension
我们只需要将app的ready事件中保留窗口创建就好了:
app.on('ready', async () => {
createWindow()
})
至此,一个vue+electron项目就搭建好了。
三、自定义标题栏
1. 标题栏样式
#background.js
const win = new BrowserWindow({
width: 800,
height: 600,
frame: false, // 是否有边框(标题栏)
})
#App.vue
<template>
<div id="nav">
<div class="title-bar">标题栏</div>
<div class="page-content">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
<router-view />
</div>
</div>
</template>
<style lang="less">
body { margin: 0;}
.title-bar {
background-color: rgb(115, 118, 146);
height: 32px;
line-height: 32px;
text-align: left;
padding: 0 20px;
font-size: 14px;
color: #fff;
}
.page-content {
height: calc(100vh - 32px);
overflow: auto;
}
</style>
效果图如下:
2. 设置标题栏拖动窗口
自定义标题栏是不能拖动的,当前只识别为页面主体的一部分,我们需要另外对它做些配置。
.title-bar {
... -webkit-app-region: drag; // 设为可拖动
}
此时我们的自定义标题栏就可以像原生标题栏一样拖动了。
3. 可能遇到的问题:无法点击窗口操作按钮
对于这类自定义窗口操作按钮来说,将标题栏设为可拖动会导致点击事件触发不了,此时应该为这些按钮配置为不可拖动 -webkit-app-region: no-drag。
4. 可能遇到的问题:标题栏无法resize窗口
此时的自定义标题栏是可以拖动了,但是把鼠标放在窗口边缘,会发现我们不能通过手动来调整窗口大小了,但是非标题栏部分都能resize。
解决方法:将标题栏除了下边以外,都缩小1-3px。
<style lang="less">
body {
margin: 0;
}
.title-bar {
width: calc(100vw - 4px);
margin: 1px 2px 0px;
box-sizing: border-box;
background-color: rgb(115, 118, 146);
height: 32px;
line-height: 32px;
text-align: left;
padding: 0 20px;
font-size: 14px;
color: #fff;
-webkit-app-region: drag;
}
.page-content {
height: calc(100vh - 33px);
overflow: auto;
}
</style>
此时标题栏效果如下:
多余的白边视情况选择多给title套一层或者页面背景设为title同色,页面内容颜色另外设置。
四、标题栏增加窗口操作
1. 导入remote模块
#App.vue
import { remote } from 'electron'; // 导入remote模块
remote模块是个什么玩意?
remote模块就是为了让我们能够在渲染进程使用主进程才能使用的api。
Electron将api分为Main Process(主进程)和Renderer Process(渲染进程)以及两个Process都能使用的模块。而渲染进程能使用的api少之又少,并且不能直接操作窗口。
如果我们能够在渲染进程中就获取当前窗口的引用,那么我们就可以对窗口进行操作了。
2. 获取当前窗口实例
#App.vue
const CURRENT_WINDOW = remote.getCurrentWindow();
3. 通过实例操作窗口
#App.vue
const operateWindow = (operate: any) => {
let isSticky = ref(false);
switch (operate) {
case 'close':
if (CURRENT_WINDOW.isClosable() && !CURRENT_WINDOW.isDestroyed()) {
CURRENT_WINDOW.hide();
CURRENT_WINDOW.close();
CURRENT_WINDOW.destroy();
}
break;
case 'minimize':
CURRENT_WINDOW.minimize();
break;
case 'sticky':
isSticky.value = !CURRENT_WINDOW.isAlwaysOnTop();
let mode = ['screen-saver', 'normal'];
CURRENT_WINDOW.setAlwaysOnTop(
isSticky.value,
(<any>mode)[+isSticky.value]
);
break;
}
};
五、窗口阴影
#background.js
const win = new BrowserWindow({
width: 800,
height: 600,
hasShadow: true,
})
1. 可能出现的问题:设置阴影无效
一般情况下设置hasShodow:true后窗口阴影就显示出来了,但有的脚手架生成的background.js文件会将窗口的transparent属性设置为true,将窗口透明,在这种情况下的阴影是看不出效果的。
六、主进程、渲染进程之间通信
1. 渲染进程向主进程
① 通过ipcRenderer.send和ipcMain.on
渲染进程发送:
ipcRenderer.send('getMsg', `渲染进程发送的数据: 123`);主进程接收:
ipcMain.on('getMsg', (e, args) => {
console.log(`---getMsg ${args}`);
}); // ---getMsg 渲染进程发送的数据: 123
② 通过ipcRenderer.invoke和ipcMain.handle进行双向通信
渲染进程发送:
ipcRenderer.invoke('getMsgWithCallback', '---msg from render process').then(res => {
console.log(res); // ---msg from main process
});
主进程:
ipcMain.handle('getMsgWithCallback', (e, args) => {
console.log(`---getMsgWithCallback ${args}`); // ---getMsgWithCallback ---msg from render process
return '---msg from main process'; // 主进程return的数据会作为invoke函数的回调参数传递给渲染进程
})
2. 主进程向渲染进程
① 通过return
渲染进程:
ipcMain.send('msg', 'massage');
主进程:
ipcMain.handle('getMsgWithCallback', (e, args) => {
console.log(`---getMsgWithCallback ${args}`); // ---getMsgWithCallback ---msg from render process
return '---msg from main process'; // 主进程return的数据会作为invoke函数的回调参数传递给渲染进程
})
② 通过ipcMainEvent对象的sender属性
渲染进程:
ipcRenderer.send('getMsgWithCallback2', '---msg from render process'); // 发送消息ipcRenderer.on('replyByMainProcess', (e, args) => {
// 接收主进程返回的数据
console.log(args) // '---msg from main process'
});
主进程:
ipcMain.handle('getMsgWithCallback2', (e, args) => {
console.log(args); // '---msg from render process'
e.sender.send('replyByMainProcess', '---msg from main process');
})
3. 双向通信
实际上1.1、2.1和2.2都是双向通信的一种方式。
4. 窗口间通信
网上很多文章会通过在要发送数据的渲染进程中调用ipcRenerder.send,在需要接受数据的渲染进程调用remote.ipcMain的方式来进行窗口间的通信,然而在渲染进程中调用remote是不好的,特别是在通信频繁的情况下,比如websocket接受数据并分发给多窗口时,甚至会引起卡顿,我们可以通过ipcRenderer.sendTo的方式进行窗口间通信。
发送端:
ipcRenderer.sendTo(webContentId, eventName, args);
接受端:
ipcRenderer.on(eventName, (e, args) => { ... });
七、loadURL/loadFile
electron创建窗口时,使用loadURL或者loadFile加载文件。
loadURL可以加载本地文件也可以加载远程文件,而loadFile只能加载本地文件,一般做vue项目的时候都是加载本地的index.html。
八、那些年我们一起踩过的坑
1. 由于大部分的Vue项目都是单页应用,因此对于多窗口的electron项目来说,每次loadURL加载的都是打包好的index.html文件,这就导致了App.vue中干的活在每次创建新窗口时都会做一遍!!!