electron-vue 入门
基于 vue (基本上是它听起来的样子) 来构造 electron 应用程序的样板代码。
Electron简介
Electron是干什么的? 简单来讲,Electron
使用 JavaScript
,HTML
和 CSS
,来构建跨平台的桌面应用程序。
按照官方的说法:如果你可以建一个网站,你就可以建一个桌面应用程序。
和传统的桌面应用相比,使用Electron
开发更容易上手,开发效率更高。并且,web技术应用广泛、生态繁荣,Electron
可以使用几乎所有的Web生态领域及Node.js生态领域的组件和技术方案。
与网页应用相比,Electron
基于Chromium
和 Node.js
,可以避免令人头痛的浏览器兼容问题。而Web前端受限访问的文件系统、系统托盘、系统通知等,开发Electron应用时可以自由地使用。
简单理解Electron的工作机制
使用Electron开发的桌面应用,类似于简易版的、定制版的Chrome浏览器,当然这个浏览器中的页面不能通过输入网址打开,而是由开发者写好的。
和浏览器架构类似,Electron应用程序区分主进程和渲染进程。
- 主进程: 负责控制应用程序的生命周期、创建和管理应用程序窗口,有着多种控制原生桌面功能的模块,例如:菜单、对话框以及托盘图标等。
- 渲染进程:负责完成渲染界面、接收用户输入、响应用户的交互等工作。
一个electon应用只有一个主进程,但可以有多个渲染进程
项目搭建 & 打包
项目搭建比较简单,直接使用 electron-vue 的官方模板就可以生成项目,需要安装 vue-cli 命令行工具
npm install -g vue-cli // 需要安装 vue-cli 脚手架
vue init simulatedgreg/electron-vue project-name // 使用 electron-vue 官方模板生成项目
npm install // 安装依赖
npm run dev // 启动项目
项目打包也比较简单,普通打包执行 npm run build 即可,如果要打包成免安装文件,执行 npm run build:dir,非常方便!
项目打包可能会遇到很多坑,后面有机会再讲解
npm run build // 打包成可执行文件
npm run build:dir // 打包成免安装文件
项目结构
简单的基础调试
- 窗口页面的调试方法和chrome浏览器类似。点击菜单栏的View --- Toggle Developer Tools,或者按它对应的快捷键,就会出现我们熟悉的开发者工具界面。
无边框窗口
要创建无边框窗口,只需在 BrowserWindow 的 options 中将 frame 设置为 false:
mainWindow = new BrowserWindow({
frame: false,
...
})
标题栏和菜单栏消失了,但也会有几个问题:
- 默认情况下,无边框窗口是不可拖拽的。应用程序需要在 CSS 中指定
-webkit-app-region: drag
来告诉 Electron 哪些区域是可拖拽的。
html,body {
height: 100%;
width: 100%;
}
body{
-webkit-app-region: drag;
}
如果用上面的属性使整个窗口都可拖拽,则必须将其中的按钮标记为不可拖拽,否则按钮将无法点击.
.enable-click {
-webkit-app-region: no-drag;
}
- 自定义最大化,最小化,关闭按钮
当我们在运行客户端时,electron称其为渲染进程,electron客户端启动则称为主进程。
最小化、最大化和关闭方法我们需要在主进程调用,而当我们实际触发最小化等等事件时是在渲染进程中的。因此我们需要将渲染进程,也就是实际运行的页面中最小化等等相关事件触发传递给主进程,然后在主进程中做事件监听,当收到渲染进程传来的方法时,调用最小化、最大化、关闭等方法来达到与原生导航栏一直的效果。
IPC通信
这就需要用到IPC通信了。
IPC(Inter-Process Communication),就是进程间通信。Electron应用程序区分主进程和渲染进程,有时候,两者之间需要通信,传输一些数据、发送一些消息。
渲染进程 TO 主进程
比如,点击关闭按钮,就需要渲染进程向主进程发送隐藏主窗口的请求。
渲染进程使用Electron内置的ipcRenderer
模块向主进程发送消息,ipcRenderer.send
方法的第一个参数是消息管道名称。
<div class="fd-header-right">
<i class="fa fa-icon-min" @click="setWin('min')"></i>
<i class="fa" :class="isMax ? 'fa-icon-default' : 'fa-icon-max'"
@click="setWin('max')"></i>
<i class="el-icon-close" @click="setWin('close')"></i>
</div>
<script>
const {ipcRenderer} = require("electron");
export default {
name: 'ccHeader',
data() {
return {
// 是否最大化
isMax: false
}
},
methods: {
setWin(type){
ipcRenderer.send(type);
}
}
}
</script>
主进程通过ipcMain接收消息,ipcMain.on方法的第一个参数也为消息管道的名称,与ipcRenderer.send的名称对应,第二个参数是接收到消息的回调函数。
import { app, BrowserWindow, Menu, ipcMain } from 'electron'
//入口文件index.js
ipcMain.on('min', () => mainWindow.minimize());
ipcMain.on('max', () => {
if (mainWindow.isMaximized()) {
mainWindow.restore();
} else {
mainWindow.maximize()
}
});
ipcMain.on('close', () => mainWindow.close());
主进程 TO 渲染进程
主进程向渲染进程发送消息是通过渲染进程的webContents。在mainWindow渲染进程设定了任务后,会传输给主进程任务信息,当任务时间到了,主进程会创建提醒窗口remindWindow,并通过remindWindow.webContents将任务名称发给remindWindow。
function createRemindWindow (task) {
remindWindow = new BrowserWindow({
//options
})
remindWindow.loadURL(`file://${__dirname}/src/remind.html`)
//主进程发送消息给渲染进程
remindWindow.webContents.send('setTask', task)
}
在remindWindow渲染进程中,通过ipcRenderer.on接受消息。
ipcRenderer.on('setTask', (event,task) => {
document.querySelector('.reminder').innerHTML =
`<span>${decodeURIComponent(task)}</span>的时间到啦!`
})
渲染进程 TO 渲染进程
渲染进程之间传递消息,可以通过主进程中转,即窗口A先把消息发送给主进程,主进程再把这个消息发送给窗口B,这种非常常见。
- 当点击自定义的窗口关闭按钮,我们并不希望退出程序,只是将窗口隐藏,可以通过系统托盘再次打开窗口。
系统托盘
程序启动时,将应用程序加入系统托盘。在Electron中,借助Tray
(系统托盘)模块实现。
const { app, BrowserWindow, Tray, Menu } = electron
const iconPath = path.join(__dirname, './src/img/icon.png')
let mainWindow, tray
app.on('ready', () => {
mainWindow = new BrowserWindow({
//... options
})
mainWindow.loadURL(`file://${__dirname}/src/main.html`)
tray = new Tray(iconPath) //实例化一个tray对象,构造函数的唯一参数是需要在托盘中显示的图标url
tray.setToolTip('Tasky') //鼠标移到托盘中应用程序的图标上时,显示的文本
tray.on('click', () => { //点击图标的响应事件,这里是切换主窗口的显示和隐藏
if(mainWindow.isVisible()){
mainWindow.hide()
}else{
mainWindow.show()
}
})
tray.on('right-click', () => { //右键点击图标时,出现的菜单,通过Menu.buildFromTemplate定制,这里只包含退出程序的选项。
const menuConfig = Menu.buildFromTemplate([
{
label: '退出',
click: () => app.quit()
}
])
tray.popUpContextMenu(menuConfig)
})
})
窗口位置
当任务时间到,提醒窗口会在屏幕右下角出现。怎样设定窗口位置呢?
function openCalendarWindow() {
// 先判断是否已经打开过一个了
if (calendarWin) {
calendarWin.close();
}
calendarWin = new BrowserWindow({
width: 400,
height: 550,
parent: mainWindow, // win是主窗口
frame: true, // 是否显示顶部工具栏
webPreferences: {
nodeIntegration: true
}
})
//获取屏幕尺寸
const size = screen.getPrimaryDisplay().workAreaSize
//获取托盘位置的y坐标(windows在右下角,Mac在右上角)
const { y } = tray.getBounds()
//获取窗口的宽高
const { height, width } = calendarWin.getBounds()
//计算窗口的y坐标
const yPosition = process.platform === 'darwin' ? y : y - height
//setBounds设置窗口的位置
calendarWin.setBounds({
x: size.width - width, //x坐标为屏幕宽度 - 窗口宽度
y: yPosition,
height,
width
})
//当有多个应用时,提醒窗口始终处于最上层
calendarWin.setAlwaysOnTop(true)
// calendarWin.webContents.closeDevTools();
// 取消菜单栏
Menu.setApplicationMenu(null);
calendarWin.loadURL(winURL + '#/task');
calendarWin.on('closed', () => { calendarWin = null })
}
关闭窗口
提醒窗口会在一段时间后关闭,calendarWin.close()来关闭窗口。
当窗口关闭后,我们可以设置calendarWin = null来回收分配给该渲染进程的资源。
calendarWin.on('closed', () => { calendarWin = null })
常见功能使用
import { app, BrowserWindow, Menu, ipcMain } from 'electron'
// src/main/index.js
function createWindow () {
/**
* Initial window options
*/
mainWindow = new BrowserWindow({
height: 563,
useContentSize: true,
// resizable: false, // 是否允许用户改变窗口大小
width: 1000,
// fullscreen: true, // 全屏显示
frame: false, // 是否显示顶部工具栏
// MAC
titleBarStyle: 'hidden',
// webPreferences:{
// backgroundThrottling: false, //设置应用在后台正常运行
// nodeIntegration:true, //设置能在页面使用nodejs的API
// contextIsolation: false
// }
})
mainWindow.loadURL(winURL);
// 打开f12 ctr+shift+i
// 关闭开发者工具
mainWindow.webContents.closeDevTools();
// 取消菜单栏
Menu.setApplicationMenu(null);
mainWindow.on('closed', () => {
mainWindow = null
})
}