创建项目
1
| pnpm create @quick-start/electron my-app --template vue
|
electron-vite: https://cn.electron-vite.org/guide/
进程
进程分为:主进程和渲染进程在 Electron 中,每个应用有一个主进程和一个或多个渲染进程
主进程:
- 负责运行 package.json 的 main 脚本,并执行应用的整个生命周期
- 控制所有的 Web 页面和与它们的交互
- 只有在主进程中才能调用某些 Electron 的原生 API
- 一个 Electron 应用始终有一个主进程
渲染进程:
- 每个 Electron 的 BrowserWindow 在其自己的渲染进程中运行 Web 页面
- 负责页面的渲染,即页面上的用户界面
- 一个 Electron 应用可以有多个渲染进程
- 与主进程相对独立,但二者之间可以通过 IPC(进程间通讯)进行通信
进程通信
在 Electron 中,渲染进程(即网页)可以通过多种方式与主进程(即 Node.js 环境)进行通信。以下是几种常见的方法:
主进程到渲染进程
webContents.send
描述:主进程向特定的渲染进程发送消息
主进程
1 2 3
| import { BrowserWindow } from 'electron' let win = new BrowserWindow() win.webContents.send('main-to-renderer', { key: 'value' })
|
渲染进程
1 2 3
| electron.ipcRenderer.on('main-to-renderer', (event, data) => { console.log(data) })
|
渲染进程到主进程
ipcRenderer.send
描述:渲染进程向主进程发送消息,不等待响应
渲染进程
1 2 3 4 5 6
| electron.ipcRenderer.send('renderer-to-main', { key: 'value' })
electron.ipcRenderer.on('reply-channel', (event, arg) => { console.log(arg) })
|
主进程
1 2 3 4 5 6 7 8
| import { ipcMain } from 'electron' ipcMain.on('renderer-to-main', (event, data) => { console.log(data) event.reply('reply-channel', 'reply message') })
|
打开外部网页
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script setup lang="ts"> const handleClick = () => { electron.ipcRenderer.send('open', 'open') } </script>
<template> <div class="dashboard-page"> <el-button type="primary" @click="handleClick">open</el-button> </div> </template>
<style lang="scss" scoped></style>
|
1 2 3 4
| ipcMain.on('open', (event, arg) => { mainWindow.loadURL('https://www.baidu.com') })
|
ipcRenderer.invoke
描述:渲染进程向主进程发送消息,并等待响应,返回一个Promise对象,用于需要同步或异步获取主进程处理结果的情况
渲染进程
1 2 3 4 5 6 7 8
| electron.ipcRenderer.invoke('renderer-to-main-invoke', { key: 'value' }) .then(response => { console.log(response) }) .catch(err => { console.error(err) })
|
主进程
1 2 3 4 5
| import { ipcMain } from 'electron' ipcMain.handle('renderer-to-main-invoke', async (event, data) => { console.log(data) return { response: '主进程的响应' } })
|
渲染进程到渲染进程
渲染进程Electron本身不直接支持渲染进程之间的直接通信,但可以通过主进程作为中介来实现
描述:一个渲染进程发送消息到主进程,主进程再将消息转发到另一个渲染进程
渲染进程A
1
| electron.ipcRenderer.send('rendererA-to-main', { key: 'value' })
|
主进程
1 2 3 4 5
| import { ipcMain, BrowserWindow } from 'electron' let winB = new BrowserWindow() ipcMain.on('rendererA-to-main', (event, data) => { winB.webContents.send('main-to-rendererB', data) })
|
渲染进程B
1 2 3 4 5
| import { ipcMain, BrowserWindow } from 'electron' let winB = new BrowserWindow() ipcMain.on('rendererA-to-main', (event, data) => { winB.webContents.send('main-to-rendererB', data) })
|
总结
- 主进程到渲染进程:使用webContents.send。
- 渲染进程到主进程:使用ipcRenderer.send(不等待响应)或ipcRenderer.invoke(等待响应)。
- 渲染进程到渲染进程:通过主进程中转,使用ipcRenderer.send和webContents.send。
退出应用
app.quit
用户操作退出
1 2 3 4 5 6 7 8 9 10 11
| const close = () => { electron.ipcRenderer.send('close') }
import { app, ipcMain } from 'electron'
ipcMain.on('close', () => { app.quit() })
|
自定义菜单退出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { app, Menu } from 'electron' const template = [ { label: 'File', submenu: [ { label: 'Exit', click() { app.quit() } } ] } ]
const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu)
|
app.exit
app.exit 方法,表示强制退出应用
1 2 3 4 5 6
| import { app } from 'electron'
app.exit(0) app.exit(1)
|
关闭主窗口退出应用
1 2 3 4 5
| mainWindow.on('close', () => { ... mainWindow.close() })
|
窗口操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const mainWindow = new BrowserWindow({ width: 900, height: 670, show: false, frame: false, autoHideMenuBar: false, ...(process.platform === 'linux' ? { icon } : {}), webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false } })
|
关闭窗口
如果你只想关闭一个特定的窗口,可以使用 BrowserWindow 提供的 close
1 2 3 4 5 6 7 8
| import { BrowserWindow } from 'electron'
let mainWindow = new BrowserWindow({ width: 800, height: 600 })
mainWindow.close()
|
监听窗口关闭事件
你可以在窗口关闭时执行某项操作,比如保存窗口状态、发送通知等
1 2 3 4 5 6 7 8
| import { app, BrowserWindow } from 'electron'
let mainWindow = new BrowserWindow({ width: 800, height: 600 })
mainWindow.on('close', () => { ... app.quit() })
|
最小化窗口
1 2 3 4 5 6 7 8 9
| electron.ipcRenderer.send('minimize')
import { ipcMain } from 'electron'
ipcMain.on('minimize', (event) => { mainWindow.minimize() })
|
封装
汇总
- 最小化窗口:
mainWindow.minimize()
- 最大化窗口:
mainWindow.maximize()
- 还原窗口:
mainWindow.unmaximize()
- 全屏窗口:
mainWindow.setFullScreen(true)
- 退出全屏:
mainWindow.setFullScreen(false)
- 关闭窗口:
mainWindow.close()
- 隐藏窗口:
mainWindow.hide()
- 显示窗口:
mainWindow.show()
- 窗口置顶:
mainWindow.setAlwaysOnTop(true)
- 取消窗口置顶:
mainWindow.setAlwaysOnTop(false)
- 窗口透明度:
mainWindow.setOpacity(0.5)
- 窗口尺寸:
mainWindow.setSize(800, 600)
- 窗口位置:
mainWindow.setPosition(100, 100)
- 窗口标题:
mainWindow.setTitle('标题')
- 窗口背景色:
mainWindow.setBackgroundColor('#333')
菜单
系统托盘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import { app, Tray, Menu } from 'electron'
app.isQuiting = false
mainWindow.on('close', (event) => { if (!app.isQuiting) { event.preventDefault() mainWindow.hide() } })
const iconPath = path.join(__dirname, '../../resources/icon.png?asset')
const trayIcon = nativeImage.createFromPath(icon) let tray = new Tray(trayIcon)
const contextMenu = Menu.buildFromTemplate([ { label: '显示应用', click: () => { mainWindow.show() } }, { type: 'separator' }, { label: '退出', click: () => { app.quit() } } ])
tray.setContextMenu(contextMenu)
tray.setToolTip('这是我的Electron应用')
tray.on('click', () => { if (mainWindow.isVisible()) { mainWindow.hide() } else { mainWindow.show() } })
app.on('before-quit', () => { app.isQuiting = true if (tray) { tray.destroy() } })
|

自定义菜单栏
设置应用菜单
如果无边框窗口需要设置自定义菜单栏,需要封装组件通过进程通信来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { app, Menu } from 'electron'
const contextMenu = Menu.buildFromTemplate([ { label: '显示应用', click: () => { mainWindow.show() } }, { label: '退出', click: () => { app.quit() } } ])
Menu.setApplicationMenu(menu)
|
右键菜单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { Menu } from 'electron'
const contextMenu = Menu.buildFromTemplate([ { label: 'f12', click: () => { mainWindow.webContents.openDevTools() } }, ])
mainWindow.webContents.on('context-menu', (event) => { contextMenu.popup({ window: mainWindow }) })
|