Skip to content

Electron 开发指南

📋 目录

🎯 简介

Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用程序的框架。它结合了 Chromium 和 Node.js,让开发者可以使用 Web 技术构建桌面应用。

1.1 核心优势

  • 跨平台兼容性:一套代码可运行在 Windows、macOS 和 Linux 上
  • Web 技术栈:使用熟悉的 HTML、CSS 和 JavaScript 开发
  • 原生功能访问:通过 Electron API 访问系统原生功能
  • 活跃的社区:丰富的插件和生态系统
  • 强大的性能:基于 Chromium,提供现代化的 Web 体验

1.2 应用场景

  • 桌面工具:开发跨平台的实用工具
  • 企业应用:构建内部管理系统
  • 媒体播放器:开发视频和音频播放器
  • 代码编辑器:如 VS Code、Atom 等
  • 聊天应用:构建实时通信工具

1.3 最新版本特性

Electron 团队持续更新框架,带来新特性和性能改进:

  • Electron 20+:支持最新的 Chromium 版本
  • 安全增强:默认启用上下文隔离和沙箱
  • 性能优化:改进启动速度和内存使用
  • API 改进:简化原生功能访问
  • TypeScript 支持:内置 TypeScript 类型定义

🔧 环境搭建

2.1 开发环境配置

2.1.1 安装 Node.js

确保安装了最新版本的 Node.js(建议 v16+):

bash
# 检查 Node.js 版本
node -v

# 检查 npm 版本
npm -v

# 推荐使用 nvm 管理 Node.js 版本
# 安装 nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# 使用 nvm 安装 Node.js
nvm install 20
nvm use 20

2.1.2 创建项目

使用官方推荐的方式创建 Electron 项目:

bash
# 方法 1: 使用 npm create vite@latest
npm create vite@latest my-electron-app -- --template vanilla
cd my-electron-app

# 安装 Electron
npm install electron --save-dev

# 安装 electron-builder(用于打包)
npm install electron-builder --save-dev

# 方法 2: 使用官方示例
git clone https://github.com/electron/electron-quick-start
cd electron-quick-start
npm install

2.1.3 配置 package.json

json
// package.json
{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "Electron 应用",
  "main": "src/main/index.js",
  "scripts": {
    "start": "electron .",
    "dev": "electron .",
    "build": "electron-builder",
    "build:win": "electron-builder --win",
    "build:mac": "electron-builder --mac",
    "build:linux": "electron-builder --linux",
    "build:all": "electron-builder --win --mac --linux",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "electron",
    "desktop",
    "app"
  ],
  "author": "Your Name",
  "license": "MIT",
  "devDependencies": {
    "electron": "^28.0.0",
    "electron-builder": "^24.0.0"
  }
}

2.2 项目结构

推荐的项目结构:

bash
my-electron-app/
  ├── src/                # 源代码目录
   ├── main/          # 主进程代码
   ├── index.js   # 主进程入口
   ├── ipc.js     # IPC 通信
   ├── dialogs.js # 系统对话框
   ├── tray.js    # 系统托盘
   └── updater.js # 自动更新
   ├── renderer/      # 渲染进程代码
   ├── index.html # 主页面
   ├── renderer.js # 渲染进程逻辑
   └── styles.css # 样式文件
   ├── preload/       # 预加载脚本
   └── index.js   # 预加载脚本
   └── assets/        # 静态资源
       ├── icon.ico   # Windows 图标
       ├── icon.icns  # macOS 图标
       └── icon.png   # Linux 图标
  ├── package.json       # 项目配置
  ├── README.md          # 项目说明
  └── .gitignore         # Git 忽略文件

2.3 开发工具配置

2.3.1 VS Code 配置

json
// .vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.validate": [
    "javascript",
    "html"
  ]
}

2.3.2 ESLint 配置

javascript
// .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
    browser: true,
    es2021: true
  },
  extends: [
    'eslint:recommended'
  ],
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
  }
};

2.3.3 Prettier 配置

json
// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 80
}

🖥️ 主进程开发

3.1 主进程配置

主进程是 Electron 应用的入口点,负责创建窗口、处理系统事件和管理应用生命周期。

3.1.1 基本配置

javascript
// src/main/index.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const { createTray } = require('./tray');
const { setupAutoUpdater } = require('./updater');

// 保持窗口引用,防止被垃圾回收
let mainWindow;
let tray;

function createWindow() {
  // 创建浏览器窗口
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    minWidth: 800,
    minHeight: 600,
    title: 'Electron App',
    icon: path.join(__dirname, '../assets/icon.png'),
    frame: true, // 使用自定义框架时设为 false
    transparent: false, // 透明窗口
    resizable: true,
    webPreferences: {
      // 安全配置
      nodeIntegration: false, // 禁用节点集成
      contextIsolation: true, // 启用上下文隔离
      sandbox: false, // 沙箱模式
      
      // 预加载脚本
      preload: path.join(__dirname, '../preload/index.js'),
      
      // 性能配置
      backgroundThrottling: false, // 禁用后台节流
      disableHtmlFullscreenWindowResize: true, // 禁用 HTML 全屏窗口调整
      
      // 开发配置
      devTools: process.env.NODE_ENV === 'development'
    }
  });

  // 加载页面
  if (process.env.NODE_ENV === 'development') {
    // 开发环境加载本地服务器
    mainWindow.loadURL('http://localhost:3000');
  } else {
    // 生产环境加载本地文件
    mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
  }

  // 打开开发者工具
  if (process.env.NODE_ENV === 'development') {
    mainWindow.webContents.openDevTools({ mode: 'detach' });
  }

  // 窗口事件
  mainWindow.on('closed', () => {
    // 释放窗口引用
    mainWindow = null;
  });

  mainWindow.on('resize', () => {
    // 处理窗口 resize 事件
  });

  mainWindow.on('focus', () => {
    // 处理窗口聚焦事件
  });

  // 创建系统托盘
  tray = createTray(mainWindow);

  // 设置自动更新
  setupAutoUpdater(mainWindow);
}

// 应用生命周期事件
app.whenReady().then(() => {
  createWindow();

  // macOS 特定: 点击 Dock 图标时重新创建窗口
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

// 关闭所有窗口时退出应用(Windows & Linux)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 应用启动完成
app.on('ready', () => {
  console.log('应用启动完成');
});

// 应用将退出
app.on('will-quit', (event) => {
  console.log('应用将退出');
  // 可以在这里阻止退出
  // event.preventDefault();
});

// 应用已退出
app.on('quit', (event, exitCode) => {
  console.log(`应用已退出,退出码: ${exitCode}`);
});

// 导出窗口引用,供其他模块使用
module.exports = { mainWindow };

3.1.2 多窗口管理

javascript
// src/main/windows.js
const { BrowserWindow } = require('electron');
const path = require('path');

class WindowManager {
  constructor() {
    this.windows = new Map();
  }

  // 创建新窗口
  createWindow(id, options = {}) {
    const window = new BrowserWindow({
      width: 800,
      height: 600,
      webPreferences: {
        nodeIntegration: false,
        contextIsolation: true,
        preload: path.join(__dirname, '../preload/index.js')
      },
      ...options
    });

    this.windows.set(id, window);

    // 监听窗口关闭事件
    window.on('closed', () => {
      this.windows.delete(id);
    });

    return window;
  }

  // 获取窗口
  getWindow(id) {
    return this.windows.get(id);
  }

  // 关闭窗口
  closeWindow(id) {
    const window = this.windows.get(id);
    if (window) {
      window.close();
      this.windows.delete(id);
    }
  }

  // 关闭所有窗口
  closeAllWindows() {
    this.windows.forEach(window => {
      if (!window.isDestroyed()) {
        window.close();
      }
    });
    this.windows.clear();
  }

  // 广播消息到所有窗口
  broadcast(message, data) {
    this.windows.forEach(window => {
      if (!window.isDestroyed()) {
        window.webContents.send(message, data);
      }
    });
  }
}

module.exports = new WindowManager();

3.2 进程间通信

进程间通信(IPC)是 Electron 应用中主进程和渲染进程之间交换数据的重要方式。

3.2.1 基本 IPC 通信

javascript
// src/main/ipc.js
const { ipcMain, dialog } = require('electron');
const { mainWindow } = require('./index');

// 处理异步消息
ipcMain.handle('get-app-info', async () => {
  return {
    name: app.name,
    version: app.getVersion(),
    electron: process.versions.electron,
    platform: process.platform
  };
});

// 处理同步消息
ipcMain.on('get-sync-data', (event) => {
  event.returnValue = '同步数据响应';
});

// 处理带参数的消息
ipcMain.handle('calculate', async (event, { a, b, operation }) => {
  switch (operation) {
    case 'add':
      return a + b;
    case 'subtract':
      return a - b;
    case 'multiply':
      return a * b;
    case 'divide':
      return a / b;
    default:
      throw new Error('不支持的操作');
  }
});

// 处理文件对话框
ipcMain.handle('open-file-dialog', async () => {
  const result = await dialog.showOpenDialog({
    properties: ['openFile', 'multiSelections'],
    filters: [
      { name: '文本文件', extensions: ['txt', 'md'] },
      { name: '所有文件', extensions: ['*'] }
    ]
  });

  if (!result.canceled) {
    return result.filePaths;
  }
  return [];
});

// 处理保存文件对话框
ipcMain.handle('save-file-dialog', async (event, defaultPath) => {
  const result = await dialog.showSaveDialog({
    defaultPath,
    filters: [
      { name: '文本文件', extensions: ['txt'] },
      { name: '所有文件', extensions: ['*'] }
    ]
  });

  if (!result.canceled) {
    return result.filePath;
  }
  return null;
});

// 窗口操作
ipcMain.handle('window-minimize', () => {
  if (mainWindow) {
    mainWindow.minimize();
  }
});

ipcMain.handle('window-maximize', () => {
  if (mainWindow) {
    if (mainWindow.isMaximized()) {
      mainWindow.unmaximize();
    } else {
      mainWindow.maximize();
    }
  }
});

ipcMain.handle('window-close', () => {
  if (mainWindow) {
    mainWindow.close();
  }
});

// 消息广播
function broadcastMessage(channel, data) {
  const windows = BrowserWindow.getAllWindows();
  windows.forEach(window => {
    if (!window.isDestroyed()) {
      window.webContents.send(channel, data);
    }
  });
}

module.exports = { broadcastMessage };

3.2.2 安全的 IPC 通信

javascript
// src/main/ipc-security.js
const { ipcMain } = require('electron');

// 定义允许的通道
const ALLOWED_CHANNELS = {
  // 应用操作
  'app:get-info': true,
  'app:restart': true,
  'app:quit': true,
  
  // 文件操作
  'file:open': true,
  'file:save': true,
  'file:delete': true,
  
  // 窗口操作
  'window:minimize': true,
  'window:maximize': true,
  'window:close': true,
  
  // 系统操作
  'system:tray': true,
  'system:dialog': true
};

// 验证通道是否允许
function validateChannel(channel) {
  return ALLOWED_CHANNELS[channel] === true;
}

// 验证数据格式
function validateData(data, schema) {
  // 实现数据验证逻辑
  return true;
}

// 安全的 IPC 处理
class SecureIPC {
  constructor() {
    this.setupHandlers();
  }

  setupHandlers() {
    // 通用消息处理
    ipcMain.handle('secure:message', (event, { channel, data, schema }) => {
      // 验证通道
      if (!validateChannel(channel)) {
        throw new Error(`禁止的通道: ${channel}`);
      }

      // 验证数据
      if (schema && !validateData(data, schema)) {
        throw new Error('无效的数据格式');
      }

      // 处理消息
      return this.handleMessage(channel, data);
    });
  }

  async handleMessage(channel, data) {
    switch (channel) {
      case 'app:get-info':
        return this.getAppInfo();
      case 'file:open':
        return this.openFile(data);
      // 其他通道处理
      default:
        throw new Error(`未处理的通道: ${channel}`);
    }
  }

  async getAppInfo() {
    return {
      name: app.name,
      version: app.getVersion()
    };
  }

  async openFile(data) {
    // 实现文件打开逻辑
    return [];
  }
}

module.exports = new SecureIPC();

🖥️ 渲染进程开发

渲染进程是 Electron 应用中负责显示用户界面的部分,基于 Chromium 引擎运行,可以使用现代 Web 技术构建。

4.1 页面开发

4.1.1 基本页面结构

html
<!-- src/renderer/index.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Electron App</title>
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; frame-src 'none';">
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div id="app">
    <header>
      <h1>欢迎使用 Electron</h1>
      <div class="window-controls">
        <button id="minimize">−</button>
        <button id="maximize">□</button>
        <button id="close">×</button>
      </div>
    </header>
    <main>
      <button id="openFile">打开文件</button>
      <button id="saveFile">保存文件</button>
      <div id="content"></div>
    </main>
    <footer>
      <p>Electron App © 2026</p>
    </footer>
  </div>
  <script src="renderer.js"></script>
</body>
</html>
css
/* src/renderer/styles.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  background-color: #f5f5f5;
  color: #333;
}

#app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

header {
  background-color: #2c3e50;
  color: white;
  padding: 1rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.window-controls {
  display: flex;
  gap: 0.5rem;
}

.window-controls button {
  width: 30px;
  height: 30px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
  font-weight: bold;
}

#minimize {
  background-color: #f39c12;
  color: white;
}

#maximize {
  background-color: #27ae60;
  color: white;
}

#close {
  background-color: #e74c3c;
  color: white;
}

main {
  flex: 1;
  padding: 2rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

button {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  background-color: #3498db;
  color: white;
  font-size: 14px;
  cursor: pointer;
  transition: background-color 0.2s;
}

button:hover {
  background-color: #2980b9;
}

#content {
  margin-top: 1rem;
  padding: 1rem;
  background-color: white;
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  min-height: 200px;
}

footer {
  background-color: #ecf0f1;
  padding: 1rem;
  text-align: center;
  font-size: 14px;
  color: #7f8c8d;
}
javascript
// src/renderer/renderer.js
// 使用暴露的 API
if (window.electronAPI) {
  // 窗口控制
  document.getElementById('minimize').addEventListener('click', () => {
    window.electronAPI.minimize();
  });

  document.getElementById('maximize').addEventListener('click', () => {
    window.electronAPI.maximize();
  });

  document.getElementById('close').addEventListener('click', () => {
    window.electronAPI.close();
  });

  // 文件操作
  document.getElementById('openFile').addEventListener('click', async () => {
    try {
      const files = await window.electronAPI.openFile();
      if (files && files.length > 0) {
        document.getElementById('content').textContent = `打开的文件: ${files.join(', ')}`;
      }
    } catch (error) {
      console.error('打开文件失败:', error);
    }
  });

  document.getElementById('saveFile').addEventListener('click', async () => {
    try {
      const content = 'Hello Electron!';
      const filePath = await window.electronAPI.saveFile(content);
      if (filePath) {
        document.getElementById('content').textContent = `文件已保存到: ${filePath}`;
      }
    } catch (error) {
      console.error('保存文件失败:', error);
    }
  });

  // 获取应用信息
  async function loadAppInfo() {
    try {
      const appInfo = await window.electronAPI.getAppInfo();
      console.log('应用信息:', appInfo);
    } catch (error) {
      console.error('获取应用信息失败:', error);
    }
  }

  // 加载应用信息
  loadAppInfo();

  // 监听更新事件
  window.electronAPI.onUpdateAvailable((version) => {
    console.log('发现新版本:', version);
    alert(`发现新版本 ${version},请更新应用`);
  });

  window.electronAPI.onUpdateProgress((progress) => {
    console.log('更新进度:', progress);
  });
}

// 页面加载完成
window.addEventListener('DOMContentLoaded', () => {
  console.log('页面加载完成');
});

// 窗口关闭前
window.addEventListener('beforeunload', (event) => {
  // 可以在这里提示用户保存更改
  // event.preventDefault();
  // event.returnValue = '';
});

4.2 预加载脚本

预加载脚本在渲染进程加载之前运行,用于安全地暴露 Electron API 到渲染进程。

4.2.1 基本配置

javascript
// src/preload/index.js
const { contextBridge, ipcRenderer } = require('electron');

// 暴露安全的 API 到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
  // 文件操作
  openFile: () => ipcRenderer.invoke('file:open'),
  saveFile: (content) => ipcRenderer.invoke('file:save', content),
  
  // 窗口操作
  minimize: () => ipcRenderer.invoke('window:minimize'),
  maximize: () => ipcRenderer.invoke('window:maximize'),
  close: () => ipcRenderer.invoke('window:close'),
  
  // 应用信息
  getAppInfo: () => ipcRenderer.invoke('app:get-info'),
  getAppPath: () => ipcRenderer.invoke('app:get-path'),
  
  // 系统操作
  showMessageBox: (options) => ipcRenderer.invoke('system:message-box', options),
  
  // 消息通信(仅允许特定通道)
  onUpdateAvailable: (callback) => {
    ipcRenderer.on('update:available', (event, version) => callback(version));
  },
  onUpdateProgress: (callback) => {
    ipcRenderer.on('update:progress', (event, progress) => callback(progress));
  },
  onNotification: (callback) => {
    ipcRenderer.on('notification', (event, data) => callback(data));
  }
});

// 导出类型定义(用于 TypeScript)
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {};
}

4.2.2 高级预加载脚本

javascript
// src/preload/advanced.js
const { contextBridge, ipcRenderer, webFrame } = require('electron');

// 限制 webFrame 权限
webFrame.setZoomLevelLimits(1, 1);
webFrame.setLayoutZoomLevelLimits(0, 0);

// 安全的 API 暴露
const api = {
  // 应用相关
  app: {
    getVersion: () => ipcRenderer.invoke('app:get-version'),
    getName: () => ipcRenderer.invoke('app:get-name'),
    restart: () => ipcRenderer.invoke('app:restart'),
    quit: () => ipcRenderer.invoke('app:quit')
  },
  
  // 文件系统
  fs: {
    readFile: (path) => ipcRenderer.invoke('fs:read-file', path),
    writeFile: (path, content) => ipcRenderer.invoke('fs:write-file', path, content),
    exists: (path) => ipcRenderer.invoke('fs:exists', path),
    listDir: (path) => ipcRenderer.invoke('fs:list-dir', path)
  },
  
  // 系统相关
  system: {
    getOS: () => ipcRenderer.invoke('system:get-os'),
    getCPUUsage: () => ipcRenderer.invoke('system:get-cpu-usage'),
    getMemoryUsage: () => ipcRenderer.invoke('system:get-memory-usage')
  },
  
  // 通信
  ipc: {
    on: (channel, callback) => {
      // 验证通道
      const validChannels = ['update', 'notification', 'progress'];
      if (validChannels.includes(channel)) {
        ipcRenderer.on(channel, (event, ...args) => callback(...args));
      }
    },
    once: (channel, callback) => {
      const validChannels = ['update', 'notification', 'progress'];
      if (validChannels.includes(channel)) {
        ipcRenderer.once(channel, (event, ...args) => callback(...args));
      }
    }
  }
};

// 暴露 API
contextBridge.exposeInMainWorld('electron', api);

// 清理
process.once('loaded', () => {
  console.log('预加载脚本执行完成');
});

4.3 现代前端框架集成

4.3.1 React 集成

bash
# 创建 React + Electron 项目
npm create vite@latest my-electron-app -- --template react
cd my-electron-app
npm install electron --save-dev
javascript
// src/main/index.js
const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
  const mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, '../preload/index.js'),
      nodeIntegration: false,
      contextIsolation: true
    }
  });

  // 开发环境加载 Vite 服务器
  if (process.env.NODE_ENV === 'development') {
    mainWindow.loadURL('http://localhost:5173');
  } else {
    // 生产环境加载构建文件
    mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
  }
}

// 其他配置...
javascript
// src/renderer/src/App.jsx
import { useState, useEffect } from 'react';

function App() {
  const [files, setFiles] = useState([]);
  const [appInfo, setAppInfo] = useState(null);

  // 打开文件
  const handleOpenFile = async () => {
    if (window.electronAPI) {
      try {
        const result = await window.electronAPI.openFile();
        setFiles(result);
      } catch (error) {
        console.error('打开文件失败:', error);
      }
    }
  };

  // 获取应用信息
  useEffect(() => {
    const loadAppInfo = async () => {
      if (window.electronAPI) {
        try {
          const info = await window.electronAPI.getAppInfo();
          setAppInfo(info);
        } catch (error) {
          console.error('获取应用信息失败:', error);
        }
      }
    };

    loadAppInfo();
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <h1>Electron + React App</h1>
        {appInfo && (
          <p>版本: {appInfo.version}</p>
        )}
      </header>
      <main>
        <button onClick={handleOpenFile}>打开文件</button>
        <div>
          <h2>打开的文件:</h2>
          <ul>
            {files.map((file, index) => (
              <li key={index}>{file}</li>
            ))}
          </ul>
        </div>
      </main>
    </div>
  );
}

export default App;

4.3.2 Vue 集成

bash
# 创建 Vue + Electron 项目
npm create vite@latest my-electron-app -- --template vue
cd my-electron-app
npm install electron --save-dev
javascript
// src/renderer/src/App.vue
<template>
  <div class="app">
    <header>
      <h1>Electron + Vue App</h1>
      <p v-if="appInfo">版本: {{ appInfo.version }}</p>
    </header>
    <main>
      <button @click="handleOpenFile">打开文件</button>
      <button @click="handleSaveFile">保存文件</button>
      <div class="file-list">
        <h2>打开的文件:</h2>
        <ul>
          <li v-for="(file, index) in files" :key="index">{{ file }}</li>
        </ul>
      </div>
    </main>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const files = ref([]);
const appInfo = ref(null);

// 打开文件
const handleOpenFile = async () => {
  if (window.electronAPI) {
    try {
      const result = await window.electronAPI.openFile();
      files.value = result;
    } catch (error) {
      console.error('打开文件失败:', error);
    }
  }
};

// 保存文件
const handleSaveFile = async () => {
  if (window.electronAPI) {
    try {
      const content = 'Hello Electron + Vue!';
      const filePath = await window.electronAPI.saveFile(content);
      if (filePath) {
        alert(`文件已保存到: ${filePath}`);
      }
    } catch (error) {
      console.error('保存文件失败:', error);
    }
  }
};

// 获取应用信息
onMounted(async () => {
  if (window.electronAPI) {
    try {
      const info = await window.electronAPI.getAppInfo();
      appInfo.value = info;
    } catch (error) {
      console.error('获取应用信息失败:', error);
    }
  }
});
</script>

<style scoped>
.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

header {
  background-color: #42b883;
  color: white;
  padding: 1rem;
  text-align: center;
}

main {
  flex: 1;
  padding: 2rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

button {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  background-color: #35495e;
  color: white;
  cursor: pointer;
}

button:hover {
  background-color: #2c3e50;
}

.file-list {
  margin-top: 1rem;
  padding: 1rem;
  background-color: #f5f5f5;
  border-radius: 4px;
}
</style>

4.4 渲染进程生命周期

4.4.1 生命周期事件

javascript
// src/renderer/lifecycle.js
// 页面加载完成
window.addEventListener('DOMContentLoaded', () => {
  console.log('DOM 内容加载完成');
  // 初始化应用
  initializeApp();
});

// 页面完全加载
window.addEventListener('load', () => {
  console.log('页面完全加载');
  // 执行需要完整 DOM 的操作
  setupEventListeners();
});

// 窗口失去焦点
window.addEventListener('blur', () => {
  console.log('窗口失去焦点');
  // 暂停动画、视频等
});

// 窗口获得焦点
window.addEventListener('focus', () => {
  console.log('窗口获得焦点');
  // 恢复动画、视频等
});

// 窗口大小变化
window.addEventListener('resize', () => {
  console.log('窗口大小变化');
  // 调整布局
  resizeLayout();
});

// 窗口关闭前
window.addEventListener('beforeunload', (event) => {
  console.log('窗口即将关闭');
  // 检查是否有未保存的更改
  if (hasUnsavedChanges()) {
    event.preventDefault();
    event.returnValue = '您有未保存的更改,确定要离开吗?';
  }
});

// 页面卸载
window.addEventListener('unload', () => {
  console.log('页面正在卸载');
  // 清理资源
  cleanupResources();
});

// 初始化应用
function initializeApp() {
  console.log('初始化应用');
  // 初始化状态、加载配置等
}

// 设置事件监听器
function setupEventListeners() {
  console.log('设置事件监听器');
  // 绑定用户交互事件
}

// 调整布局
function resizeLayout() {
  // 响应式布局调整
}

// 检查未保存的更改
function hasUnsavedChanges() {
  // 检查逻辑
  return false;
}

// 清理资源
function cleanupResources() {
  console.log('清理资源');
  // 清理定时器、取消网络请求等
}

4.5 渲染进程性能优化

4.5.1 渲染优化

javascript
// src/renderer/performance.js
// 使用 requestAnimationFrame 进行动画
function animate() {
  requestAnimationFrame(animate);
  // 动画逻辑
}

// 防抖
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// 节流
function throttle(func, limit) {
  let inThrottle;
  return function executedFunction(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// 优化滚动事件
const optimizedScroll = debounce(() => {
  console.log('滚动事件触发');
  // 处理滚动逻辑
}, 100);

window.addEventListener('scroll', optimizedScroll);

// 优化 resize 事件
const optimizedResize = throttle(() => {
  console.log(' resize 事件触发');
  // 处理 resize 逻辑
}, 200);

window.addEventListener('resize', optimizedResize);

// 虚拟滚动(处理大量数据)
function setupVirtualScroll(container, items, itemHeight) {
  const containerHeight = container.clientHeight;
  const totalItems = items.length;
  const totalHeight = totalItems * itemHeight;
  
  container.style.height = `${containerHeight}px`;
  container.style.overflow = 'auto';
  
  const content = document.createElement('div');
  content.style.height = `${totalHeight}px`;
  content.style.position = 'relative';
  container.appendChild(content);
  
  const renderWindow = document.createElement('div');
  renderWindow.style.position = 'absolute';
  renderWindow.style.top = '0';
  renderWindow.style.left = '0';
  renderWindow.style.right = '0';
  content.appendChild(renderWindow);
  
  function renderItems() {
    const scrollTop = container.scrollTop;
    const startIndex = Math.floor(scrollTop / itemHeight);
    const endIndex = Math.min(
      startIndex + Math.ceil(containerHeight / itemHeight) + 1,
      totalItems
    );
    
    renderWindow.innerHTML = '';
    renderWindow.style.transform = `translateY(${startIndex * itemHeight}px)`;
    
    for (let i = startIndex; i < endIndex; i++) {
      const item = document.createElement('div');
      item.style.height = `${itemHeight}px`;
      item.style.borderBottom = '1px solid #eee';
      item.style.padding = '10px';
      item.textContent = items[i];
      renderWindow.appendChild(item);
    }
  }
  
  container.addEventListener('scroll', renderItems);
  renderItems();
}

// 使用示例
const container = document.getElementById('virtual-list');
const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
setupVirtualScroll(container, items, 50);

4.5.2 内存优化

javascript
// src/renderer/memory.js
// 避免内存泄漏
function avoidMemoryLeaks() {
  // 1. 正确清理事件监听器
  const element = document.getElementById('my-element');
  const handler = () => console.log('点击事件');
  
  element.addEventListener('click', handler);
  
  // 清理事件监听器
  function cleanup() {
    element.removeEventListener('click', handler);
  }
  
  // 2. 避免闭包陷阱
  function createHandler() {
    const data = Array(1000000).fill('test');
    
    return function() {
      console.log('处理事件');
      // 避免引用大量数据
    };
  }
  
  // 3. 使用 WeakMap 和 WeakSet
  const weakMap = new WeakMap();
  function storeData(obj, data) {
    weakMap.set(obj, data);
  }
  
  // 4. 及时清理定时器
  const timer = setInterval(() => {
    console.log('定时器执行');
  }, 1000);
  
  function clearTimer() {
    clearInterval(timer);
  }
  
  // 5. 清理 WebSocket 连接
  let socket;
  function setupWebSocket() {
    socket = new WebSocket('wss://example.com');
    
    socket.onclose = () => {
      console.log('WebSocket 关闭');
    };
  }
  
  function closeWebSocket() {
    if (socket) {
      socket.close();
      socket = null;
    }
  }
}

// 监控内存使用
function monitorMemory() {
  if (performance && performance.memory) {
    setInterval(() => {
      const memory = performance.memory;
      console.log('内存使用情况:', {
        usedJSHeapSize: (memory.usedJSHeapSize / 1024 / 1024).toFixed(2) + ' MB',
        totalJSHeapSize: (memory.totalJSHeapSize / 1024 / 1024).toFixed(2) + ' MB',
        jsHeapSizeLimit: (memory.jsHeapSizeLimit / 1024 / 1024).toFixed(2) + ' MB'
      });
    }, 5000);
  }
}

// 初始化内存监控
monitorMemory();

4.6 错误处理

4.6.1 全局错误处理

javascript
// src/renderer/error-handling.js
// 捕获未处理的错误
window.addEventListener('error', (event) => {
  console.error('未捕获的错误:', event.error);
  console.error('错误文件:', event.filename);
  console.error('错误行号:', event.lineno);
  console.error('错误列号:', event.colno);
  
  // 显示错误提示
  showErrorNotification('应用发生错误', event.error.message);
  
  // 阻止默认行为
  event.preventDefault();
});

// 捕获未处理的 Promise 拒绝
window.addEventListener('unhandledrejection', (event) => {
  console.error('未处理的 Promise 拒绝:', event.reason);
  
  // 显示错误提示
  showErrorNotification('操作失败', event.reason.message || String(event.reason));
  
  // 阻止默认行为
  event.preventDefault();
});

// 显示错误通知
function showErrorNotification(title, message) {
  if (window.electronAPI) {
    window.electronAPI.showMessageBox({
      type: 'error',
      title: title,
      message: message,
      buttons: ['确定']
    });
  } else {
    alert(`${title}: ${message}`);
  }
}

// 安全的异步操作
async function safeAsyncOperation(operation, errorMessage) {
  try {
    return await operation();
  } catch (error) {
    console.error(errorMessage, error);
    showErrorNotification(errorMessage, error.message);
    return null;
  }
}

// 使用示例
async function loadData() {
  return await safeAsyncOperation(async () => {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new Error('网络请求失败');
    }
    return await response.json();
  }, '加载数据失败');
}

4.7 最佳实践

4.7.1 渲染进程最佳实践

  1. 安全优先

    • 始终使用预加载脚本暴露 API
    • 避免使用 nodeIntegration: true
    • 启用 contextIsolation: true
    • 使用 Content Security Policy
  2. 性能优化

    • 使用 requestAnimationFrame 进行动画
    • 优化事件监听器(防抖、节流)
    • 避免频繁 DOM 操作
    • 使用虚拟滚动处理大量数据
    • 延迟加载非关键资源
  3. 用户体验

    • 响应式设计适配不同窗口大小
    • 提供加载状态和错误提示
    • 优化启动时间
    • 合理使用动画和过渡效果
  4. 代码组织

    • 模块化代码结构
    • 使用现代前端框架(React、Vue、Svelte)
    • 遵循前端代码规范
    • 合理使用状态管理
  5. 调试技巧

    • 使用 Chrome DevTools 调试渲染进程
    • 合理使用 console 日志
    • 监控性能和内存使用
    • 使用 React DevTools 或 Vue DevTools 等扩展

4.7.2 常见问题及解决方案

问题原因解决方案
白屏页面加载失败、预加载脚本错误检查控制台错误、确保预加载脚本正确
渲染卡顿频繁 DOM 操作、复杂动画使用 requestAnimationFrame、虚拟滚动
内存泄漏未清理事件监听器、闭包正确清理资源、使用 WeakMap/WeakSet
API 不可用预加载脚本配置错误检查 contextBridge 配置、确保 API 正确暴露
样式问题平台差异、CSS 兼容性使用跨平台兼容的 CSS、考虑使用 CSS 框架

🔌 原生功能集成

Electron 提供了丰富的 API 来访问系统原生功能,让桌面应用拥有更强大的能力。

5.1 系统对话框

系统对话框是与用户交互的重要方式,用于文件选择、消息提示等操作。

5.1.1 文件对话框

javascript
// src/main/dialogs.js
const { dialog } = require('electron');

// 打开文件对话框
async function openFileDialog(options = {}) {
  const defaultOptions = {
    title: '打开文件',
    defaultPath: '',
    buttonLabel: '打开',
    filters: [
      { name: '所有文件', extensions: ['*'] }
    ],
    properties: ['openFile'],
    message: '选择要打开的文件'
  };

  const result = await dialog.showOpenDialog({
    ...defaultOptions,
    ...options
  });
  
  if (!result.canceled) {
    return result.filePaths;
  }
  return [];
}

// 打开多个文件
async function openMultipleFiles() {
  return await openFileDialog({
    properties: ['openFile', 'multiSelections']
  });
}

// 选择文件夹
async function selectFolder() {
  const result = await dialog.showOpenDialog({
    title: '选择文件夹',
    properties: ['openDirectory']
  });
  
  if (!result.canceled) {
    return result.filePaths[0];
  }
  return null;
}

// 保存文件对话框
async function saveFileDialog(options = {}) {
  const defaultOptions = {
    title: '保存文件',
    defaultPath: '',
    buttonLabel: '保存',
    filters: [
      { name: '所有文件', extensions: ['*'] }
    ],
    message: '选择保存位置'
  };

  const result = await dialog.showSaveDialog({
    ...defaultOptions,
    ...options
  });
  
  if (!result.canceled) {
    return result.filePath;
  }
  return null;
}

// 示例:保存文本文件
async function saveTextFile(content, defaultName = 'untitled.txt') {
  const filePath = await saveFileDialog({
    defaultPath: defaultName,
    filters: [
      { name: '文本文件', extensions: ['txt'] },
      { name: '所有文件', extensions: ['*'] }
    ]
  });
  
  if (filePath) {
    // 保存文件内容
    const fs = require('fs').promises;
    await fs.writeFile(filePath, content, 'utf8');
    return filePath;
  }
  return null;
}

module.exports = {
  openFileDialog,
  openMultipleFiles,
  selectFolder,
  saveFileDialog,
  saveTextFile
};

5.1.2 消息对话框

javascript
// src/main/message-dialogs.js
const { dialog } = require('electron');

// 显示信息对话框
function showInfoMessage(message, options = {}) {
  return dialog.showMessageBox({
    type: 'info',
    title: '信息',
    message,
    buttons: ['确定'],
    defaultId: 0,
    cancelId: -1,
    ...options
  });
}

// 显示警告对话框
function showWarningMessage(message, options = {}) {
  return dialog.showMessageBox({
    type: 'warning',
    title: '警告',
    message,
    buttons: ['确定'],
    defaultId: 0,
    cancelId: -1,
    ...options
  });
}

// 显示错误对话框
function showErrorMessage(message, options = {}) {
  return dialog.showMessageBox({
    type: 'error',
    title: '错误',
    message,
    buttons: ['确定'],
    defaultId: 0,
    cancelId: -1,
    ...options
  });
}

// 显示确认对话框
function showConfirmMessage(message, options = {}) {
  return dialog.showMessageBox({
    type: 'question',
    title: '确认',
    message,
    buttons: ['取消', '确定'],
    defaultId: 1,
    cancelId: 0,
    ...options
  });
}

// 显示选择对话框
function showSelectMessage(message, choices, options = {}) {
  return dialog.showMessageBox({
    type: 'question',
    title: '选择',
    message,
    buttons: choices,
    defaultId: 0,
    cancelId: -1,
    ...options
  });
}

// 示例:确认退出
async function confirmQuit(window) {
  const result = await showConfirmMessage(
    '确定要退出应用吗?未保存的更改将会丢失。',
    {
      parent: window,
      icon: null, // 可以指定图标
      detail: '请确认您的操作',
      checkboxLabel: '不再提示',
      checkboxChecked: false
    }
  );
  
  return {
    confirmed: result.response === 1,
    dontAskAgain: result.checkboxChecked
  };
}

module.exports = {
  showInfoMessage,
  showWarningMessage,
  showErrorMessage,
  showConfirmMessage,
  showSelectMessage,
  confirmQuit
};

5.2 系统托盘

系统托盘是应用在系统任务栏中的图标,提供快速访问和状态显示。

5.2.1 基本托盘

javascript
// src/main/tray.js
const { app, Tray, Menu, nativeImage } = require('electron');
const path = require('path');

class AppTray {
  constructor() {
    this.tray = null;
    this.mainWindow = null;
  }

  // 创建托盘
  create(mainWindow) {
    this.mainWindow = mainWindow;
    
    // 创建托盘图标
    const iconPath = path.join(__dirname, '../assets/tray-icon.png');
    const icon = nativeImage.createFromPath(iconPath);
    
    // 设置图标大小
    icon.setTemplateImage(true); // 在 macOS 上使用模板模式
    
    // 创建托盘
    this.tray = new Tray(icon);
    
    // 设置托盘提示
    this.tray.setToolTip('Electron App');
    
    // 设置上下文菜单
    this.updateMenu();
    
    // 绑定事件
    this.bindEvents();
    
    return this.tray;
  }

  // 更新菜单
  updateMenu() {
    if (!this.tray) return;
    
    const contextMenu = Menu.buildFromTemplate([
      {
        label: '显示主窗口',
        click: () => this.showWindow(),
        enabled: !this.mainWindow.isVisible()
      },
      {
        label: '隐藏主窗口',
        click: () => this.hideWindow(),
        enabled: this.mainWindow.isVisible()
      },
      {
        type: 'separator'
      },
      {
        label: '设置',
        click: () => this.openSettings()
      },
      {
        label: '关于',
        click: () => this.openAbout()
      },
      {
        type: 'separator'
      },
      {
        label: '退出',
        click: () => this.quitApp()
      }
    ]);
    
    this.tray.setContextMenu(contextMenu);
  }

  // 绑定事件
  bindEvents() {
    if (!this.tray) return;
    
    // 点击托盘图标
    this.tray.on('click', () => {
      this.toggleWindow();
    });
    
    // 双击托盘图标
    this.tray.on('double-click', () => {
      this.showWindow();
    });
    
    // 右键点击托盘图标
    this.tray.on('right-click', () => {
      this.updateMenu();
    });
  }

  // 显示窗口
  showWindow() {
    if (this.mainWindow) {
      this.mainWindow.show();
      this.mainWindow.focus();
    }
  }

  // 隐藏窗口
  hideWindow() {
    if (this.mainWindow) {
      this.mainWindow.hide();
    }
  }

  // 切换窗口显示/隐藏
  toggleWindow() {
    if (this.mainWindow) {
      if (this.mainWindow.isVisible()) {
        this.mainWindow.hide();
      } else {
        this.mainWindow.show();
        this.mainWindow.focus();
      }
    }
  }

  // 打开设置
  openSettings() {
    // 实现打开设置窗口的逻辑
    console.log('打开设置');
  }

  // 打开关于
  openAbout() {
    // 实现打开关于窗口的逻辑
    console.log('打开关于');
  }

  // 退出应用
  quitApp() {
    app.quit();
  }

  // 更新托盘图标
  updateIcon(iconPath) {
    if (this.tray) {
      const icon = nativeImage.createFromPath(iconPath);
      icon.setTemplateImage(true);
      this.tray.setImage(icon);
    }
  }

  // 更新托盘提示
  updateToolTip(tooltip) {
    if (this.tray) {
      this.tray.setToolTip(tooltip);
    }
  }

  // 销毁托盘
  destroy() {
    if (this.tray) {
      this.tray.destroy();
      this.tray = null;
    }
  }
}

// 导出单例
module.exports = new AppTray();

5.2.2 高级托盘

javascript
// src/main/advanced-tray.js
const { app, Tray, Menu, nativeImage, Notification } = require('electron');
const path = require('path');

class AdvancedTray extends AppTray {
  constructor() {
    super();
    this.unreadCount = 0;
    this.isConnected = false;
  }

  // 创建托盘
  create(mainWindow) {
    super.create(mainWindow);
    this.updateIconWithBadge();
    return this.tray;
  }

  // 更新菜单
  updateMenu() {
    if (!this.tray) return;
    
    const contextMenu = Menu.buildFromTemplate([
      {
        label: `连接状态: ${this.isConnected ? '已连接' : '未连接'}`,
        enabled: false,
        icon: this.getStatusIcon()
      },
      {
        label: `未读消息: ${this.unreadCount}`,
        enabled: false
      },
      {
        type: 'separator'
      },
      {
        label: '显示主窗口',
        click: () => this.showWindow(),
        enabled: !this.mainWindow.isVisible()
      },
      {
        label: '隐藏主窗口',
        click: () => this.hideWindow(),
        enabled: this.mainWindow.isVisible()
      },
      {
        type: 'separator'
      },
      {
        label: '刷新',
        click: () => this.refresh()
      },
      {
        label: '检查更新',
        click: () => this.checkUpdates()
      },
      {
        type: 'separator'
      },
      {
        label: '设置',
        click: () => this.openSettings()
      },
      {
        label: '关于',
        click: () => this.openAbout()
      },
      {
        type: 'separator'
      },
      {
        label: '退出',
        click: () => this.quitApp()
      }
    ]);
    
    this.tray.setContextMenu(contextMenu);
  }

  // 获取状态图标
  getStatusIcon() {
    const iconPath = this.isConnected 
      ? path.join(__dirname, '../assets/connected.png') 
      : path.join(__dirname, '../assets/disconnected.png');
    return nativeImage.createFromPath(iconPath);
  }

  // 更新图标和徽章
  updateIconWithBadge() {
    if (!this.tray) return;
    
    // 基础图标
    const baseIconPath = path.join(__dirname, '../assets/tray-icon.png');
    let icon = nativeImage.createFromPath(baseIconPath);
    
    // 如果有未读消息,添加徽章
    if (this.unreadCount > 0) {
      icon = this.addBadgeToIcon(icon, this.unreadCount);
    }
    
    // 设置图标
    icon.setTemplateImage(true);
    this.tray.setImage(icon);
  }

  // 添加徽章到图标
  addBadgeToIcon(baseIcon, count) {
    // 创建徽章图标
    const badgeCanvas = document.createElement('canvas');
    const badgeCtx = badgeCanvas.getContext('2d');
    
    // 设置徽章大小
    const badgeSize = 20;
    badgeCanvas.width = badgeSize;
    badgeCanvas.height = badgeSize;
    
    // 绘制红色圆形背景
    badgeCtx.beginPath();
    badgeCtx.arc(badgeSize / 2, badgeSize / 2, badgeSize / 2, 0, Math.PI * 2);
    badgeCtx.fillStyle = '#ff3b30';
    badgeCtx.fill();
    
    // 绘制白色文字
    badgeCtx.fillStyle = '#ffffff';
    badgeCtx.font = 'bold 12px sans-serif';
    badgeCtx.textAlign = 'center';
    badgeCtx.textBaseline = 'middle';
    badgeCtx.fillText(count > 99 ? '99+' : count.toString(), badgeSize / 2, badgeSize / 2);
    
    // 创建徽章图像
    const badgeImage = nativeImage.createFromDataURL(badgeCanvas.toDataURL());
    
    // 合并图标和徽章
    const finalCanvas = document.createElement('canvas');
    const finalCtx = finalCanvas.getContext('2d');
    
    const baseSize = baseIcon.getSize();
    finalCanvas.width = baseSize.width;
    finalCanvas.height = baseSize.height;
    
    // 绘制基础图标
    const baseData = baseIcon.toDataURL();
    const baseImg = new Image();
    baseImg.src = baseData;
    
    return new Promise((resolve) => {
      baseImg.onload = () => {
        finalCtx.drawImage(baseImg, 0, 0);
        
        // 绘制徽章(右上角)
        finalCtx.drawImage(
          badgeImage.toDataURL(),
          baseSize.width - badgeSize,
          0,
          badgeSize,
          badgeSize
        );
        
        resolve(nativeImage.createFromDataURL(finalCanvas.toDataURL()));
      };
    });
  }

  // 更新未读消息数
  setUnreadCount(count) {
    this.unreadCount = Math.max(0, count);
    this.updateIconWithBadge();
    this.updateMenu();
    
    // 显示通知
    if (count > 0) {
      new Notification({
        title: '新消息',
        body: `您有 ${count} 条未读消息`,
        icon: path.join(__dirname, '../assets/icon.png')
      }).show();
    }
  }

  // 更新连接状态
  setConnected(connected) {
    this.isConnected = connected;
    this.updateMenu();
    
    // 显示通知
    new Notification({
      title: '连接状态',
      body: connected ? '已连接到服务器' : '与服务器断开连接',
      icon: path.join(__dirname, '../assets/icon.png')
    }).show();
  }

  // 刷新
  async refresh() {
    try {
      // 实现刷新逻辑
      console.log('刷新中...');
      // 模拟刷新
      await new Promise(resolve => setTimeout(resolve, 1000));
      console.log('刷新完成');
      
      // 显示通知
      new Notification({
        title: '刷新完成',
        body: '应用已成功刷新',
        icon: path.join(__dirname, '../assets/icon.png')
      }).show();
    } catch (error) {
      console.error('刷新失败:', error);
      // 显示错误通知
      new Notification({
        title: '刷新失败',
        body: '刷新过程中发生错误',
        icon: path.join(__dirname, '../assets/icon.png')
      }).show();
    }
  }

  // 检查更新
  async checkUpdates() {
    try {
      // 实现检查更新逻辑
      console.log('检查更新中...');
      // 模拟检查更新
      await new Promise(resolve => setTimeout(resolve, 1500));
      console.log('检查更新完成');
      
      // 显示通知
      new Notification({
        title: '更新检查',
        body: '当前已是最新版本',
        icon: path.join(__dirname, '../assets/icon.png')
      }).show();
    } catch (error) {
      console.error('检查更新失败:', error);
    }
  }
}

// 导出单例
module.exports = new AdvancedTray();

5.3 原生菜单

原生菜单是应用顶部的菜单栏,提供应用的主要功能入口。

5.3.1 基本菜单

javascript
// src/main/menu.js
const { app, Menu, dialog } = require('electron');
const isMac = process.platform === 'darwin';

// 创建应用菜单
function createMenu(mainWindow) {
  const template = [
    // macOS 应用菜单
    ...(isMac ? [{
      label: app.name,
      submenu: [
        {
          label: '关于',
          click: () => showAboutDialog()
        },
        {
          type: 'separator'
        },
        {
          label: '服务',
          submenu: []
        },
        {
          type: 'separator'
        },
        {
          label: '偏好设置',
          accelerator: 'Cmd+,',
          click: () => openSettings()
        },
        {
          type: 'separator'
        },
        {
          label: '隐藏',
          accelerator: 'Cmd+H',
          click: () => app.hide()
        },
        {
          label: '隐藏其他',
          accelerator: 'Cmd+Alt+H',
          click: () => app.hideOtherApplications()
        },
        {
          label: '显示全部',
          click: () => app.showAllApplications()
        },
        {
          type: 'separator'
        },
        {
          label: '退出',
          accelerator: 'Cmd+Q',
          click: () => app.quit()
        }
      ]
    }] : []),
    
    // 文件菜单
    {
      label: '文件',
      submenu: [
        {
          label: '新建',
          accelerator: isMac ? 'Cmd+N' : 'Ctrl+N',
          click: () => createNewFile(mainWindow)
        },
        {
          label: '打开',
          accelerator: isMac ? 'Cmd+O' : 'Ctrl+O',
          click: () => openFile(mainWindow)
        },
        {
          label: '打开最近',
          submenu: [
            {
              label: '清除最近文件',
              click: () => clearRecentFiles()
            }
          ]
        },
        {
          type: 'separator'
        },
        {
          label: '保存',
          accelerator: isMac ? 'Cmd+S' : 'Ctrl+S',
          click: () => saveFile(mainWindow)
        },
        {
          label: '另存为',
          accelerator: isMac ? 'Cmd+Shift+S' : 'Ctrl+Shift+S',
          click: () => saveAsFile(mainWindow)
        },
        {
          type: 'separator'
        },
        {
          label: '退出',
          accelerator: isMac ? 'Cmd+Q' : 'Alt+F4',
          click: () => app.quit()
        }
      ]
    },
    
    // 编辑菜单
    {
      label: '编辑',
      submenu: [
        {
          label: '撤销',
          accelerator: isMac ? 'Cmd+Z' : 'Ctrl+Z',
          role: 'undo'
        },
        {
          label: '重做',
          accelerator: isMac ? 'Cmd+Shift+Z' : 'Ctrl+Y',
          role: 'redo'
        },
        {
          type: 'separator'
        },
        {
          label: '剪切',
          accelerator: isMac ? 'Cmd+X' : 'Ctrl+X',
          role: 'cut'
        },
        {
          label: '复制',
          accelerator: isMac ? 'Cmd+C' : 'Ctrl+C',
          role: 'copy'
        },
        {
          label: '粘贴',
          accelerator: isMac ? 'Cmd+V' : 'Ctrl+V',
          role: 'paste'
        },
        {
          label: '删除',
          role: 'delete'
        },
        {
          type: 'separator'
        },
        {
          label: '全选',
          accelerator: isMac ? 'Cmd+A' : 'Ctrl+A',
          role: 'selectAll'
        }
      ]
    },
    
    // 视图菜单
    {
      label: '视图',
      submenu: [
        {
          label: '刷新',
          accelerator: isMac ? 'Cmd+R' : 'F5',
          click: () => mainWindow.reload()
        },
        {
          label: '切换开发者工具',
          accelerator: isMac ? 'Cmd+Alt+I' : 'Ctrl+Shift+I',
          click: () => mainWindow.webContents.toggleDevTools()
        },
        {
          type: 'separator'
        },
        {
          label: '切换全屏',
          accelerator: isMac ? 'Ctrl+Cmd+F' : 'F11',
          click: () => mainWindow.setFullScreen(!mainWindow.isFullScreen())
        },
        {
          label: '缩放',
          submenu: [
            {
              label: '放大',
              accelerator: 'Cmd+Plus',
              click: () => mainWindow.webContents zoomIn()
            },
            {
              label: '缩小',
              accelerator: 'Cmd+-',
              click: () => mainWindow.webContents.zoomOut()
            },
            {
              label: '重置',
              accelerator: 'Cmd+0',
              click: () => mainWindow.webContents.zoomLevel = 0
            }
          ]
        }
      ]
    },
    
    // 窗口菜单
    {
      label: '窗口',
      submenu: [
        {
          label: '最小化',
          accelerator: isMac ? 'Cmd+M' : 'Ctrl+M',
          click: () => mainWindow.minimize()
        },
        {
          label: '最大化',
          click: () => {
            if (mainWindow.isMaximized()) {
              mainWindow.unmaximize();
            } else {
              mainWindow.maximize();
            }
          }
        },
        {
          label: '关闭',
          accelerator: isMac ? 'Cmd+W' : 'Ctrl+W',
          click: () => mainWindow.close()
        },
        {
          type: 'separator'
        },
        {
          label: '前置所有窗口',
          click: () => mainWindow.focus()
        }
      ]
    },
    
    // 帮助菜单
    {
      label: '帮助',
      submenu: [
        {
          label: '文档',
          click: () => openDocumentation()
        },
        {
          label: '教程',
          click: () => openTutorial()
        },
        {
          label: '常见问题',
          click: () => openFAQ()
        },
        {
          type: 'separator'
        },
        {
          label: '检查更新',
          click: () => checkUpdates()
        },
        {
          label: '关于',
          click: () => showAboutDialog()
        }
      ]
    }
  ];

  const menu = Menu.buildFromTemplate(template);
  Menu.setApplicationMenu(menu);
  
  return menu;
}

// 显示关于对话框
function showAboutDialog() {
  dialog.showMessageBox({
    title: '关于 Electron App',
    message: 'Electron App',
    detail: `版本: ${app.getVersion()}\nElectron: ${process.versions.electron}\nNode.js: ${process.version}\nV8: ${process.versions.v8}`,
    buttons: ['确定'],
    icon: null
  });
}

// 新建文件
function createNewFile(mainWindow) {
  mainWindow.webContents.send('file:new');
}

// 打开文件
function openFile(mainWindow) {
  mainWindow.webContents.send('file:open');
}

// 保存文件
function saveFile(mainWindow) {
  mainWindow.webContents.send('file:save');
}

// 另存为
function saveAsFile(mainWindow) {
  mainWindow.webContents.send('file:save-as');
}

// 清除最近文件
function clearRecentFiles() {
  app.clearRecentDocuments();
}

// 打开设置
function openSettings() {
  console.log('打开设置');
}

// 打开文档
function openDocumentation() {
  console.log('打开文档');
}

// 打开教程
function openTutorial() {
  console.log('打开教程');
}

// 打开常见问题
function openFAQ() {
  console.log('打开常见问题');
}

// 检查更新
function checkUpdates() {
  console.log('检查更新');
}

// 创建上下文菜单
function createContextMenu() {
  const contextMenu = Menu.buildFromTemplate([
    {
      label: '复制',
      role: 'copy'
    },
    {
      label: '粘贴',
      role: 'paste'
    },
    {
      label: '剪切',
      role: 'cut'
    },
    {
      type: 'separator'
    },
    {
      label: '全选',
      role: 'selectAll'
    }
  ]);
  
  return contextMenu;
}

module.exports = {
  createMenu,
  createContextMenu,
  showAboutDialog
};

5.4 原生通知

原生通知可以向用户发送系统级别的通知,提高用户体验。

5.4.1 基本通知

javascript
// src/main/notifications.js
const { app, Notification } = require('electron');
const path = require('path');

// 检查通知权限
function checkNotificationPermission() {
  if (Notification.isSupported()) {
    console.log('通知功能可用');
    return true;
  } else {
    console.log('通知功能不可用');
    return false;
  }
}

// 发送通知
function sendNotification(options) {
  if (!checkNotificationPermission()) {
    return null;
  }
  
  const defaultOptions = {
    title: 'Electron App',
    body: '这是一条通知',
    icon: path.join(__dirname, '../assets/icon.png'),
    silent: false,
    timeoutType: 'default',
    urgency: 'normal' // low, normal, critical
  };
  
  const notification = new Notification({
    ...defaultOptions,
    ...options
  });
  
  notification.show();
  return notification;
}

// 发送成功通知
function sendSuccessNotification(title, body) {
  return sendNotification({
    title,
    body,
    icon: path.join(__dirname, '../assets/success.png')
  });
}

// 发送错误通知
function sendErrorNotification(title, body) {
  return sendNotification({
    title,
    body,
    icon: path.join(__dirname, '../assets/error.png'),
    urgency: 'critical'
  });
}

// 发送警告通知
function sendWarningNotification(title, body) {
  return sendNotification({
    title,
    body,
    icon: path.join(__dirname, '../assets/warning.png'),
    urgency: 'normal'
  });
}

// 发送信息通知
function sendInfoNotification(title, body) {
  return sendNotification({
    title,
    body,
    icon: path.join(__dirname, '../assets/info.png'),
    urgency: 'low'
  });
}

// 发送带按钮的通知(仅支持某些平台)
function sendNotificationWithButtons(title, body, buttons) {
  return sendNotification({
    title,
    body,
    actions: buttons.map((button, index) => ({
      type: 'button',
      text: button.text,
      id: `button-${index}`
    })),
    hasReply: false
  });
}

// 发送带回复的通知(仅支持某些平台)
function sendNotificationWithReply(title, body) {
  return sendNotification({
    title,
    body,
    hasReply: true,
    replyPlaceholder: '输入回复...'
  });
}

// 示例:发送下载完成通知
function sendDownloadCompleteNotification(fileName, filePath) {
  const notification = sendSuccessNotification(
    '下载完成',
    `文件 "${fileName}" 已下载完成`
  );
  
  if (notification) {
    notification.on('click', () => {
      // 打开文件所在目录
      const { shell } = require('electron');
      shell.showItemInFolder(filePath);
    });
  }
  
  return notification;
}

// 示例:发送更新通知
function sendUpdateNotification(version) {
  const notification = sendInfoNotification(
    '应用更新',
    `发现新版本 ${version},是否立即更新?`
  );
  
  if (notification) {
    notification.on('click', () => {
      // 打开更新页面
      console.log('打开更新页面');
    });
  }
  
  return notification;
}

module.exports = {
  checkNotificationPermission,
  sendNotification,
  sendSuccessNotification,
  sendErrorNotification,
  sendWarningNotification,
  sendInfoNotification,
  sendNotificationWithButtons,
  sendNotificationWithReply,
  sendDownloadCompleteNotification,
  sendUpdateNotification
};

5.5 系统剪贴板

系统剪贴板可以在应用和系统之间共享数据,支持文本、图片等多种格式。

5.5.1 基本操作

javascript
// src/main/clipboard.js
const { clipboard, nativeImage } = require('electron');

// 写入文本
function writeText(text) {
  clipboard.writeText(text);
  console.log('文本已复制到剪贴板');
}

// 读取文本
function readText() {
  const text = clipboard.readText();
  console.log('剪贴板中的文本:', text);
  return text;
}

// 写入HTML
function writeHTML(html) {
  clipboard.writeHTML(html);
  console.log('HTML已复制到剪贴板');
}

// 读取HTML
function readHTML() {
  const html = clipboard.readHTML();
  console.log('剪贴板中的HTML:', html);
  return html;
}

// 写入图片
function writeImage(imagePath) {
  const image = nativeImage.createFromPath(imagePath);
  clipboard.writeImage(image);
  console.log('图片已复制到剪贴板');
}

// 读取图片
function readImage() {
  const image = clipboard.readImage();
  if (!image.isEmpty()) {
    console.log('剪贴板中有图片');
    return image;
  }
  console.log('剪贴板中没有图片');
  return null;
}

// 写入RTF
function writeRTF(rtf) {
  clipboard.writeRTF(rtf);
  console.log('RTF已复制到剪贴板');
}

// 读取RTF
function readRTF() {
  const rtf = clipboard.readRTF();
  console.log('剪贴板中的RTF:', rtf);
  return rtf;
}

// 写入多种格式
function writeMultipleFormats(data) {
  clipboard.write(data);
  console.log('多种格式数据已复制到剪贴板');
}

// 读取多种格式
function readMultipleFormats() {
  const formats = clipboard.availableFormats();
  console.log('剪贴板中可用的格式:', formats);
  
  const data = {};
  formats.forEach(format => {
    try {
      data[format] = clipboard.read(format);
    } catch (error) {
      console.error(`读取格式 ${format} 时出错:`, error);
    }
  });
  
  return data;
}

// 清除剪贴板
function clearClipboard() {
  clipboard.clear();
  console.log('剪贴板已清除');
}

// 检查剪贴板是否为空
function isClipboardEmpty() {
  const isEmpty = clipboard.isEmpty();
  console.log('剪贴板是否为空:', isEmpty);
  return isEmpty;
}

// 示例:复制文件路径
function copyFilePath(filePath) {
  writeText(filePath);
  console.log(`文件路径已复制: ${filePath}`);
}

// 示例:复制图片
function copyImageToClipboard(imagePath) {
  writeImage(imagePath);
  console.log(`图片已复制: ${imagePath}`);
}

module.exports = {
  writeText,
  readText,
  writeHTML,
  readHTML,
  writeImage,
  readImage,
  writeRTF,
  readRTF,
  writeMultipleFormats,
  readMultipleFormats,
  clearClipboard,
  isClipboardEmpty,
  copyFilePath,
  copyImageToClipboard
};

5.5.2 高级操作

javascript
// src/main/advanced-clipboard.js
const { clipboard, nativeImage } = require('electron');
const fs = require('fs').promises;
const path = require('path');

// 监控剪贴板变化
function watchClipboard(callback, interval = 1000) {
  let previousText = clipboard.readText();
  let previousImage = clipboard.readImage().toDataURL();
  
  const watcher = setInterval(() => {
    const currentText = clipboard.readText();
    const currentImage = clipboard.readImage().toDataURL();
    
    if (currentText !== previousText) {
      previousText = currentText;
      callback('text', currentText);
    }
    
    if (currentImage !== previousImage) {
      previousImage = currentImage;
      callback('image', currentImage);
    }
  }, interval);
  
  return {
    stop: () => clearInterval(watcher)
  };
}

// 从剪贴板保存图片
async function saveImageFromClipboard(savePath) {
  const image = clipboard.readImage();
  if (image.isEmpty()) {
    throw new Error('剪贴板中没有图片');
  }
  
  // 确保目录存在
  const dir = path.dirname(savePath);
  await fs.mkdir(dir, { recursive: true });
  
  // 保存图片
  const imageBuffer = image.toPNG();
  await fs.writeFile(savePath, imageBuffer);
  
  console.log(`图片已保存到: ${savePath}`);
  return savePath;
}

// 复制带有元数据的文本
function copyTextWithMetadata(text, metadata) {
  const data = {
    'text/plain': text,
    'application/json': JSON.stringify(metadata)
  };
  
  clipboard.write(data);
  console.log('文本和元数据已复制到剪贴板');
}

// 读取带有元数据的文本
function readTextWithMetadata() {
  const text = clipboard.readText();
  let metadata = null;
  
  try {
    const metadataStr = clipboard.read('application/json');
    metadata = JSON.parse(metadataStr);
  } catch (error) {
    console.error('读取元数据时出错:', error);
  }
  
  return {
    text,
    metadata
  };
}

module.exports = {
  watchClipboard,
  saveImageFromClipboard,
  copyTextWithMetadata,
  readTextWithMetadata
};

5.6 Shell 操作

Shell 模块提供了与桌面集成的功能,如打开文件、文件夹、URL 等。

5.6.1 基本操作

javascript
// src/main/shell.js
const { shell } = require('electron');

// 打开文件
function openFile(filePath) {
  return shell.openPath(filePath);
}

// 打开文件夹
function openFolder(folderPath) {
  return shell.showItemInFolder(folderPath);
}

// 打开URL
function openURL(url) {
  return shell.openExternal(url);
}

// 移动文件到回收站
function moveToTrash(filePath) {
  return shell.trashItem(filePath);
}

// 显示文件属性
function showFileProperties(filePath) {
  return shell.showItemInFolder(filePath);
}

// 执行默认操作
function executeDefaultAction(filePath) {
  return shell.openPath(filePath);
}

// 示例:打开文档
function openDocument(docPath) {
  shell.openPath(docPath)
    .then(success => {
      if (success) {
        console.log(`文档已打开: ${docPath}`);
      } else {
        console.error(`无法打开文档: ${docPath}`);
      }
    });
}

// 示例:打开GitHub
function openGitHub() {
  shell.openExternal('https://github.com')
    .then(success => {
      if (success) {
        console.log('GitHub已打开');
      } else {
        console.error('无法打开GitHub');
      }
    });
}

// 示例:删除文件到回收站
function deleteToTrash(filePath) {
  shell.trashItem(filePath)
    .then(success => {
      if (success) {
        console.log(`文件已移动到回收站: ${filePath}`);
      } else {
        console.error(`无法删除文件: ${filePath}`);
      }
    });
}

module.exports = {
  openFile,
  openFolder,
  openURL,
  moveToTrash,
  showFileProperties,
  executeDefaultAction,
  openDocument,
  openGitHub,
  deleteToTrash
};

📦 打包发布

打包和发布是Electron应用开发的重要环节,涉及多平台构建、代码签名、自动更新等多个方面。

6.1 electron-builder 配置

6.1.1 基本配置

json
// package.json
{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "Electron 应用",
  "main": "src/main/index.js",
  "scripts": {
    "start": "electron .",
    "dev": "electron .",
    "build": "electron-builder",
    "build:win": "electron-builder --win",
    "build:mac": "electron-builder --mac",
    "build:linux": "electron-builder --linux",
    "build:all": "electron-builder --win --mac --linux",
    "build:win32": "electron-builder --win --ia32",
    "build:win64": "electron-builder --win --x64",
    "build:mac-arm": "electron-builder --mac --arm64",
    "build:mac-x64": "electron-builder --mac --x64",
    "build:linux-arm": "electron-builder --linux --arm64",
    "build:linux-x64": "electron-builder --linux --x64"
  },
  "build": {
    "appId": "com.example.app",
    "productName": "My Electron App",
    "copyright": "Copyright © 2026 ${author}",
    "directories": {
      "output": "dist",
      "buildResources": "src/assets"
    },
    "files": [
      "src/**/*",
      "package.json"
    ],
    "extraResources": [
      {
        "from": "src/resources",
        "to": "resources"
      }
    ],
    "mac": {
      "target": [
        "dmg",
        "pkg",
        "zip"
      ],
      "icon": "src/assets/icon.icns",
      "category": "public.app-category.utilities",
      "hardenedRuntime": true,
      "gatekeeperAssess": false,
      "entitlements": "build/entitlements.mac.plist",
      "entitlementsInherit": "build/entitlements.mac.plist",
      "notarize": {
        "teamId": "YOUR_TEAM_ID"
      }
    },
    "win": {
      "target": [
        "nsis",
        "portable",
        "zip"
      ],
      "icon": "src/assets/icon.ico",
      "publisherName": "Your Company",
      "signingHashAlgorithms": ["sha256"],
      "rfc3161TimeStampServer": "http://timestamp.digicert.com"
    },
    "linux": {
      "target": [
        "AppImage",
        "deb",
        "rpm",
        "snap",
        "tar.gz"
      ],
      "icon": "src/assets/icon.png",
      "category": "Utility",
      "desktop": {
        "Name": "My Electron App",
        "Comment": "An Electron application",
        "Categories": "Utility;Application"
      }
    },
    "nsis": {
      "oneClick": false,
      "allowToChangeInstallationDirectory": true,
      "installerIcon": "src/assets/installer.ico",
      "uninstallerIcon": "src/assets/uninstaller.ico",
      "installerHeaderIcon": "src/assets/installer.ico",
      "createDesktopShortcut": true,
      "createStartMenuShortcut": true,
      "shortcutName": "My App"
    },
    "appImage": {
      "category": "Utility"
    },
    "deb": {
      "maintainer": "Your Name <your.email@example.com>",
      "depends": ["gconf2", "gconf-service", "libnotify4", "libappindicator1", "libxtst6", "libnss3"]
    },
    "rpm": {
      "license": "MIT",
      "vendor": "Your Company",
      "category": "Utility"
    },
    "snap": {
      "grade": "stable",
      "confinement": "strict",
      "summary": "My Electron App",
      "description": "An Electron application built with modern web technologies"
    },
    "publish": [
      {
        "provider": "github",
        "owner": "your-username",
        "repo": "your-repo",
        "token": "${GH_TOKEN}",
        "releaseType": "release",
        "prerelease": false
      }
    ]
  },
  "devDependencies": {
    "electron": "^28.0.0",
    "electron-builder": "^24.0.0",
    "electron-notarize": "^1.2.0"
  }
}

6.1.2 签名配置

xml
<!-- build/entitlements.mac.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <!-- 基本权限 -->
    <key>com.apple.security.app-sandbox</key>
    <true/>
    
    <!-- 网络访问 -->
    <key>com.apple.security.network.client</key>
    <true/>
    <key>com.apple.security.network.server</key>
    <true/>
    
    <!-- 文件系统访问 -->
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>
    <key>com.apple.security.files.downloads.read-write</key>
    <true/>
    
    <!-- 进程间通信 -->
    <key>com.apple.security.automation.apple-events</key>
    <true/>
    
    <!-- 硬件访问 -->
    <key>com.apple.security.device.camera</key>
    <true/>
    <key>com.apple.security.device.microphone</key>
    <true/>
    
    <!-- 系统功能 -->
    <key>com.apple.security.screenCapture</key>
    <true/>
    <key>com.apple.security.print</key>
    <true/>
  </dict>
</plist>

6.2 自动更新

6.2.1 electron-updater 配置

javascript
// src/main/updater.js
const { app, dialog, autoUpdater } = require('electron');
const path = require('path');
const log = require('electron-log');

// 配置日志
log.transports.file.level = 'info';
log.transports.console.level = 'info';
autoUpdater.logger = log;

// 配置自动更新
function setupAutoUpdater(mainWindow) {
  // 设置更新源
  const updateFeed = 'https://github.com/your-username/your-repo/releases/download/v{version}/';
  
  if (process.env.NODE_ENV === 'production') {
    autoUpdater.setFeedURL({
      provider: 'github',
      owner: 'your-username',
      repo: 'your-repo',
      releaseType: 'release'
    });
    
    // 检查更新频率(每小时)
    setInterval(() => {
      autoUpdater.checkForUpdates();
    }, 60 * 60 * 1000);
    
    // 初始检查
    autoUpdater.checkForUpdates();
  }
  
  // 监听更新事件
  autoUpdater.on('checking-for-update', () => {
    log.info('检查更新中...');
  });
  
  autoUpdater.on('update-available', (info) => {
    log.info('发现新版本:', info.version);
    
    dialog.showMessageBox({
      type: 'info',
      title: '更新可用',
      message: `发现新版本 ${info.version}`,
      detail: '是否立即下载并安装更新?',
      buttons: ['稍后', '立即更新'],
      defaultId: 1
    }).then((result) => {
      if (result.response === 1) {
        autoUpdater.downloadUpdate();
      }
    });
  });
  
  autoUpdater.on('update-not-available', () => {
    log.info('当前已是最新版本');
  });
  
  autoUpdater.on('error', (error) => {
    log.error('更新出错:', error);
    
    dialog.showMessageBox({
      type: 'error',
      title: '更新错误',
      message: '检查更新时出错',
      detail: error.message,
      buttons: ['确定']
    });
  });
  
  autoUpdater.on('download-progress', (progressObj) => {
    const percentage = Math.round(progressObj.percent);
    log.info(`下载进度: ${percentage}%`);
    
    // 发送进度到渲染进程
    if (mainWindow && !mainWindow.isDestroyed()) {
      mainWindow.webContents.send('update-progress', {
        percentage,
        transferred: progressObj.transferred,
        total: progressObj.total
      });
    }
  });
  
  autoUpdater.on('update-downloaded', (info) => {
    log.info('更新已下载完成');
    
    dialog.showMessageBox({
      type: 'info',
      title: '更新已下载',
      message: `新版本 ${info.version} 已下载完成`,
      detail: '应用将重启并安装更新',
      buttons: ['立即重启', '稍后重启'],
      defaultId: 0
    }).then((result) => {
      if (result.response === 0) {
        autoUpdater.quitAndInstall();
      }
    });
  });
}

// 手动检查更新
function checkForUpdates() {
  if (process.env.NODE_ENV === 'production') {
    autoUpdater.checkForUpdates();
  }
}

module.exports = {
  setupAutoUpdater,
  checkForUpdates
};

6.3 CI/CD 配置

6.3.1 GitHub Actions

yaml
# .github/workflows/build.yml
name: Build and Release

on:
  push:
    tags: [ 'v*' ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: 20
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Build for Windows
      if: matrix.os == 'windows-latest'
      run: npm run build:win
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
        WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}

    - name: Build for macOS
      if: matrix.os == 'macos-latest'
      run: npm run build:mac
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        APPLE_ID: ${{ secrets.APPLE_ID }}
        APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
        APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

    - name: Build for Linux
      if: matrix.os == 'ubuntu-latest'
      run: npm run build:linux
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

    - name: Upload artifacts
      uses: actions/upload-artifact@v4
      with:
        name: builds-${{ matrix.os }}
        path: dist/

  release:
    needs: build
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')

    steps:
    - uses: actions/checkout@v4

    - name: Download all artifacts
      uses: actions/download-artifact@v4
      with:
        path: dist

    - name: Create GitHub Release
      uses: ncipollo/release-action@v1
      with:
        artifacts: 'dist/**/*'
        token: ${{ secrets.GITHUB_TOKEN }}
        draft: false
        prerelease: false
        generateReleaseNotes: true

🔒 安全最佳实践

安全是Electron应用开发的重要考虑因素,以下是一些关键的安全最佳实践。

7.1 基本安全配置

javascript
// src/main/index.js
const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
  const mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      // 安全配置
      nodeIntegration: false, // 禁用节点集成
      contextIsolation: true, // 启用上下文隔离
      sandbox: true, // 启用沙箱
      webSecurity: true, // 启用Web安全
      allowRunningInsecureContent: false, // 禁用不安全内容
      enableRemoteModule: false, // 禁用远程模块
      preload: path.join(__dirname, '../preload/index.js')
    }
  });
  
  // 其他配置...
}

7.2 Content Security Policy

html
<!-- src/renderer/index.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Electron App</title>
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; frame-src 'none';">
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <!-- 内容 -->
</body>
</html>

7.3 安全的 IPC 通信

javascript
// src/preload/index.js
const { contextBridge, ipcRenderer } = require('electron');

// 暴露安全的 API 到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
  // 文件操作
  openFile: () => ipcRenderer.invoke('file:open'),
  saveFile: (content) => ipcRenderer.invoke('file:save', content),
  
  // 窗口操作
  minimize: () => ipcRenderer.invoke('window:minimize'),
  maximize: () => ipcRenderer.invoke('window:maximize'),
  close: () => ipcRenderer.invoke('window:close'),
  
  // 应用信息
  getAppInfo: () => ipcRenderer.invoke('app:get-info'),
  
  // 消息通信(仅允许特定通道)
  onUpdateAvailable: (callback) => ipcRenderer.on('update:available', (event, ...args) => callback(...args)),
  onUpdateProgress: (callback) => ipcRenderer.on('update:progress', (event, ...args) => callback(...args))
});

7.4 防注入措施

javascript
// src/main/ipc-security.js
const { ipcMain } = require('electron');

// 定义允许的通道
const ALLOWED_CHANNELS = {
  'file:open': true,
  'file:save': true,
  'window:minimize': true,
  'window:maximize': true,
  'window:close': true,
  'app:get-info': true
};

// 验证通道
function validateChannel(channel) {
  return ALLOWED_CHANNELS[channel] === true;
}

// 验证数据
function validateData(data, expectedType) {
  if (expectedType === 'string') {
    return typeof data === 'string';
  } else if (expectedType === 'object') {
    return typeof data === 'object' && data !== null;
  }
  return true;
}

// 安全的 IPC 处理
ipcMain.handle('file:save', (event, content) => {
  if (!validateData(content, 'string')) {
    throw new Error('无效的数据格式');
  }
  
  // 处理保存文件逻辑
  // ...
});

7.5 依赖安全

7.5.1 依赖检查

bash
# 安装依赖检查工具
npm install -g npm-audit

# 检查依赖漏洞
npm audit

# 自动修复漏洞
npm audit fix

# 深度检查
npm audit --production

7.5.2 安全的依赖管理

json
// package.json
{
  "scripts": {
    "audit": "npm audit",
    "audit:fix": "npm audit fix",
    "security": "npm audit && npm outdated"
  }
}

⚡ 性能优化

性能优化是Electron应用开发的重要环节,以下是一些关键的优化策略。

8.1 启动优化

8.1.1 减少启动时间

javascript
// src/main/index.js
const { app, BrowserWindow } = require('electron');

// 禁用硬件加速(如果遇到启动问题)
// app.disableHardwareAcceleration();

// 延迟加载非关键模块
function createWindow() {
  const mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      // 性能配置
      backgroundThrottling: false,
      disableHtmlFullscreenWindowResize: true,
      // 其他配置...
    }
  });
  
  // 加载页面
  mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
  
  // 延迟加载其他功能
  setTimeout(() => {
    require('./tray');
    require('./updater');
  }, 1000);
}

8.2 运行时优化

8.2.1 内存优化

javascript
// 监控内存使用
function monitorMemory() {
  setInterval(() => {
    const memoryUsage = process.memoryUsage();
    console.log('内存使用情况:', {
      rss: (memoryUsage.rss / 1024 / 1024).toFixed(2) + ' MB',
      heapTotal: (memoryUsage.heapTotal / 1024 / 1024).toFixed(2) + ' MB',
      heapUsed: (memoryUsage.heapUsed / 1024 / 1024).toFixed(2) + ' MB',
      external: (memoryUsage.external / 1024 / 1024).toFixed(2) + ' MB'
    });
  }, 5000);
}

// 清理内存
function cleanupMemory() {
  if (global.gc) {
    global.gc();
    console.log('内存已清理');
  }
}

8.2.2 渲染性能

javascript
// src/renderer/performance.js
// 使用 requestAnimationFrame 进行动画
function animate() {
  requestAnimationFrame(animate);
  // 动画逻辑...
}

// 防抖和节流
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

function throttle(func, limit) {
  let inThrottle;
  return function executedFunction(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

8.3 资源优化

8.3.1 打包优化

json
// package.json
{
  "build": {
    "compression": "maximum",
    "asar": true,
    "asarUnpack": [
      "**/*.node",
      "**/*.dll",
      "**/*.so",
      "**/*.dylib"
    ],
    "win": {
      "target": [
        {
          "target": "nsis",
          "arch": ["x64"]
        }
      ]
    }
  }
}

🔍 调试工具

9.1 开发工具

9.1.1 Chrome DevTools

javascript
// src/main/index.js
const { app, BrowserWindow } = require('electron');

function createWindow() {
  const mainWindow = new BrowserWindow({/*...*/});
  
  // 打开开发者工具
  if (process.env.NODE_ENV === 'development') {
    mainWindow.webContents.openDevTools({
      mode: 'detach',
      activate: true
    });
  }
}

9.1.2 主进程调试

bash
# 启动主进程调试
npm run start:debug

# 或直接使用命令
electron --inspect=5858 .

# 然后在 Chrome 中访问
# chrome://inspect/#devices

9.2 日志管理

javascript
// src/main/logger.js
const log = require('electron-log');
const path = require('path');

// 配置日志
log.transports.file.resolvePath = () => path.join(__dirname, '../../logs/main.log');
log.transports.file.level = 'info';
log.transports.console.level = 'info';

// 自定义日志格式
log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}';

// 导出日志实例
module.exports = {
  info: (message) => log.info(message),
  warn: (message) => log.warn(message),
  error: (message) => log.error(message),
  debug: (message) => log.debug(message)
};

🎯 最佳实践

10.1 代码组织

10.1.1 模块化设计

  • 主进程模块:按功能拆分(窗口管理、IPC、托盘等)
  • 渲染进程模块:按页面和功能组织
  • 预加载脚本:仅暴露必要的API
  • 共享模块:提取通用功能到共享目录

10.1.2 命名规范

  • 文件命名:使用kebab-case(如 window-manager.js
  • 类命名:使用PascalCase(如 WindowManager
  • 函数命名:使用camelCase(如 createWindow
  • 常量命名:使用UPPER_SNAKE_CASE(如 MAX_WINDOW_WIDTH

10.2 开发流程

10.2.1 开发环境

  • 热重载:使用 electron-reload 或 Vite
  • 代码格式化:使用 Prettier
  • 代码检查:使用 ESLint
  • 类型检查:使用 TypeScript(可选)

10.2.2 版本管理

  • 语义化版本:遵循 MAJOR.MINOR.PATCH
  • Git 规范:使用 Conventional Commits
  • 发布流程:自动化构建和发布

10.3 跨平台兼容性

10.3.1 平台差异处理

javascript
// src/main/platform.js
const os = require('os');
const isMac = process.platform === 'darwin';
const isWindows = process.platform === 'win32';
const isLinux = process.platform === 'linux';

function getPlatformSpecificPath() {
  if (isMac) {
    return path.join(os.homedir(), 'Library', 'Application Support', 'My App');
  } else if (isWindows) {
    return path.join(process.env.APPDATA, 'My App');
  } else {
    return path.join(os.homedir(), '.my-app');
  }
}

module.exports = {
  isMac,
  isWindows,
  isLinux,
  getPlatformSpecificPath
};

❓ 常见问题

11.1 开发问题

11.1.1 白屏问题

  • 原因:页面加载失败、渲染进程崩溃、依赖错误
  • 解决方案
    1. 检查控制台错误
    2. 确保预加载脚本正确
    3. 检查 CSP 配置
    4. 验证依赖安装

11.1.2 HMR 不工作

  • 原因:配置错误、端口被占用
  • 解决方案
    1. 检查开发服务器配置
    2. 确保端口未被占用
    3. 验证文件监听配置

11.2 打包问题

11.2.1 打包失败

  • 原因:依赖错误、配置错误、签名问题
  • 解决方案
    1. 检查依赖安装
    2. 验证 electron-builder 配置
    3. 检查签名证书
    4. 查看详细错误日志

11.2.2 应用启动失败

  • 原因:缺少依赖、权限问题、路径错误
  • 解决方案
    1. 检查 asar 打包配置
    2. 确保依赖正确打包
    3. 验证文件权限
    4. 检查日志文件

11.3 安全问题

11.3.1 安全警告

  • 原因:不安全的配置、过时的依赖
  • 解决方案
    1. 启用所有安全配置
    2. 更新依赖到最新版本
    3. 定期运行安全扫描

📝 总结

Electron 是一个强大的框架,允许开发者使用 Web 技术构建跨平台桌面应用。通过本文的学习,您应该掌握了:

  1. 核心概念:主进程/渲染进程架构、IPC 通信、原生功能集成
  2. 开发流程:环境搭建、项目结构、调试技巧
  3. 高级特性:系统集成、打包发布、自动更新
  4. 最佳实践:安全配置、性能优化、跨平台兼容性

12.1 关键要点

  • 安全第一:始终启用安全配置,避免使用不安全的 API
  • 性能优化:关注启动时间、内存使用和渲染性能
  • 用户体验:利用原生功能提升应用体验
  • 跨平台兼容:处理平台差异,确保多平台正常运行
  • 持续更新:保持依赖和 Electron 版本更新

12.2 未来发展

Electron 生态系统持续发展,未来的趋势包括:

  • 更好的性能:持续优化启动时间和内存使用
  • 更安全:加强默认安全配置
  • 更集成:与原生系统更深度集成
  • 更便捷:简化开发和打包流程

📚 参考资源

13.1 官方文档

13.2 学习资源

13.3 工具和库

13.4 社区资源

13.5 相关文章

// 复制文件路径到剪贴板 function copyFiles(filePaths) { clipboard.writeBuffer('public.file-urls', Buffer.from(filePaths.join('\n'))); return true; }

// 从剪贴板读取文件路径 function getFiles() { const buffer = clipboard.readBuffer('public.file-urls'); if (buffer) { return buffer.toString().split('\n').filter(Boolean); } return []; }

// 检查剪贴板内容类型 function getClipboardType() { if (clipboard.hasImage()) { return 'image'; } else if (clipboard.readHTML() !== '') { return 'html'; } else if (clipboard.readText() !== '') { return 'text'; } else if (getFiles().length > 0) { return 'files'; } else { return 'empty'; } }

// 清空剪贴板 function clearClipboard() { clipboard.clear(); return getClipboardType() === 'empty'; }

// 示例:复制代码 function copyCode(code, language = 'javascript') { // 复制纯文本 const success = copyText(code);

// 同时复制 HTML 格式(带语法高亮) const html = <pre><code class="language-${language}">${code}</code></pre>; copyHTML(html, code);

return success; }

// 示例:复制文件路径 function copyFilePath(filePath) { return copyText(filePath); }

module.exports = { copyText, getText, copyHTML, getHTML, copyImage, getImage, copyFiles, getFiles, getClipboardType, clearClipboard, copyCode, copyFilePath };


### 5.6 系统 Shell

系统 Shell 用于执行系统级别的操作,如打开文件、文件夹和 URL。

#### 5.6.1 基本 Shell 操作

```javascript
// src/main/shell.js
const { shell } = require('electron');
const fs = require('fs').promises;
const path = require('path');

// 打开文件
async function openFile(filePath) {
  try {
    // 检查文件是否存在
    await fs.access(filePath);
    await shell.openPath(filePath);
    return true;
  } catch (error) {
    console.error('打开文件失败:', error);
    return false;
  }
}

// 打开文件夹
async function openFolder(folderPath) {
  try {
    // 检查文件夹是否存在
    const stats = await fs.stat(folderPath);
    if (!stats.isDirectory()) {
      throw new Error('路径不是文件夹');
    }
    await shell.openPath(folderPath);
    return true;
  } catch (error) {
    console.error('打开文件夹失败:', error);
    return false;
  }
}

// 显示文件在文件夹中
async function showFileInFolder(filePath) {
  try {
    // 检查文件是否存在
    await fs.access(filePath);
    shell.showItemInFolder(filePath);
    return true;
  } catch (error) {
    console.error('显示文件失败:', error);
    return false;
  }
}

// 打开 URL
async function openURL(url) {
  try {
    await shell.openExternal(url);
    return true;
  } catch (error) {
    console.error('打开 URL 失败:', error);
    return false;
  }
}

// 打开邮箱
async function openEmail(email, subject = '', body = '') {
  const mailtoUrl = `mailto:${email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
  return await openURL(mailtoUrl);
}

// 打开搜索
async function openSearch(query, engine = 'google') {
  const searchUrls = {
    google: `https://www.google.com/search?q=${encodeURIComponent(query)}`,
    baidu: `https://www.baidu.com/s?wd=${encodeURIComponent(query)}`,
    bing: `https://www.bing.com/search?q=${encodeURIComponent(query)}`,
    duckduckgo: `https://duckduckgo.com/?q=${encodeURIComponent(query)}`
  };
  
  const url = searchUrls[engine] || searchUrls.google;
  return await openURL(url);
}

// 移动文件到回收站
async function moveToTrash(filePath) {
  try {
    await shell.trashItem(filePath);
    return true;
  } catch (error) {
    console.error('移动到回收站失败:', error);
    return false;
  }
}

// 检查是否可以处理协议
function canHandleProtocol(protocol) {
  return shell.hasDefaultProtocolClient(protocol);
}

// 设置默认协议客户端
function setDefaultProtocolClient(protocol, path, args) {
  return shell.setAsDefaultProtocolClient(protocol, path, args);
}

// 取消默认协议客户端
function unsetDefaultProtocolClient(protocol) {
  return shell.unsetAsDefaultProtocolClient(protocol);
}

// 示例:打开项目文档
async function openDocumentation() {
  const docPath = path.join(__dirname, '../../docs/index.html');
  return await openFile(docPath);
}

// 示例:打开 GitHub 仓库
async function openGitHub() {
  return await openURL('https://github.com/yourusername/yourapp');
}

// 示例:联系支持
async function contactSupport() {
  return await openEmail('support@example.com', '应用支持', '请描述您遇到的问题:\n\n');
}

module.exports = {
  openFile,
  openFolder,
  showFileInFolder,
  openURL,
  openEmail,
  openSearch,
  moveToTrash,
  canHandleProtocol,
  setDefaultProtocolClient,
  unsetDefaultProtocolClient,
  openDocumentation,
  openGitHub,
  contactSupport
};

5.7 原生拖放

原生拖放功能允许用户在应用和系统之间拖放文件和数据。

5.7.1 基本拖放实现

javascript
// src/main/drag-and-drop.js
const { ipcMain } = require('electron');

// 注册拖放事件
function setupDragAndDrop(mainWindow) {
  // 监听渲染进程的拖放事件
  ipcMain.on('file-dropped', (event, files) => {
    console.log('收到拖放的文件:', files);
    // 处理拖放的文件
    handleDroppedFiles(files);
  });
  
  // 监听渲染进程的拖动开始事件
  ipcMain.on('drag-start', (event, data) => {
    console.log('开始拖动:', data);
    // 处理拖动开始
  });
}

// 处理拖放的文件
async function handleDroppedFiles(files) {
  for (const file of files) {
    console.log('处理文件:', file);
    // 根据文件类型进行不同处理
    if (file.endsWith('.txt')) {
      await handleTextFile(file);
    } else if (file.endsWith('.json')) {
      await handleJsonFile(file);
    } else if (file.endsWith('.png') || file.endsWith('.jpg') || file.endsWith('.jpeg')) {
      await handleImageFile(file);
    } else {
      await handleUnknownFile(file);
    }
  }
}

// 处理文本文件
async function handleTextFile(filePath) {
  try {
    const fs = require('fs').promises;
    const content = await fs.readFile(filePath, 'utf8');
    console.log('文本文件内容:', content);
    // 可以发送到渲染进程显示
  } catch (error) {
    console.error('读取文本文件失败:', error);
  }
}

// 处理 JSON 文件
async function handleJsonFile(filePath) {
  try {
    const fs = require('fs').promises;
    const content = await fs.readFile(filePath, 'utf8');
    const data = JSON.parse(content);
    console.log('JSON 文件内容:', data);
    // 可以发送到渲染进程显示
  } catch (error) {
    console.error('读取 JSON 文件失败:', error);
  }
}

// 处理图像文件
async function handleImageFile(filePath) {
  try {
    console.log('图像文件:', filePath);
    // 可以发送到渲染进程显示
  } catch (error) {
    console.error('处理图像文件失败:', error);
  }
}

// 处理未知类型文件
async function handleUnknownFile(filePath) {
  console.log('未知类型文件:', filePath);
  // 可以显示文件信息
}

module.exports = {
  setupDragAndDrop,
  handleDroppedFiles
};
javascript
// src/preload/drag-and-drop.js
const { contextBridge, ipcRenderer } = require('electron');

// 暴露拖放 API
contextBridge.exposeInMainWorld('dragAndDrop', {
  // 发送拖放文件事件
  sendDroppedFiles: (files) => {
    ipcRenderer.send('file-dropped', files);
  },
  
  // 发送拖动开始事件
  sendDragStart: (data) => {
    ipcRenderer.send('drag-start', data);
  }
});
javascript
// src/renderer/drag-and-drop.js

// 初始化拖放
function initializeDragAndDrop() {
  setupDragAndDropListeners();
  setupDragStartListeners();
}

// 设置拖放监听器
function setupDragAndDropListeners() {
  const dropZone = document.getElementById('drop-zone');
  if (!dropZone) return;
  
  // 拖动进入
  dropZone.addEventListener('dragenter', (e) => {
    e.preventDefault();
    e.stopPropagation();
    dropZone.classList.add('drag-over');
  });
  
  // 拖动离开
  dropZone.addEventListener('dragleave', (e) => {
    e.preventDefault();
    e.stopPropagation();
    dropZone.classList.remove('drag-over');
  });
  
  // 拖动悬停
  dropZone.addEventListener('dragover', (e) => {
    e.preventDefault();
    e.stopPropagation();
  });
  
  // 放置
  dropZone.addEventListener('drop', (e) => {
    e.preventDefault();
    e.stopPropagation();
    dropZone.classList.remove('drag-over');
    
    // 获取拖放的文件
    const files = Array.from(e.dataTransfer.files).map(file => file.path);
    if (files.length > 0) {
      // 发送到主进程
      window.dragAndDrop.sendDroppedFiles(files);
      
      // 显示拖放信息
      showDropInfo(files);
    }
  });
}

// 设置拖动开始监听器
function setupDragStartListeners() {
  const draggableItems = document.querySelectorAll('.draggable');
  draggableItems.forEach(item => {
    item.setAttribute('draggable', 'true');
    
    item.addEventListener('dragstart', (e) => {
      e.dataTransfer.setData('text/plain', item.textContent);
      e.dataTransfer.effectAllowed = 'copy';
      
      // 发送到主进程
      window.dragAndDrop.sendDragStart({
        text: item.textContent,
        id: item.id
      });
    });
  });
}

// 显示拖放信息
function showDropInfo(files) {
  const dropInfo = document.getElementById('drop-info');
  if (!dropInfo) return;
  
  dropInfo.innerHTML = `
    <h3>拖放的文件:</h3>
    <ul>
      ${files.map(file => `<li>${file}</li>`).join('')}
    </ul>
  `;
  
  // 3秒后隐藏
  setTimeout(() => {
    dropInfo.innerHTML = '';
  }, 3000);
}

// 初始化
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', initializeDragAndDrop);
} else {
  initializeDragAndDrop();
}

5.8 原生功能最佳实践

5.8.1 性能优化

  1. 延迟加载:只在需要时加载原生模块
  2. 缓存结果:缓存频繁使用的系统信息
  3. 批量操作:批量处理文件和系统操作
  4. 异步处理:使用异步 API 避免阻塞主线程
  5. 错误处理:妥善处理原生 API 可能的错误

5.8.2 跨平台兼容性

  1. 平台检测:使用 process.platform 检测平台
  2. 条件代码:为不同平台提供不同实现
  3. 测试:在所有目标平台上测试应用
  4. 文档:记录平台特定的行为

5.8.3 用户体验

  1. 响应速度:保持 UI 响应,避免长时间阻塞
  2. 反馈:为用户操作提供明确的反馈
  3. 一致性:遵循平台的设计规范
  4. 可访问性:确保应用对所有用户可用
  5. 隐私:尊重用户隐私,合理使用系统权限

📦 打包发布

打包发布是 Electron 应用开发的重要环节,涉及到应用的构建、签名、分发和更新。

6.1 打包配置

6.1.1 基本配置

json
// package.json
{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "Electron 应用",
  "main": "src/main/index.js",
  "scripts": {
    "start": "electron .",
    "dev": "electron .",
    "build": "electron-builder",
    "build:win": "electron-builder --win",
    "build:mac": "electron-builder --mac",
    "build:linux": "electron-builder --linux",
    "build:all": "electron-builder --win --mac --linux",
    "build:win32": "electron-builder --win --ia32",
    "build:win64": "electron-builder --win --x64",
    "build:mac-arm": "electron-builder --mac --arm64",
    "build:mac-intel": "electron-builder --mac --x64",
    "release": "electron-builder --publish always"
  },
  "build": {
    "appId": "com.example.myapp",
    "productName": "My Electron App",
    "copyright": "Copyright © 2024 ${author}",
    "version": "${version}",
    "directories": {
      "output": "dist",
      "buildResources": "src/assets",
      "app": "."
    },
    "files": [
      "src/**/*",
      "package.json",
      "!node_modules/**/*",
      "!*.md",
      "!.gitignore",
      "!.eslintrc.js",
      "!.prettierrc"
    ],
    "extraResources": [
      {
        "from": "resources/",
        "to": "resources/"
      }
    ],
    "extraFiles": [
      {
        "from": "config/",
        "to": "config/"
      }
    ],
    "asar": true,
    "asarUnpack": [
      "**/*.node",
      "**/*.dll",
      "**/*.so",
      "**/*.dylib"
    ],
    "protocols": [
      {
        "name": "My App Protocol",
        "schemes": ["myapp"]
      }
    ],
    "win": {
      "target": [
        {
          "target": "nsis",
          "arch": ["x64", "ia32"]
        },
        {
          "target": "portable",
          "arch": ["x64"]
        }
      ],
      "icon": "src/assets/icon.ico",
      "publisherName": "Your Company Name",
      "signingHashAlgorithms": ["sha256"],
      "verifyUpdateCodeSignature": true,
      "rfc3161TimeStampServer": "http://timestamp.digicert.com",
      "legalTrademarks": "Your Trademark",
      "fileAssociations": [
        {
          "ext": "txt",
          "name": "Text File",
          "icon": "src/assets/file-icons/txt.ico"
        }
      ]
    },
    "mac": {
      "target": [
        {
          "target": "dmg",
          "arch": ["x64", "arm64"]
        },
        {
          "target": "pkg",
          "arch": ["x64", "arm64"]
        },
        {
          "target": "zip",
          "arch": ["x64", "arm64"]
        }
      ],
      "icon": "src/assets/icon.icns",
      "category": "public.app-category.utilities",
      "bundleVersion": "1.0.0",
      "minimumSystemVersion": "10.15.0",
      "hardenedRuntime": true,
      "gatekeeperAssess": false,
      "entitlements": "build/entitlements.mac.plist",
      "entitlementsInherit": "build/entitlements.mac.inherit.plist",
      "provisioningProfile": "build/profile.provisionprofile"
    },
    "linux": {
      "target": [
        "AppImage",
        "deb",
        "rpm",
        "tar.gz"
      ],
      "icon": "src/assets",
      "category": "Utility",
      "executableName": "my-electron-app",
      "desktop": {
        "Name": "My Electron App",
        "Comment": "An Electron application",
        "Categories": "Utility;Application;"
      }
    },
    "nsis": {
      "oneClick": false,
      "allowToChangeInstallationDirectory": true,
      "createDesktopShortcut": true,
      "createStartMenuShortcut": true,
      "shortcutName": "My Electron App",
      "installerIcon": "src/assets/installer.ico",
      "uninstallerIcon": "src/assets/uninstaller.ico",
      "installerHeaderIcon": "src/assets/installer-header.ico",
      "include": "build/nsis/include.nsh",
      "script": "build/nsis/installer.nsh",
      "deleteAppDataOnUninstall": false,
      "runAfterFinish": true
    },
    "dmg": {
      "background": "build/dmg/background.png",
      "icon": "src/assets/dmg-icon.icns",
      "iconSize": 80,
      "contents": [
        {
          "x": 130,
          "y": 220
        },
        {
          "x": 410,
          "y": 220,
          "type": "link",
          "path": "/Applications"
        }
      ],
      "window": {
        "width": 540,
        "height": 380
      }
    },
    "appImage": {
      "license": "LICENSE",
      "category": "Utility"
    },
    "deb": {
      "depends": ["gconf2", "gconf-service", "libnotify4", "libappindicator1", "libxtst6", "libnss3"],
      "maintainer": "Your Name <your@email.com>",
      "section": "utils",
      "priority": "optional"
    },
    "rpm": {
      "depends": ["gconf2", "gconf-service", "libnotify", "libappindicator", "libXtst", "nss"],
      "license": "MIT",
      "group": "Applications/Utilities"
    },
    "publish": [
      {
        "provider": "github",
        "owner": "yourusername",
        "repo": "yourapp",
        "releaseType": "release",
        "draft": false,
        "prerelease": false,
        "token": "${GH_TOKEN}"
      },
      {
        "provider": "s3",
        "bucket": "your-bucket",
        "region": "us-east-1",
        "path": "releases"
      }
    ]
  },
  "devDependencies": {
    "electron": "^28.0.0",
    "electron-builder": "^24.0.0",
    "electron-updater": "^6.0.0"
  }
}

6.1.2 高级配置

javascript
// electron-builder.config.js
module.exports = {
  appId: 'com.example.myapp',
  productName: 'My Electron App',
  copyright: 'Copyright © 2024 ${author}',
  version: '${version}',
  directories: {
    output: 'dist',
    buildResources: 'src/assets',
    app: '.'
  },
  files: [
    'src/**/*',
    'package.json',
    '!node_modules/**/*',
    '!*.md',
    '!.gitignore',
    '!.eslintrc.js',
    '!.prettierrc'
  ],
  extraResources: [
    {
      from: 'resources/',
      to: 'resources/'
    }
  ],
  asar: true,
  asarUnpack: [
    '**/*.node',
    '**/*.dll',
    '**/*.so',
    '**/*.dylib'
  ],
  win: {
    target: ['nsis', 'portable'],
    icon: 'src/assets/icon.ico',
    publisherName: 'Your Company Name'
  },
  mac: {
    target: ['dmg', 'pkg', 'zip'],
    icon: 'src/assets/icon.icns',
    category: 'public.app-category.utilities'
  },
  linux: {
    target: ['AppImage', 'deb', 'rpm', 'tar.gz'],
    icon: 'src/assets',
    category: 'Utility'
  },
  publish: [
    {
      provider: 'github',
      owner: 'yourusername',
      repo: 'yourapp'
    }
  ]
};

6.2 多平台构建

6.2.1 构建命令

bash
# 构建所有平台
npm run build:all

# 构建 Windows 32 位
npm run build:win32

# 构建 Windows 64 位
npm run build:win64

# 构建 macOS ARM
npm run build:mac-arm

# 构建 macOS Intel
npm run build:mac-intel

# 构建 Linux
npm run build:linux

# 发布应用
npm run release

6.2.2 CI/CD 配置

yaml
# .github/workflows/build.yml
name: Build and Release

on:
  push:
    tags:
      - 'v*.*.*'
  workflow_dispatch:

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [windows-latest, ubuntu-latest, macos-latest]

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm install

      - name: Build application
        run: npm run build
        env:
          GH_TOKEN: ${{ secrets.GH_TOKEN }}

      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        with:
          name: ${{ matrix.os }}-build
          path: dist/

  release:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Download all artifacts
        uses: actions/download-artifact@v3
        with:
          path: dist/

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          files: dist/**/*
          draft: false
          prerelease: false
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

6.3 自动更新

6.3.1 基本更新

javascript
// src/main/updater.js
const { app, autoUpdater, dialog } = require('electron-updater');
const { ipcMain } = require('electron');

class AppUpdater {
  constructor() {
    this.mainWindow = null;
    this.isChecking = false;
  }

  // 初始化更新器
  initialize(mainWindow) {
    this.mainWindow = mainWindow;
    this.setupAutoUpdater();
    this.setupIPC();
  }

  // 设置自动更新器
  setupAutoUpdater() {
    // 配置更新源
    autoUpdater.setFeedURL({
      provider: 'github',
      owner: 'yourusername',
      repo: 'yourapp',
      private: false
    });

    // 配置更新选项
    autoUpdater.autoDownload = false;
    autoUpdater.autoInstallOnAppQuit = true;
    autoUpdater.allowPrerelease = false;

    // 监听更新事件
    this.setupEventListeners();
  }

  // 设置事件监听器
  setupEventListeners() {
    // 检查更新开始
    autoUpdater.on('checking-for-update', () => {
      console.log('检查更新中...');
      this.sendStatus('checking');
    });

    // 发现新版本
    autoUpdater.on('update-available', (info) => {
      console.log('发现新版本:', info.version);
      this.sendStatus('available', info);
    });

    // 无新版本
    autoUpdater.on('update-not-available', () => {
      console.log('当前已是最新版本');
      this.sendStatus('not-available');
    });

    // 更新错误
    autoUpdater.on('error', (error) => {
      console.error('更新出错:', error);
      this.sendStatus('error', error);
    });

    // 下载进度
    autoUpdater.on('download-progress', (progress) => {
      console.log('下载进度:', Math.round(progress.percent));
      this.sendStatus('downloading', progress);
    });

    // 下载完成
    autoUpdater.on('update-downloaded', (info) => {
      console.log('更新下载完成:', info.version);
      this.sendStatus('downloaded', info);
      this.showUpdateDialog();
    });
  }

  // 设置 IPC 通信
  setupIPC() {
    // 检查更新
    ipcMain.handle('check-for-updates', async () => {
      if (this.isChecking) {
        return { status: 'checking' };
      }

      this.isChecking = true;
      try {
        await autoUpdater.checkForUpdates();
        return { status: 'success' };
      } catch (error) {
        console.error('检查更新失败:', error);
        return { status: 'error', error: error.message };
      } finally {
        this.isChecking = false;
      }
    });

    // 下载更新
    ipcMain.handle('download-update', async () => {
      try {
        await autoUpdater.downloadUpdate();
        return { status: 'success' };
      } catch (error) {
        console.error('下载更新失败:', error);
        return { status: 'error', error: error.message };
      }
    });

    // 安装更新
    ipcMain.handle('install-update', async () => {
      try {
        autoUpdater.quitAndInstall();
        return { status: 'success' };
      } catch (error) {
        console.error('安装更新失败:', error);
        return { status: 'error', error: error.message };
      }
    });

    // 取消更新
    ipcMain.handle('cancel-update', async () => {
      try {
        autoUpdater.cancel();
        return { status: 'success' };
      } catch (error) {
        console.error('取消更新失败:', error);
        return { status: 'error', error: error.message };
      }
    });
  }

  // 发送状态到渲染进程
  sendStatus(status, data = null) {
    if (this.mainWindow && !this.mainWindow.isDestroyed()) {
      this.mainWindow.webContents.send('update-status', { status, data });
    }
  }

  // 显示更新对话框
  showUpdateDialog() {
    dialog.showMessageBox({
      title: '应用更新',
      message: '发现新版本',
      detail: '更新已下载完成,是否立即安装并重启应用?',
      buttons: ['稍后', '立即安装'],
      defaultId: 1,
      cancelId: 0,
      type: 'info'
    }).then((result) => {
      if (result.response === 1) {
        autoUpdater.quitAndInstall();
      }
    });
  }

  // 手动检查更新
  checkForUpdates() {
    return autoUpdater.checkForUpdates();
  }
}

// 导出单例
module.exports = new AppUpdater();

6.3.2 高级更新策略

javascript
// src/main/advanced-updater.js
const { app, autoUpdater, dialog, Notification } = require('electron');
const { ipcMain } = require('electron');
const path = require('path');

class AdvancedUpdater extends AppUpdater {
  constructor() {
    super();
    this.updateInterval = null;
  }

  // 初始化
  initialize(mainWindow) {
    super.initialize(mainWindow);
    this.setupAutoCheck();
  }

  // 设置自动检查
  setupAutoCheck() {
    // 应用启动时检查更新
    this.checkForUpdates();

    // 每小时自动检查一次
    this.updateInterval = setInterval(() => {
      this.checkForUpdates();
    }, 3600000);
  }

  // 检查更新
  async checkForUpdates() {
    try {
      console.log('自动检查更新');
      await super.checkForUpdates();
    } catch (error) {
      console.error('自动检查更新失败:', error);
    }
  }

  // 显示更新通知
  showUpdateNotification(info) {
    const notification = new Notification({
      title: '应用更新',
      body: `发现新版本 ${info.version},是否立即更新?`,
      icon: path.join(__dirname, '../assets/icon.png')
    });

    notification.on('click', () => {
      this.showUpdateDialog();
    });

    notification.show();
  }

  // 处理更新可用
  handleUpdateAvailable(info) {
    console.log('处理更新可用:', info);
    this.sendStatus('available', info);
    
    // 显示通知
    this.showUpdateNotification(info);
  }

  // 清理资源
  cleanup() {
    if (this.updateInterval) {
      clearInterval(this.updateInterval);
      this.updateInterval = null;
    }
  }
}

// 导出单例
module.exports = new AdvancedUpdater();

6.4 应用签名

6.4.1 Windows 签名

bash
# 安装签名工具
npm install --save-dev electron-windows-sign

# 配置签名环境变量
# 设置证书密码
set CSC_LINK=path/to/certificate.pfx
set CSC_KEY_PASSWORD=your-certificate-password

# 构建并签名
npm run build:win

6.4.2 macOS 签名

bash
# 配置签名环境变量
# 设置开发者 ID
export CSC_NAME="Developer ID Application: Your Company (TEAMID)"

# 构建并签名
npm run build:mac

# 验证签名
codesign --verify --deep --strict --verbose=2 dist/mac/My\ Electron\ App.app

# 验证 Notarization
spctl --assess --type execute --verbose=2 dist/mac/My\ Electron\ App.app

6.4.3 Notarization (macOS)

javascript
// electron-builder.config.js
module.exports = {
  // ... 其他配置
  mac: {
    // ... 其他配置
    hardenedRuntime: true,
    gatekeeperAssess: false,
    entitlements: 'build/entitlements.mac.plist',
    entitlementsInherit: 'build/entitlements.mac.inherit.plist',
    notarize: {
      teamId: 'YOUR_TEAM_ID'
    }
  }
};
xml
<!-- build/entitlements.mac.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
    <key>com.apple.security.network.server</key>
    <true/>
    <key>com.apple.security.automation.apple-events</key>
    <true/>
  </dict>
</plist>

6.5 发布到应用商店

6.5.1 Microsoft Store

json
// package.json
{
  "build": {
    // ... 其他配置
    "win": {
      // ... 其他配置
      "target": [
        {
          "target": "nsis",
          "arch": ["x64", "ia32"]
        },
        {
          "target": "msstore",
          "arch": ["x64"]
        }
      ]
    },
    "msstore": {
      "applicationId": "YourAppId",
      "publisher": "CN=YourPublisherId"
    }
  }
}

6.5.2 Mac App Store

json
// package.json
{
  "build": {
    // ... 其他配置
    "mac": {
      // ... 其他配置
      "target": [
        {
          "target": "dmg",
          "arch": ["x64", "arm64"]
        },
        {
          "target": "mas",
          "arch": ["x64", "arm64"]
        }
      ]
    },
    "mas": {
      "identity": "3rd Party Mac Developer Application: Your Company (TEAMID)",
      "provisioningProfile": "build/mas.provisionprofile"
    }
  }
}

6.6 部署策略

6.6.1 手动部署

  1. 构建应用:使用 npm run build 构建应用
  2. 测试应用:在目标平台上测试构建产物
  3. 上传构建产物:上传到 GitHub Releases、S3 等
  4. 发布更新:通知用户更新

6.6.2 自动部署

  1. 配置 CI/CD:使用 GitHub Actions、Jenkins 等
  2. 自动构建:代码推送时自动构建
  3. 自动测试:构建后自动运行测试
  4. 自动发布:测试通过后自动发布

6.6.3 企业部署

  1. 内部应用商店:搭建内部应用分发系统
  2. 静默安装:配置静默安装脚本
  3. 组策略部署:使用 Windows 组策略部署
  4. MDM 集成:集成移动设备管理系统

6.7 打包最佳实践

6.7.1 性能优化

  1. 减少应用体积

    • 移除未使用的依赖
    • 使用 tree-shaking
    • 压缩资源文件
    • 合理配置 files 字段
  2. 优化启动速度

    • 延迟加载非关键模块
    • 预加载关键资源
    • 优化主进程代码
  3. 减少内存使用

    • 合理管理资源
    • 及时释放不需要的资源
    • 使用 asar 压缩

6.7.2 安全性

  1. 代码签名

    • 对应用进行签名
    • 验证更新包签名
  2. 安全配置

    • 使用 HTTPS 分发更新
    • 配置合理的内容安全策略
    • 保护敏感信息
  3. 漏洞修复

    • 及时更新依赖
    • 定期安全扫描
    • 修复已知漏洞

6.7.3 用户体验

  1. 安装体验

    • 提供简洁的安装向导
    • 支持自定义安装路径
    • 提供卸载选项
  2. 更新体验

    • 提供明确的更新提示
    • 支持自动更新
    • 提供手动更新选项
  3. 错误处理

    • 妥善处理安装错误
    • 提供清晰的错误信息
    • 支持回滚机制

6.8 常见打包问题

6.8.1 构建失败

  • 问题:构建过程中出现错误
  • 解决方案
    1. 检查依赖是否正确安装
    2. 检查构建配置是否正确
    3. 检查证书配置是否正确
    4. 查看详细的错误日志

6.8.2 应用无法启动

  • 问题:构建后的应用无法启动
  • 解决方案
    1. 检查主进程入口文件是否正确
    2. 检查依赖是否完整
    3. 检查权限是否正确
    4. 查看应用日志

6.8.3 更新失败

  • 问题:自动更新失败
  • 解决方案
    1. 检查更新源配置是否正确
    2. 检查网络连接是否正常
    3. 检查签名是否正确
    4. 查看更新日志

6.8.4 签名问题

  • 问题:应用签名失败
  • 解决方案
    1. 检查证书是否有效
    2. 检查证书配置是否正确
    3. 检查网络连接是否正常
    4. 查看签名工具日志

6.8.5 平台兼容性

  • 问题:应用在某些平台上不兼容
  • 解决方案
    1. 为不同平台提供不同的构建配置
    2. 测试所有目标平台
    3. 使用平台特定的代码
    4. 提供平台特定的资源

安全最佳实践

1. 上下文隔离

javascript
// src/main/index.js
function createWindow() {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, '../preload/index.js')
    }
  });
}

2. CSP 配置

html
<!-- src/renderer/index.html -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';">

3. 安全通信

javascript
// src/preload/index.js
contextBridge.exposeInMainWorld('electronAPI', {
  // 只暴露必要的 API
  sendMessage: (channel, data) => {
    // 白名单通道
    const validChannels = ['toMain', 'toRenderer'];
    if (validChannels.includes(channel)) {
      ipcRenderer.send(channel, data);
    }
  },
  
  // 验证数据
  validateData: (data) => {
    // 实现数据验证逻辑
    return typeof data === 'object' && data !== null;
  }
});

性能优化

1. 内存管理

javascript
// src/main/index.js
function createWindow() {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      // 启用内存优化
      backgroundThrottling: false,
      // 禁用远程模块
      enableRemoteModule: false
    }
  });
  
  // 监听内存警告
  app.on('render-process-gone', (event, webContents, details) => {
    console.log('渲染进程崩溃:', details);
  });
  
  // 定期清理内存
  setInterval(() => {
    if (mainWindow && !mainWindow.isDestroyed()) {
      mainWindow.webContents.session.clearCache();
    }
  }, 3600000); // 每小时清理一次
}

2. 启动优化

javascript
// src/main/index.js
app.whenReady().then(() => {
  // 延迟加载非关键模块
  setTimeout(() => {
    require('./background-tasks');
  }, 1000);
  
  // 使用 ready-to-show 事件
  const mainWindow = new BrowserWindow({
    show: false
  });
  
  mainWindow.once('ready-to-show', () => {
    mainWindow.show();
  });
});

调试工具

1. 开发工具

javascript
// src/main/index.js
function createWindow() {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      devTools: process.env.NODE_ENV === 'development'
    }
  });
  
  if (process.env.NODE_ENV === 'development') {
    mainWindow.webContents.openDevTools();
  }
}

2. 日志系统

javascript
// src/main/logger.js
const { app } = require('electron');
const path = require('path');
const fs = require('fs');

class Logger {
  constructor() {
    this.logPath = path.join(app.getPath('userData'), 'logs');
    this.ensureLogDirectory();
  }
  
  ensureLogDirectory() {
    if (!fs.existsSync(this.logPath)) {
      fs.mkdirSync(this.logPath);
    }
  }
  
  log(message, level = 'info') {
    const timestamp = new Date().toISOString();
    const logMessage = `[${timestamp}] [${level}] ${message}\n`;
    
    const logFile = path.join(this.logPath, `${level}.log`);
    fs.appendFileSync(logFile, logMessage);
    
    if (level === 'error') {
      console.error(logMessage);
    } else {
      console.log(logMessage);
    }
  }
  
  error(message) {
    this.log(message, 'error');
  }
  
  warn(message) {
    this.log(message, 'warn');
  }
  
  info(message) {
    this.log(message, 'info');
  }
}

module.exports = new Logger();

最佳实践

1. 项目结构

bash
src/
  ├── main/              # 主进程代码
   ├── index.js       # 主进程入口
   ├── ipc.js         # IPC 通信
   ├── dialogs.js     # 系统对话框
   ├── tray.js        # 系统托盘
   └── updater.js     # 自动更新
  ├── renderer/          # 渲染进程代码
   ├── index.html     # 主页面
   ├── renderer.js    # 渲染进程逻辑
   └── styles.css     # 样式文件
  ├── preload/           # 预加载脚本
   └── index.js       # 预加载脚本
  └── assets/            # 静态资源
      ├── icon.ico       # Windows 图标
      ├── icon.icns      # macOS 图标
      └── icon.png       # Linux 图标

2. 代码规范

javascript
// .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
    browser: true
  },
  extends: [
    'eslint:recommended'
  ],
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
  }
};

// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5"
}

总结

Electron 提供了强大的跨平台桌面应用开发能力,通过合理使用框架特性和遵循最佳实践,可以构建出安全、高性能的桌面应用。

参考资源