「课件」原创 => Electron·Vue【转载请标明出处】
Zero·前言
npm install -g cnpm --registry=https://registry.npmmirror.com
0.0 Electron 组成
- Chromium 允许使用 前端web技术
- Node.js 强大的 生态,成就了 js 可以实现更多功能
- Native apis(原生 API)这个其实很关键 ~,一个合格的 窗体程序,是一定要能够与 当下处在的操作系统做交互的~
0.1 Electron 核心流程
- 渲染进程(render process) & 主进程(main process)
主进程创建窗体 => 窗体加载界面(调用 render process)
渲染进程一些操作 => 调用主进程里面的代码(IPC 通信机制)
主进程 => 可能会调用 native apis => 渲染进程(更新渲染)(IPC 通信机制)
一个 electron 程序,只有一个主进程(main process)并且只有它可以进行 GUI 的一系列 API 操作。
一个 electron 程序,可以有多个渲染进程
0.2 环境搭建
- 首先你得安装 node.js
mkdir my-electron-test
cd my-electron-test
npm init
npm install electron
npm install nodemon
{
"name": "my-electron-test",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"scripts": {
"start": "nodemon --exec electron . --watch ./ --ext .js,.html,.css,.vue",
"test": "echo \"Error: no test specified\" && exit 1",
"build": "electron ."
},
"author": "MQy",
"license": "MIT",
"dependencies": {
"electron": "^33.2.1",
"nodemon": "^3.1.9"
}
}
- 其次新建 main.js 与 index.html

const { app, BrowserWindow } = require("electron");
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
});
win.loadFile("index.html"); // 将 index.html 这个页面加载到 窗体里面
};
app.whenReady().then(() => {
// 当 程序准备就绪 那么就创建这么一个窗口
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
<h1>你好</h1>
</body>
</html>
npm run start

第一章·Electron 基础
1.0 优缺点
- 缺点
内存资源占用高:Chromium 的占用没办法避免
启动时间较长:它必须先启动一个完整的浏览器引擎和应用进程
性能较差:毕竟使用的是 Web 技术,相比于系统原生技术 在性能上会显得较差
应用体积大:每个 electron 应用必须包含 Chromium 和 Node.js 的副本
资源消耗与原生应用重复:因为每个 electron 应用都会独立运行 Chromium 引擎
缺乏原生体验:Electron 应用通常很难完全模仿系统原生的 UI 和交互行为,这在用户对一致性要求较高的系统(如 macOS)上尤为明显。
对硬件要求较高:低配置设备几乎 都会卡顿。。
- 优点
跨平台支持:因为本质上是浏览器,所以很简单的就支持了跨平台~
易上手:本身前端技术栈就很好学,然后该框架也就只使用 前端技术,所以易上手。
开发效率高:前端在画交互界面方面,有着天然的优势,酷炫和复杂的成分也完全可以依靠切图或引入模型等操作实现。
生态强大:背靠 Node.js,生态不可能不强大 ~
支持自动更新:提供了内置的自动更新功能,使得开发者无需 自行开发自动更新功能。
广泛的应用案例:Visual Studio Code、腾讯 QQ、飞书、网易云音乐 ...
1.1 生命周期事件
const { app, BrowserWindow } = require("electron");
let mainWindow;
app.on("ready", () => { // app 初始化完成
console.log("App is ready")
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});
mainWindow.loadFile('index.html') // 挂载前端页面
mainWindow.on("ready-to-show", () => { // 全部 dom 加载完毕,通常用于显示窗口(避免空白页面)。
mainWindow.show(); // 显示窗口
});
mainWindow.on("focus", () => { // 窗口获得焦点时触发。
})
mainWindow.on("blur", () => { // 窗口失去焦点时触发。
})
mainWindow.on("close", () => { // 窗口关闭时 触发
// event.preventDefault() // 可以阻止窗口关闭,哈哈
})
mainWindow.on("closed", () => { // 窗口关闭之后触发
// 通常用于清理窗口对象引用,防止内存泄漏。
console.log('Window is closed');
mainWindow = null; // 清理引用
})
})
app.on("before-quit", () => { // 在应用退出前触发,可以在这里执行一些清理任务。
console.log("退出之前");
})
app.on("will-quit", () => { // 应用退出前触发,可以用于释放资源(如关闭后台任务、数据库连接等)。
console.log("马上要退出了,实际上也是退出前,只是在 before 之后触发")
})
app.on('quit', () => { // 应用完全退出后触发,生命周期的最后一个事件。
console.log("退出时触发个事件")
})
// macOS 特殊处理
app.on('window-all-closed', () => { // 所有窗口都关闭之后(该事件一旦监听,那么窗口就不会自行退出)
if (process.platform !== 'darwin') { // macOS 不退出
app.quit(); // 必须手动退出才行
}
});
app.on('activate', () => { // macOS 专用事件,当应用被激活(如点击 Dock 图标)时触发,通常用来重新创建窗口(如果没有窗口存在)。
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
1.1 Hello Electron
mkdir hello-electron
cd hello-electron
npm init
cnpm i electron
cnpm i nodemon
cnpm i electron-win-state


- package.json
{
"dependencies": {
"electron": "^34.0.0",
"electron-win-state": "^1.1.22",
"nodemon": "^3.1.9"
},
"name": "hello-electron",
"version": "1.0.0",
"main": "main.js",
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --exec electron . --watch ./ --ext .js,.html,.css,.vue"
},
"author": "MQy",
"license": "MIT",
"description": "Hello electron"
}
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'self'; data:; script-src 'self'; style-src 'self';
'unsafe-inline';"/>
<title>Document</title>
<script src="./renderer/app.js"></script>
</head>
<body>
Hello Electrion!
</body>
</html>
- main.js
const { app, BrowserWindow } = require("electron");
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
x: 100,
y: 100,
minHeight: 600,
minWidth: 800,
resizable: true, // 可调整大小
movable: true, // 可移动
minimizable: true, // 可最小化
maximizable: true, // 可最大化
closable: true, // 可关闭
focusable: true, // 可聚焦
fullscreenable: true, // 可全屏
// kiosk: true, // 无边框的全屏模式
title: "Hello Electrion", // 窗口标题
icon: "icon.png", // 窗口图标
frame: true, // 显示窗口边框和menubar
transparent: false, // 窗口背景透明
alwaysOnTop: false, // 窗口置顶
skipTaskbar: false, // 任务栏中不显示窗口
autoHideMenuBar: false, // 自动隐藏菜单栏
titleBarStyle: "hidden", // 隐藏边框
opacity: 1, // 窗口透明度
webPreferences: {
nodeIntegration: true, // 集成 node.js
contextIsolation: false, // 暂时不隔离 node.js 环境
devTools: false, // 默认打开开发者工具
},
});
win.loadFile("index.html");
// 窗口关闭之前的事件监听
win.on("closed", (event) => {
event.preventDefault(); // 阻止关闭窗口
console.log("Window is about to close.");
});
// 窗口已关闭之后
// 一般用来清理资源
win.on("closed", () => {
console.log("Window has been closed.");
});
// 窗口获得焦点
win.on("focus", () => {
console.log("Window focused.");
});
// 窗口失去焦点
win.on("blur", () => {
console.log("Window blurred.");
});
};
// 当监听该事件时,正常的关闭事件就会失效
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit(); // 所以要在这里 加上 app.quit()
}
});
// 执行一些清理工作或确认退出
app.on("before-quit", (event) => {
console.log("App is about to quit.");
});
// 记录日志或保存状态。
app.on("quit", () => {
console.log("Application quit.");
});
// before-quit => quit => window-all-closed 执行顺序
// 准备好的时候
app.on("ready", () => {
const mainWindow = createWindow();
});
// 如果是 Mac 系统,则在 dock 图标被点击后激活应用窗口
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow(); // 所以只要创建 window 就行
}
});
- app.js
const fs = require('fs');
fs.writeFile('./demo.txt', 'Hello Electrion!', (err) => {
console.log(err);
});

1.3 preload.js
webPreferences: {
nodeIntegration: true, // 集成 node.js
contextIsolation: true, // 暂时不隔离 node.js 环境
// devTools: false, // 默认打开开发者工具
preload: path.resolve(__dirname, "preload.js"), // 预加载脚本
},
官方不推荐不隔离 node.js 环境,而是建议 新建一个 preload.js 来进行 node.js 的操作。
当开启隔离的时候,preload.js 就是唯一加载进来且不被隔离的 js 文件!!!
- preload.js
const fs = require('fs');
fs.writeFile('./demo.txt', 'Hello preload!', (err) => {
console.log(err);
})
const {contextBridge} = require('electron');
// 这里暴露给主世界的 electronAPI 对象会直接绑定在 window 对象上
contextBridge.exposeInMainWorld('electronApi', {
version: process.versions.electron,
})
- app.js
// const fs = require('fs');
// fs.writeFile('./demo.txt', 'Hello Electrion!', (err) => {
// console.log(err);
// });
alert(electronApi.version)

1.4 IPC 通信
有点儿类似于 vue 的 $emit 与 $on
所以,这个地方就直接上代码了,然后 大家去测试,观察其效果即可(这篇文章本来也是给拥有 vue 基础的人拿来快速入门 electron 的)
- main.js
const { app, BrowserWindow, ipcMain, dialog } = require("electron");
const path = require("path");
let mainWindow = null;
const createWindow = () => {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
x: 100,
y: 100,
minHeight: 600,
minWidth: 800,
resizable: true, // 可调整大小
movable: true, // 可移动
minimizable: true, // 可最小化
maximizable: true, // 可最大化
closable: true, // 可关闭
focusable: true, // 可聚焦
fullscreenable: true, // 可全屏
// kiosk: true, // 无边框的全屏模式
show: false,
title: "Hello Electrion", // 窗口标题
icon: "icon.png", // 窗口图标
frame: true, // 显示窗口边框和menubar
transparent: false, // 窗口背景透明
alwaysOnTop: false, // 窗口置顶
skipTaskbar: false, // 任务栏中不显示窗口
autoHideMenuBar: false, // 自动隐藏菜单栏
opacity: 1, // 窗口透明度
webPreferences: {
nodeIntegration: true, // 集成 node.js
contextIsolation: true, // 暂时不隔离 node.js 环境
// devTools: false, // 默认打开开发者工具
preload: path.resolve(__dirname, "preload.js"), // 预加载脚本
},
});
mainWindow.loadFile("index.html");
// 窗口关闭之前的事件监听
mainWindow.on("closed", (event) => {
event.preventDefault(); // 阻止关闭窗口
console.log("Window is about to close.");
});
// 窗口已关闭之后
// 一般用来清理资源
mainWindow.on("closed", () => {
console.log("Window has been closed.");
});
// 窗口获得焦点
mainWindow.on("focus", () => {
console.log("Window focused.");
});
// 窗口失去焦点
mainWindow.on("blur", () => {
console.log("Window blurred.");
});
mainWindow.once('ready-to-show', () => {
mainWindow.show();
// 主进程 向 渲染进程发送信息
mainWindow.webContents.send('mainSend', "我已经展示了")
})
};
// 当监听该事件时,正常的关闭事件就会失效
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit(); // 所以要在这里 加上 app.quit()
}
});
// 执行一些清理工作或确认退出
app.on("before-quit", (event) => {
console.log("App is about to quit.");
});
// 记录日志或保存状态。
app.on("quit", () => {
console.log("Application quit.");
});
// before-quit => quit => window-all-closed 执行顺序
// 准备好的时候
app.on("ready", () => {
const mainWindow = createWindow();
});
// 如果是 Mac 系统,则在 dock 图标被点击后激活应用窗口
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow(); // 所以只要创建 window 就行
}
});
// 主进程接收渲染进程的事件
ipcMain.on("set-title", (event,title) => {
console.log("set-title", title);
mainWindow.setTitle(title);
});
// handle 触发,是可以接受异步 传递信息 给 渲染进程的,并且 这里的返回值会直接返回给 渲染进程
ipcMain.handle("open-file-dialog", async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ["openFile"],
filters: [
{ name: "Images", extensions: ["jpg", "png", "gif"] },
{ name: "Movies", extensions: ["mkv", "avi", "mp4"] },
{ name: "All Files", extensions: ["*"] },
],
});
return result.canceled ? null : result.filePaths[0];
});
// ipcMain.handle('open-file-dialog', async () => {
// const {canceld, filePaths} = await diaglog.showOpenDialog({
// properties: ['openFile', 'openDirectory']
// });
// if(!canceld) {
// return filePaths[0];
// }
// });
- preload.js
const fs = require('fs');
fs.writeFile('./demo.txt', 'Hello preload!', (err) => {
console.log(err);
})
const {contextBridge, ipcRenderer} = require('electron');
// 这里暴露给主世界的 electronAPI 对象会直接绑定在 window 对象上
contextBridge.exposeInMainWorld('electronApi', {
version: process.versions.electron,
setTitle: (title) => {
// send 与 on 一般都是做单纯的处理,或者是返回同步的数据。
ipcRenderer.send('set-title', title); // 发送给主进程
},
// 激活 主进程里面的 handle 事件
// 该事件的返回值会 直接 返回给 调用 electronApi.openFile() 的页面.js
openFile: async () => await ipcRenderer.invoke("open-file-dialog"),
})
// 接收主进程发送过来的信息
ipcRenderer.on('mainSend', (event, message) => {
alert(message);
})
- app.js
// const fs = require('fs');
// fs.writeFile('./demo.txt', 'Hello Electrion!', (err) => {
// console.log(err);
// });
alert(electronApi.version);
window.addEventListener("DOMContentLoaded", () => {
let setBtn = document.getElementById("setBtn");
setBtn.addEventListener("click", (title) => {
electronApi.setTitle("我了个骚羽", title);
});
document.getElementById("openFile").addEventListener("click", async () => {
const filePath = await electronApi.openFile();
if (filePath) {
console.log("Selected file:", filePath);
} else {
console.log("No file selected.");
}
});
});
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline';"
/>
<title>Document</title>
<script src="./renderer/app.js"></script>
</head>
<body>
Hello Electron!
<button id="setBtn">设置标题</button>
<button id="openFile">打开文件</button>
</body>
</html>

1.5 奇淫技巧
父子窗口
let newWindow = new BrowserWindow({
width: 600,
height: 800,
webPreferences: {
nodeIntegration: true
},
parent: mainWindow, // 父窗口是 mainWindow,即当 mainWindow 移动的时候 会跟着移动
modal: true // 弹窗形态,即该窗口会直接 作为父窗口的一个弹窗出现
})
newWindow.loadFile("./list.html")
newWindow.on('closed', () => {
mainWindow = null;
})
保存窗口状态(MAC 系统非常需要)
npm i electron-win-state
- main.js
const { app, BrowserWindow, ipcMain, dialog } = require("electron");
const windowStateKeeper = require("electron-win-state").default;
const path = require("path");
let mainWindow = null;
const winState = new windowStateKeeper({
width: 800,
height: 600,
x: 100,
y: 100,
minHeight: 600,
minWidth: 800,
resizable: true, // 可调整大小
movable: true, // 可移动
minimizable: true, // 可最小化
maximizable: true, // 可最大化
closable: true, // 可关闭
focusable: true, // 可聚焦
fullscreenable: true, // 可全屏
// kiosk: true, // 无边框的全屏模式
show: false,
title: "Hello Electrion", // 窗口标题
icon: "icon.png", // 窗口图标
frame: true, // 显示窗口边框和menubar
transparent: false, // 窗口背景透明
alwaysOnTop: false, // 窗口置顶
skipTaskbar: false, // 任务栏中不显示窗口
autoHideMenuBar: false, // 自动隐藏菜单栏
opacity: 1, // 窗口透明度
webPreferences: {
nodeIntegration: true, // 集成 node.js
contextIsolation: true, // 暂时不隔离 node.js 环境
// devTools: false, // 默认打开开发者工具
preload: path.resolve(__dirname, "preload.js"), // 预加载脚本
},
});
const createWindow = () => {
mainWindow = new BrowserWindow({
...winState.state,
});
mainWindow.loadFile("index.html");
// 窗口关闭之前的事件监听
mainWindow.on("close", (event) => {
// event.preventDefault(); // 阻止关闭窗口
console.log("Window is about to close.");
});
// 窗口已关闭之后
// 一般用来清理资源
mainWindow.on("closed", () => {
mainWindow = null;
console.log("Window has been closed.");
});
// 窗口获得焦点
mainWindow.on("focus", () => {
console.log("Window focused.");
});
// 窗口失去焦点
mainWindow.on("blur", () => {
console.log("Window blurred.");
});
mainWindow.once("ready-to-show", () => {
mainWindow.show();
mainWindow.webContents.send("mainSend", "我已经展示了");
});
// let newWindow = new BrowserWindow({
// width: 600,
// height: 800,
// webPreferences: {
// nodeIntegration: true
// },
// parent: mainWindow, // 父窗口是 mainWindow,即当 mainWindow 移动的时候 会跟着移动
// modal: true // 弹窗形态,即该窗口会直接 作为父窗口的一个弹窗出现
// })
// newWindow.loadFile("./list.html")
// newWindow.on('closed', () => {
// mainWindow = null;
// })
winState.manage(mainWindow)
};
// 当监听该事件时,正常的关闭事件就会失效
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit(); // 所以要在这里 加上 app.quit()
}
});
// 执行一些清理工作或确认退出
app.on("before-quit", (event) => {
console.log("App is about to quit.");
});
// 记录日志或保存状态。
app.on("quit", () => {
console.log("Application quit.");
});
// before-quit => quit => window-all-closed 执行顺序
// 准备好的时候
app.on("ready", () => {
const mainWindow = createWindow();
});
// 如果是 Mac 系统,则在 dock 图标被点击后激活应用窗口
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow(); // 所以只要创建 window 就行
}
});
ipcMain.on("set-title", (event, title) => {
console.log("set-title", title);
mainWindow.setTitle(title);
});
// Handle file dialog
ipcMain.handle("open-file-dialog", async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ["openFile"],
filters: [
{ name: "Images", extensions: ["jpg", "png", "gif"] },
{ name: "Movies", extensions: ["mkv", "avi", "mp4"] },
{ name: "All Files", extensions: ["*"] },
],
});
return result.canceled ? null : result.filePaths[0];
});
// ipcMain.handle('open-file-dialog', async () => {
// const {canceld, filePaths} = await diaglog.showOpenDialog({
// properties: ['openFile', 'openDirectory']
// });
// if(!canceld) {
// return filePaths[0];
// }
// });
- 如果 win 也想 像 mac 系统那样。。如关~ 哈哈,可以像下面这样写
mainWindow.on('close', (e) => {
if (process.platform === 'win32') {
e.preventDefault();
mainWindow.hide(); // 或者最小化 mainWindow.minimize();
}
});
webContents
const ws = mainWindow.webContents;
ws.openDevTools() // 打开控制台的方法
ws.on("did-finish-load", () => {
console.log("所有资源都加载完毕,包括图片资源")
}})
ws.on("dom-ready", () => {
console.log("只有 dom 元素加载完毕,图片之类的可能未加载完毕~")
})
// 右键菜单的时候触发 下方事件
ws.on("context-menu", (e, params) => {
console.log(e, params)
})
// 向页面中 注入 js
ws.on("context-menu", (e, params) => {
wc.executeJavaScript(`alert('${params.selectionText}')`)
})
透明窗口且可拖拽
当你想要做出高度自定义风格的 窗口程序时,就必须使用到 透明窗口 且 可拖拽的方案!
- main.js
mainWindow = new BrowserWindow({
transparent: true, // 窗口背景透明
frame: false // 让其窗口的基本外层的架子 直接消失
});
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline';"
/>
<title>Document</title>
<script src="./renderer/app.js"></script>
<style>
body {
-webkit-app-region: drag
}
button, input {
-webkit-app-region: no-drag
}
</style>
</head>
<body>
Hello Electron!
<button id="setBtn">设置标题</button>
<button id="openFile">打开文件</button>
</body>
</html>
1.6 dialog
- 打开文件 dialog
dialog.showOpenDialog(mainWindow,{
buttonLabel: '嘿嘿ok', // 更改 按钮文本
defaultPath: app.getPath(''), // 默认要选择文件的 路径
// multiSelections 是否可以选择多个文件
// createDirectory 选择的时候 可以 新建文件夹
// openFile 可以选择文件
// openDirectory 可以选择文件夹
properties: ['multiSelections', 'createDirectory', 'openFile', 'openDirectory']
}).then((result) => {
console.log(result.filepaths)
})
- 保存文件 dialog
dialog.showSaveDialog(mainWindow,{}).then(result => {
console.log(result.filePath);
})
- 信息框 dialog
const answers = ['Yes', 'No', 'Maybe']
dialog.showMessageBox({
title: 'Message Box',
message: 'Please select an option',
detail: 'Message details.',
buttons: answers
}).then(({response}) => {
console.log(`User selected:${answers[response]}`);
})
1.7 globalShortcut
快捷键触发
globalShortcut.register('G', () => {
console.log('你按了 G');
})
globalShortcut.register('CommandOrControl+Y', () => {
console.log('你按了 G');
})
globalShortcut.register('CommandOrControl+Shift+Z', () => {
console.log('你按了 G');
})
1.8 Menu
顶部 Menu
const {Menu} = require('electron')
const mainMenu = Menu.buildFromTemplate([
{
label: 'electron',
submenu: [
{
label: 'submenu-1',
click: () => {
alert("我是 submenu-1")
},
accelerator: 'Shift+Alt+G' // 快捷键
},
{
label: 'submenu-2',
role: 'toggleFullScreen', // electron 默认支持的行为
}
]
}
])
Menu.setApplicationMenu(mainMenu); // 作用于 这个窗口上
上下文 Menu
右键窗口内容的时候,会弹出的 菜单。
// 实际上也是用 Menu 来做的,只不过 最后使用的方式 不同
let contextMenu = Menu.buildFromTemplate([
{label: 'Item 1'},
{role: 'editMenu'}
])
contextMenu.popup(); // 只要写上了,那么你右键窗口的时候 就能弹出来这个 菜单
1.9 Tray
Tray 顾名思义,托盘~
function createTray() {
tray = new Tray('') // 需要传递一个 托盘图标
tray.setToolTip("Tray details"); // 鼠标悬停时 显示的提示信息
tray.on('click', e => {
if(e.shiftKey) {
app.quit(); // 退出
}else{
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
}
})
// 设置右键菜单
tray.setContextMenu(Menu.buildFromTemplate([
{label: 'item1'},
{label: 'item2'}
]));
}
第二章·Electron + Vue
2.1 创建项目
cnpm create @quick-start/electron@latest electron-vite # 还是比较建议选择 js 作为脚本语言的,不是很推荐使用 ts
npm i
- package.json (安装常见的依赖)
{
"name": "electron-vite",
"version": "1.0.0",
"description": "An Electron application with Vue",
"main": "./out/main/index.js",
"author": "example.com",
"homepage": "https://electron-vite.org",
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win",
"build:mac": "npm run build && electron-builder --mac",
"build:linux": "npm run build && electron-builder --linux"
},
"build": {
"extraResources": [
"./assets/**"
],
"productName": "EasyChat",
"appId": "com.easychat",
"directories": {
"output": "insallPackages"
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "./resources/icon.png",
"uninstallerIcon": "./resources/icon.png",
"installerHeaderIcon": "./resources/icon.png",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "EasyChat"
},
"mac": {
"icon": "icons/icon.icns"
},
"win": {
"artifactName": "${productName}setup.${version}.exe",
"icon": "resources/icon.png",
"target": [
"nsis"
]
}
},
"dependencies": {
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^3.0.0",
"@imengyu/vue3-context-menu": "^1.4.4",
"axios": "^1.7.9",
"dplayer": "^1.27.1",
"electron-store": "^10.0.0",
"element-plus": "^2.9.3",
"express": "^4.21.2",
"fluent-ffmpeg": "^2.1.3",
"fs-extra": "^11.3.0",
"js-md5": "^0.8.3",
"moment": "^2.30.1",
"pinia": "^2.3.0",
"rebuild": "^0.1.2",
"sass": "^1.83.4",
"sass-loader": "^16.0.4",
"sqlite3": "^5.1.7",
"v-viewer": "^3.0.21",
"vue-cookies": "^1.8.5",
"vue-router": "^4.5.0",
"ws": "^8.18.0"
},
"devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2",
"@rushstack/eslint-patch": "^1.10.3",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"electron": "^31.0.2",
"electron-builder": "^24.13.3",
"electron-vite": "^2.3.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.26.0",
"prettier": "^3.3.2",
"vite": "^5.3.1",
"vue": "^3.4.30"
}
}
- electron.vite.config.mjs
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()]
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: {
resolve: {
alias: {
'@': resolve('src/renderer/src')
}
},
plugins: [vue()]
}
})
2.2 Hello Electron·Vue

- main/index.js
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
const login_windowProps = {
width: 300,
height: 370
}
const register_windowProps = {
height: 490
}
let mainWindow = null
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: login_windowProps.width,
height: login_windowProps.height,
show: false,
titleBarStyle: 'hidden',
resizable: false,
frame: true,
transparent: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// IPC test
ipcMain.on('ping', () => console.log('pong'))
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
ipcMain.on('changeLoginHeight', (event) => {
mainWindow.setSize(login_windowProps.width, register_windowProps.height)
})
- preload/index.js
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer
const api = {
changeLoginHeight: () => {
ipcRenderer.send('changeLoginHeight')
}
}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}
- renderer/src/assets/common/css
.drag {
-webkit-app-region: drag;
}
.no-drag {
-webkit-app-region: no-drag;
}
- renderer/src/indewx.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Electron</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
/>
<link rel="stylesheet" href="./src/common/css/common.css" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
- renderer/src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
mode: 'hash',
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: '默认路径',
redirect: '/login'
},
{
path: '/login',
name: '登录',
component: () => import('@/views/Login.vue')
}
]
})
export default router
- renderer/src/views/Login.vue
<template>
<div class="login-box">
<div class="top-box drag">EasyChat</div>
<el-button @click="changeHeight">改变高度</el-button>
</div>
</template>
<script setup>
import { ref, reactive, getCurrentInstance, nextTick } from 'vue'
const { proxy } = getCurrentInstance()
const changeHeight = () => {
window.api.changeLoginHeight()
}
</script>
<style lang="scss" scoped>
.login-box {
width: 100%;
height: 100%;
background-color: #fff;
}
.top-box {
width: 100%;
background-color: red;
}
</style>

这里,大体上给个思路。前端页面与后端请求那部分,完全就是自己玩页面内的 js 脚本。然后 经典把接口定义声明都放到 api 文件夹里面,封装好 自己的 axios 即可。
如果需要 渲染进程与主进程进行通讯,或者要让页面必须渲染在一个新的窗口上时,才会在 main/index.js 与 preload/index.js 中 声明与定义方法!

浙公网安备 33010602011771号