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双进程模型的开发者。
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: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' }
]
},
{
label: '视图',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
4. 打包与发布
4.1 使用electron-builder配置
electron-builder是目前最流行的Electron打包工具,以下是推荐配置:
javascript复制// electron.vite.config.ts
import { defineConfig } from 'electron-vite'
import { resolve } from 'path'
export default defineConfig({
main: {
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/main/index.ts')
}
}
}
},
renderer: {
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/renderer/index.html')
}
}
}
}
})
package.json中添加打包脚本:
json复制{
"scripts": {
"build": "vite build && electron-builder",
"build:mac": "vite build && electron-builder --mac",
"build:win": "vite build && electron-builder --win",
"build:linux": "vite build && electron-builder --linux"
},
"build": {
"appId": "com.example.myapp",
"productName": "My Electron App",
"copyright": "Copyright © 2023",
"mac": {
"category": "public.app-category.developer-tools",
"target": ["dmg", "zip"]
},
"win": {
"target": ["nsis", "zip"]
},
"linux": {
"target": ["AppImage", "deb"]
},
"files": [
"dist/**/*",
"src/main/**/*"
],
"extraResources": [
{
"from": "resources/",
"to": "resources/"
}
]
}
}
4.2 优化打包体积
Electron应用常因体积过大被诟病,以下是优化策略:
-
排除不必要的依赖
在package.json中明确指定依赖类型:json复制{ "dependencies": { // 必须打包的运行时依赖 }, "devDependencies": { // 仅开发需要的依赖 } } -
使用asar归档
electron-builder默认使用asar归档,可以进一步配置:json复制{ "build": { "asar": true, "asarUnpack": [ "**/*.node", "**/resources/*" ] } } -
按平台打包
避免在一个包中包含所有平台的二进制文件:bash复制# 单独为每个平台打包 npm run build:mac npm run build:win npm run build:linux
5. 调试与性能优化
5.1 调试技巧
-
主进程调试
在启动命令中添加--inspect参数:json复制{ "scripts": { "dev": "electron --inspect=9229 ." } }然后可以在Chrome中访问chrome://inspect进行调试。
-
渲染进程调试
直接使用Chrome开发者工具(Ctrl+Shift+I或Cmd+Opt+I)。 -
进程间通信调试
添加IPC通信日志:typescript复制// 在主进程中添加 ipcMain.on('*', (event, ...args) => { console.log(`IPC Main received: ${event.toString()}`, args) }) // 在预加载脚本中添加 ipcRenderer.on('*', (event, ...args) => { console.log(`IPC Renderer received: ${event.toString()}`, args) })
5.2 性能优化策略
-
启用浏览器加速
在创建窗口时启用硬件加速:typescript复制new BrowserWindow({ webPreferences: { enablePreferredSizeMode: true, scrollBounce: true } }) -
懒加载资源
在Vue中使用动态导入:javascript复制const HeavyComponent = defineAsyncComponent(() => import('./components/HeavyComponent.vue') ) -
使用Web Workers
将CPU密集型任务移到Worker中:typescript复制// 主进程中 const worker = new Worker(path.join(__dirname, 'worker.js')) worker.postMessage({ task: 'heavy-computation' }) worker.on('message', (result) => { mainWindow.webContents.send('computation-result', result) })
6. 常见问题解决方案
6.1 白屏问题排查
-
检查加载路径
确保加载的HTML文件路径正确:typescript复制mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')) -
检查开发者工具
如果开发工具能打开但页面白屏,可能是:- Vue应用挂载失败
- 资源加载路径错误
- JavaScript执行错误
-
启用详细日志
在创建窗口时添加事件监听:typescript复制mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { console.error('Failed to load:', errorDescription) })
6.2 打包后功能异常
-
资源路径问题
生产环境中路径与开发环境不同,应该使用:typescript复制const appPath = app.getAppPath() const userDataPath = app.getPath('userData') -
缺失依赖
确保所有依赖都正确声明,特别是:- 二进制依赖(如.node文件)
- 外部资源(如图片、数据库文件)
-
权限问题
在Linux/macOS上,确保打包后的应用有正确权限:bash复制chmod +x /path/to/your/app
7. 项目架构建议
7.1 大型项目结构
对于复杂Electron应用,推荐采用如下结构:
code复制src/
├── common/ # 共享代码
├── main/ # 主进程
│ ├── core/ # 核心模块
│ ├── modules/ # 功能模块
│ ├── utils/ # 工具函数
│ └── index.ts # 主入口
├── renderer/ # 渲染进程
│ ├── assets/ # 静态资源
│ ├── components/ # Vue组件
│ ├── composables/ # Vue组合式函数
│ ├── router/ # 路由配置
│ ├── stores/ # 状态管理
│ ├── styles/ # 全局样式
│ ├── views/ # 页面组件
│ └── main.ts # 渲染进程入口
├── preload/ # 预加载脚本
│ ├── modules/ # 按功能组织的暴露API
│ └── index.ts # 预加载入口
└── resources/ # 应用资源
├── icons/ # 应用图标
├── locales/ # 国际化文件
└── ... # 其他资源
7.2 状态管理方案
对于复杂状态管理,推荐组合使用:
-
Pinia
用于渲染进程中的UI状态管理:typescript复制// src/renderer/stores/app.ts import { defineStore } from 'pinia' export const useAppStore = defineStore('app', { state: () => ({ theme: 'light', preferences: {} }), actions: { async loadPreferences() { this.preferences = await window.electron.ipcRenderer.invoke('load-preferences') } } }) -
主进程状态管理
对于主进程状态,可以使用简单的类实现:typescript复制// src/main/core/appState.ts class AppState { private _config: Record<string, any> constructor() { this._config = {} } get config() { return this._config } async loadConfig() { const configPath = path.join(app.getPath('userData'), 'config.json') this._config = await fs.promises.readFile(configPath, 'utf-8') .then(JSON.parse) .catch(() => ({})) } } export const appState = new AppState()
8. 进阶功能实现
8.1 自动更新实现
Electron应用的自动更新是提升用户体验的关键:
typescript复制// src/main/updater.ts
import { autoUpdater } from 'electron-updater'
import { ipcMain } from 'electron'
export function setupAutoUpdate(mainWindow: BrowserWindow) {
autoUpdater.autoDownload = false
autoUpdater.allowPrerelease = false
autoUpdater.on('update-available', (info) => {
mainWindow.webContents.send('update-available', info)
})
autoUpdater.on('update-downloaded', (info) => {
mainWindow.webContents.send('update-downloaded', info)
})
ipcMain.handle('check-for-updates', async () => {
return await autoUpdater.checkForUpdates()
})
ipcMain.handle('download-update', async () => {
return await autoUpdater.downloadUpdate()
})
ipcMain.handle('quit-and-install', () => {
autoUpdater.quitAndInstall()
})
}
在渲染进程中提供UI交互:
vue复制<!-- src/renderer/src/components/UpdateNotifier.vue -->
<script setup>
import { ref } from 'vue'
const updateInfo = ref(null)
const updateStatus = ref('idle')
const checkUpdate = async () => {
updateStatus.value = 'checking'
try {
updateInfo.value = await window.electron.ipcRenderer.invoke('check-for-updates')
updateStatus.value = updateInfo.value ? 'available' : 'up-to-date'
} catch (error) {
updateStatus.value = 'error'
}
}
const downloadUpdate = async () => {
updateStatus.value = 'downloading'
await window.electron.ipcRenderer.invoke('download-update')
}
const installUpdate = () => {
window.electron.ipcRenderer.invoke('quit-and-install')
}
</script>
8.2 原生模块集成
有时需要集成原生Node.js模块或C++插件:
-
编译原生模块
确保系统已安装构建工具:bash复制# macOS xcode-select --install # Windows npm install --global windows-build-tools # Linux sudo apt-get install build-essential -
使用node-gyp
在package.json中配置:json复制{ "scripts": { "rebuild": "electron-rebuild -f -w your-native-module" }, "dependencies": { "your-native-module": "^1.0.0" } }运行:
bash复制
npm run rebuild -
在Electron中使用
通过预加载脚本暴露安全接口:javascript复制// preload/index.js const { contextBridge } = require('electron') const nativeModule = require('your-native-module') contextBridge.exposeInMainWorld('native', { doSomething: (arg) => nativeModule.doSomething(arg) })
9. 测试策略
9.1 单元测试配置
-
渲染进程测试
使用Vitest测试Vue组件:bash复制
npm install -D vitest @vue/test-utils jsdom配置vitest.config.ts:
typescript复制import { defineConfig } from 'vitest/config' import Vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [Vue()], test: { environment: 'jsdom', globals: true, setupFiles: ['./test/setup.ts'] } }) -
主进程测试
使用mocha/chai测试Node.js代码:bash复制
npm install -D mocha chai @types/mocha @types/chai在package.json中添加:
json复制{ "scripts": { "test:main": "mocha --require ts-node/register src/main/**/*.spec.ts" } }
9.2 E2E测试方案
使用Spectron或Electron官方测试工具:
bash复制npm install -D @electron-forge/test-runner
配置测试脚本:
typescript复制// test/e2e/app.spec.ts
import { test, expect } from '@playwright/test'
import { _electron as electron } from 'playwright'
test('basic test', async () => {
const app = await electron.launch({ args: ['.'] })
const window = await app.firstWindow()
await expect(window).toHaveTitle('My Electron App')
await app.close()
})
在package.json中添加:
json复制{
"scripts": {
"test:e2e": "playwright test"
}
}
10. 项目实战建议
10.1 第一个项目:Markdown编辑器
建议从以下功能开始实现:
- 基本的文件读写操作
- Markdown实时预览
- 简单的工具栏
- 主题切换功能
- 导出PDF/HTML功能
关键技术点:
- 使用marked.js解析Markdown
- 实现文件保存/打开对话框
- 使用highlight.js实现代码高亮
- 添加基本的编辑功能(粗体、斜体等)
10.2 进阶项目:数据库管理工具
更复杂的项目可以尝试:
- 连接多种数据库(MySQL, PostgreSQL, SQLite)
- 可视化表数据
- SQL查询编辑器
- 数据导出/导入
- 数据库结构可视化
关键技术点:
- 使用knex.js或sequelize作为ORM
- 实现多窗口管理(查询窗口、结果窗口)
- 添加查询历史功能
- 实现连接配置管理
10.3 性能敏感项目:视频处理工具
对于性能要求高的应用:
- 使用Web Workers处理视频转码
- 利用GPU加速(通过FFmpeg)
- 实现进度通知
- 批量处理队列
- 系统资源监控
关键技术点:
- 使用fluent-ffmpeg处理视频
- 通过IPC通信传递进度
- 实现任务队列系统
- 监控CPU/内存使用情况
11. 持续学习资源
11.1 官方文档精读
-
Electron安全指南
深入理解Electron的安全模型和最佳实践 -
进程间通信文档
掌握各种IPC通信方式的适用场景 -
性能优化文档
学习Electron特有的性能优化技巧
11.2 开源项目学习
-
VS Code
微软的开源代码编辑器,Electron应用的典范 -
Hyper
现代化的终端应用,优秀的UI实现 -
Notion桌面版
复杂的商业级Electron应用
11.3 社区资源
-
Electron官方博客
获取最新版本特性和最佳实践 -
Awesome Electron
收集了大量Electron资源和工具 -
Electron Fiddle
官方提供的实验工具,快速尝试Electron API
12. 开发工作流优化
12.1 代码规范与质量
-
ESLint配置
统一代码风格:bash复制
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-vue.eslintrc.js配置:
javascript复制module.exports = { root: true, env: { node: true, browser: true, es2021: true }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:vue/vue3-recommended' ], rules: { // 自定义规则 } } -
Git Hook
使用Husky添加提交前检查:bash复制
npm install -D husky lint-staged npx husky install在package.json中添加:
json复制{ "lint-staged": { "*.{js,ts,vue}": "eslint --fix", "*.{css,scss}": "stylelint --fix" } }添加pre-commit钩子:
bash复制npx husky add .husky/pre-commit "npx lint-staged"
12.2 团队协作建议
-
文档规范
- 使用TypeScript接口定义IPC通信协议
- 为复杂功能添加流程图和架构图
- 维护CHANGELOG.md记录重要变更
-
代码审查重点
- IPC通信安全性检查
- 资源路径处理是否正确
- 生产环境安全检查
- 跨平台兼容性
-
分支策略
- main分支:生产环境代码
- develop分支:集成测试环境
- feature/xxx分支:功能开发
- release/xxx分支:版本发布
13. 跨平台开发注意事项
13.1 平台差异处理
-
路径处理
始终使用path模块处理路径:typescript复制import path from 'path' const configPath = path.join(app.getPath('userData'), 'config.json') -
快捷键差异
考虑不同平台的快捷键习惯:typescript复制const isMac = process.platform === 'darwin' const template = [ { label: '文件', submenu: [ { label: '打开', accelerator: isMac ? 'Cmd+O' : 'Ctrl+O' } ] } ] -
UI适配
- macOS:注意标题栏风格
- Windows:注意窗口边框
- Linux:注意系统主题
13.2 平台特定功能
-
Windows任务栏
实现进度条和缩略图工具栏:typescript复制// 设置进度条 mainWindow.setProgressBar(0.5) // 添加缩略图工具栏 mainWindow.setThumbarButtons([ { tooltip: '播放', icon: path.join(__dirname, 'assets/play.png'), click: () => mainWindow.webContents.send('play') } ]) -
macOS Dock菜单
自定义Dock菜单:typescript复制if (process.platform === 'darwin') { app.dock.setMenu(Menu.buildFromTemplate([ { label: '新建窗口', click: createWindow } ])) } -
Linux应用指示器
在Linux上使用AppIndicator:typescript复制if (process.platform === 'linux') { const { appIndicator } = require('electron-installer-common') const indicator = appIndicator.create('my-app', { icon: path.join(__dirname, 'assets/icon.png') }) }
14. 错误监控与日志
14.1 错误收集方案
-
主进程错误捕获
使用process.on捕获未处理异常:typescript复制process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error) // 发送错误报告 }) -
渲染进程错误捕获
通过window.onerror捕获:typescript复制// 预加载脚本中 window.addEventListener('error', (event) => { ipcRenderer.send('renderer-error', { message: event.message, stack: event.error?.stack }) }) -
使用Sentry
集成Sentry进行错误监控:bash复制
npm install @sentry/electron初始化:
typescript复制import * as Sentry from '@sentry/electron' Sentry.init({ dsn: 'YOUR_DSN_HERE', release: app.getVersion() })
14.2 日志系统实现
-
日志分级
实现不同级别的日志:typescript复制enum LogLevel { DEBUG = 'DEBUG', INFO = 'INFO', WARN = 'WARN', ERROR = 'ERROR' } function log(level: LogLevel, message: string, meta?: any) { const logEntry = { timestamp: new Date().toISOString(), level, message, meta } // 写入文件或发送到服务器 } -
日志轮转
使用winston实现日志轮转:bash复制
npm install winston winston-daily-rotate-file配置:
typescript复制import winston from 'winston' import 'winston-daily-rotate-file' const transport = new winston.transports.DailyRotateFile({ filename: 'application-%DATE%.log', datePattern: 'YYYY-MM-DD', maxSize: '20m', maxFiles: '14d' }) const logger = winston.createLogger({ transports: [transport] }) -
日志查看界面
在应用中添加日志查看器:vue复制<!-- LogViewer.vue --> <template> <div class="log-viewer"> <div v-for="(log, index) in logs" :key="index" :class="log.level"> [{{ log.timestamp }}] {{ log.message }} </div> </div> </template> <script setup> import { ref } from 'vue' import { watch } from 'fs' const logs = ref([]) // 监视日志文件变化 watch(logFile, (newLogs) => { logs.value = [...logs.value, ...newLogs].slice(-100) }) </script>
15. 用户数据管理
15.1 数据存储方案
-
简单配置存储
使用electron-store:bash复制
npm install electron-store使用示例:
typescript复制import Store from 'electron-store' const store = new Store() // 保存 store.set('user.preferences', { theme: 'dark' }) // 读取 const preferences = store.get('user.preferences') -
结构化数据
使用lowdb或SQLite:bash复制
npm install lowdb示例:
typescript复制import { Low, JSONFile } from 'lowdb' import path from 'path' const file = path.join(app.getPath('userData'), 'db.json') const adapter = new JSONFile(file) const db = new Low(adapter) await db.read() db.data ||= { posts: [] } db.data.posts.push({ id: 1, title: 'First post' }) await db.write() -
大型数据
考虑使用IndexedDB或LevelDB:bash复制
npm install level示例:
typescript复制import level from 'level' import path from 'path' const dbPath = path.join(app.getPath('userData'), 'bigdata') const db = level(dbPath) await db.put('key', 'value') const value = await db.get('key')
15.2 数据迁移策略
-
版本化数据
为数据添加版本号:typescript复制interface AppData { version: string // 其他字段 } -
迁移脚本
实现数据升级逻辑:typescript复制function migrateData(oldData: any): AppData { if (!oldData.version) { // v1到v2的迁移 return { version: '2.0', preferences: oldData.settings } } return oldData } -
备份机制
重要操作前自动备份:typescript复制async function backupData() { const backupDir = path.join(app.getPath('userData'), 'backups') await fs.promises.mkdir(backupDir, { recursive: true }) const backupFile = path.join(backupDir, `backup-${Date.now()}.json`) await fs.promises.copyFile(dataFile, backupFile) }
16. 无障碍访问支持
16.1 基础无障碍实现
-
键盘导航
确保所有功能可通过键盘访问:vue复制<template> <button @click="save" @keydown.enter="save" tabindex="0"> 保存 </button> </template> -
ARIA属性
为复杂组件添加ARIA属性:vue复制<div role="tablist"> <button role="tab" aria-selected="true" aria-controls="panel-1" > 标签1 </button> </div> -
高对比度模式
检测系统高对比度设置:typescript复制// 主进程中 import { systemPreferences } from 'electron' const isHighContrast = systemPreferences.isHighContrastColorScheme()
16.2 屏幕阅读器支持
-
实时提示
为动态内容添加实时提示:vue复制<template> <div aria-live="polite"> {{ statusMessage }} </div> </template> -
焦点管理
合理管理焦点顺序:vue复制<script setup> import { nextTick } from 'vue' const focusFirstElement = () => { nextTick(() => { document.getElementById('first-input')?.focus() }) } </script> -
测试工具
使用以下工具测试无障碍性:- axe DevTools
- WAVE Evaluation Tool
- VoiceOver (macOS)
- NVDA (Windows)
17. 多窗口应用架构
17.1 窗口管理策略
-
中央窗口管理
实现窗口管理器:typescript复制// src/main/windowManager.ts class WindowManager { private windows = new Map<string, BrowserWindow>() createWindow(id: string, options: Electron.BrowserWindowConstructorOptions) { if (this.windows.has(id)) { this.windows.get(id)?.focus() return } const win = new BrowserWindow(options) this.windows.set(id, win) win.on('closed', () => { this.windows.delete(id) }) } } export const windowManager = new WindowManager() -
窗口间通信
通过主进程中转消息:typescript复制// 主进程中 ipcMain.handle('send-to-window', (event, { windowId, channel, data }) => { const win = windowManager.getWindow(windowId) win?.webContents.send(channel, data) })
17.2 共享状态管理
- 主进程状态共享