1. Vue + Electron 开发环境搭建
1.1 技术选型分析
Vue和Electron的结合为开发者提供了一种高效构建跨平台桌面应用的方式。这种组合的优势在于:
- Vue的响应式数据绑定和组件化开发模式,使得前端界面开发更加高效
- Electron的跨平台特性,让一套代码可以运行在Windows、macOS和Linux系统上
- 现代前端工具链(如Vite)的加持,大幅提升了开发体验和构建速度
在实际项目中,我们通常会面临两种主流搭建方案的选择:
- 传统方案:Vue CLI + electron-builder
- 现代方案:Vite + electron-builder
对于新项目,我强烈推荐使用Vite方案,因为它具有更快的冷启动速度(通常比Vue CLI快10倍以上)和更简洁的配置。特别是在大型项目中,HMR(热模块替换)的速度差异会非常明显。
1.2 使用Vite创建项目(推荐方案)
以下是使用Vite创建Electron+Vue项目的详细步骤:
bash复制# 使用官方推荐的electron-vite模板
npm create @quick-start/electron my-electron-app
# 交互式命令行会询问以下配置:
# 1. 选择框架:Vue
# 2. 选择语言:TypeScript/JavaScript(根据团队习惯选择)
# 3. 是否安装其他依赖(如Pinia、Router等)
# 进入项目目录
cd my-electron-app
# 安装依赖
npm install
# 启动开发服务器
npm run dev
这个模板已经配置好了:
- 主进程和渲染进程的热重载
- 完善的TypeScript支持(如果选择TS)
- 预配置的electron-builder打包脚本
- 合理的项目结构划分
1.3 项目结构解析
一个标准的Vite+Electron+Vue项目结构如下:
code复制my-electron-app/
├── src/
│ ├── main/ # 主进程代码
│ │ ├── index.ts # 主进程入口文件
│ │ └── ... # 其他主进程模块
│ ├── renderer/ # 渲染进程代码
│ │ ├── src/ # Vue应用代码
│ │ │ ├── App.vue
│ │ │ ├── main.ts
│ │ │ └── ...
│ │ └── index.html # 渲染进程入口
├── electron.vite.config.ts # Electron+Vite配置
├── vite.config.ts # 渲染进程Vite配置
└── package.json
这种结构清晰地区分了主进程和渲染进程的代码,避免了传统electron-vue项目中常见的混淆问题。
提示:在团队协作中,建议在README.md中详细说明这种目录结构的约定,特别是对于不熟悉Electron双进程模型的开发者。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心开发概念与实践
2.1 主进程与渲染进程通信
Electron的核心概念之一就是主进程和渲染进程的分离。理解它们的区别和通信方式是开发的关键:
主进程(Main Process)
- 使用Node.js API
- 管理应用生命周期
- 创建和管理浏览器窗口
- 访问系统底层API
渲染进程(Renderer Process)
- 运行在浏览器环境中
- 使用Vue构建UI
- 通过预加载脚本安全地访问Node.js功能
进程间通信(IPC)示例
主进程中设置IPC监听:
typescript复制// src/main/index.ts
import { ipcMain } from 'electron'
ipcMain.handle('get-system-info', () => {
return {
platform: process.platform,
arch: process.arch,
memory: process.getSystemMemoryInfo()
}
})
渲染进程中使用IPC:
vue复制<!-- src/renderer/src/components/SystemInfo.vue -->
<script setup>
import { ref, onMounted } from 'vue'
const systemInfo = ref(null)
onMounted(async () => {
systemInfo.value = await window.electron.ipcRenderer.invoke('get-system-info')
})
</script>
<template>
<div>
<p>平台: {{ systemInfo?.platform }}</p>
<p>架构: {{ systemInfo?.arch }}</p>
</div>
</template>
2.2 安全最佳实践
Electron应用的安全问题不容忽视,以下是几个关键点:
-
上下文隔离(Context Isolation)
务必启用contextIsolation,防止渲染进程直接访问Node.js API:typescript复制new BrowserWindow({ webPreferences: { contextIsolation: true, preload: path.join(__dirname, '../preload/index.js') } }) -
使用预加载脚本
预加载脚本是在渲染进程加载前运行的脚本,可以安全地暴露特定API:javascript复制// src/main/preload/index.js const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electron', { ipcRenderer: { invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args), on: (channel, listener) => { ipcRenderer.on(channel, listener) return () => ipcRenderer.removeListener(channel, listener) } } }) -
禁用Node.js集成
在生产环境中,除非必要,否则应该禁用渲染进程的Node.js集成:typescript复制webPreferences: { nodeIntegration: false }
3. 实用功能实现
3.1 系统托盘实现
系统托盘是桌面应用的常见功能,以下是完整实现:
typescript复制// src/main/tray.ts
import { Tray, Menu, nativeImage } from 'electron'
import path from 'path'
export function createTray(mainWindow: BrowserWindow) {
const iconPath = path.join(__dirname, '../../resources/tray.png')
const trayIcon = nativeImage.createFromPath(iconPath).resize({ width: 16, height: 16 })
const tray = new Tray(trayIcon)
const contextMenu = Menu.buildFromTemplate([
{
label: '打开主窗口',
click: () => mainWindow.show()
},
{
label: '退出',
click: () => app.quit()
}
])
tray.setToolTip('我的Electron应用')
tray.setContextMenu(contextMenu)
// 双击托盘图标显示/隐藏窗口
tray.on('double-click', () => {
if (mainWindow.isVisible()) {
mainWindow.hide()
} else {
mainWindow.show()
}
})
return tray
}
在main进程中初始化:
typescript复制// src/main/index.ts
import { createTray } from './tray'
app.whenReady().then(() => {
const mainWindow = createWindow()
createTray(mainWindow)
})
3.2 原生菜单定制
Electron允许完全自定义应用菜单:
typescript复制// src/main/menu.ts
import { Menu, BrowserWindow } from 'electron'
export function setupMenu(mainWindow: BrowserWindow) {
const template = [
{
label: '文件',
submenu: [
{
label: '打开文件',
accelerator: 'CmdOrCtrl+O',
click: () => {
mainWindow.webContents.send('menu-open-file')
}
},
{ type: 'separator' },
{
label: '退出',
role: 'quit'
}
]
},
{
label: '编辑',
submenu: [
{ role:
