## Electron 桌面框架使用说明

---

## 一、什么是 Electron？

**Electron** = Chromium + Node.js + 原生 API

- 用 **HTML/CSS/JavaScript** 构建跨平台桌面应用
- 一套代码打包出 **Windows + macOS + Linux** 三个平台
- 代表产品：VS Code、Slack、Discord、Figma、Notion、钉钉

---

## 二、核心架构

```
┌─────────────────────────────────────┐
│           Main Process              │ ← Node.js 环境
│   ┌─────────────┐   ┌───────────┐  │
│   │  BrowserWindow │   │ 系统菜单  │  │
│   │  创建/管理窗口  │   │ 托盘/通知 │  │
│   └──────┬──────┘   └───────────┘  │
└──────────┼──────────────────────────┘
           │ IPC (进程间通信)
┌──────────┼──────────────────────────┐
│   Renderer Process        │          │ ← Chromium 环境
│   ┌──────┴──────┐                   │
│   │  index.html  │  ← 你的 UI       │
│   │  renderer.js │  ← 你的逻辑       │
│   └─────────────┘                   │
└─────────────────────────────────────┘
```

- **主进程 (Main)**：Node.js 环境，管理系统窗口、菜单、原生操作
- **渲染进程 (Renderer)**：Chromium 环境，跑网页 UI
- **IPC**：两者通过 `ipcMain` / `ipcRenderer` 通信

---

## 三、快速开始

### 1. 创建项目
```bash
mkdir my-app && cd my-app
npm init -y
npm install electron --save-dev
```

### 2. 入口文件 `main.js`
```js
const { app, BrowserWindow } = require('electron')

function createWindow() {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      nodeIntegration: true,          // 渲染进程可访问 Node.js
      contextIsolation: false,        // 关闭隔离（开发方便）
      preload: __dirname + '/preload.js'
    }
  })
  win.loadFile('index.html')
}

app.whenReady().then(createWindow)

// macOS 点击 Dock 图标重新创建窗口
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

// 所有窗口关闭时退出（macOS 除外）
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})
```

### 3. 页面 `index.html`
```html
<!DOCTYPE html>
<html>
<body>
  <h1>Hello Electron!</h1>
  <button id="btn">版本信息</button>
  <script src="renderer.js"></script>
</body>
</html>
```

### 4. 渲染进程 `renderer.js`
```js
const { ipcRenderer } = require('electron')

document.getElementById('btn').onclick = async () => {
  const version = await ipcRenderer.invoke('get-app-version')
  alert(`版本: ${version}`)
}
```

### 5. 主进程处理 IPC
```js
const { ipcMain } = require('electron')

ipcMain.handle('get-app-version', () => {
  return app.getVersion()
})
```

### 6. 启动
```json
// package.json
{
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  }
}
```
```bash
npm start
```

---

## 四、核心 API 速查

### 窗口管理
```js
const win = new BrowserWindow({ ... })

win.loadURL('https://example.com')   // 加载 URL
win.loadFile('index.html')            // 加载本地文件
win.setTitle('新标题')                // 改标题
win.minimize() / maximize() / close()
win.webContents.openDevTools()        // 打开开发者工具

// 无边框窗口
new BrowserWindow({
  frame: false,
  transparent: true,     // 透明背景
  titleBarStyle: 'hidden'
})
```

### 系统功能
```js
const { clipboard, nativeImage, shell, dialog, Notification } = require('electron')

clipboard.writeText('文字')           // 剪贴板
shell.openExternal('https://...')     // 打开外部链接
shell.openPath('/path/to/file')      // 打开文件/文件夹

dialog.showOpenDialog({              // 文件选择对话框
  properties: ['openFile', 'multiSelections']
})

dialog.showSaveDialog({              // 保存对话框
  defaultPath: 'report.pdf'
})

new Notification({                   // 系统通知
  title: '提示',
  body: '下载完成'
}).show()
```

### 系统托盘
```js
const { Tray, Menu } = require('electron')

const tray = new Tray('icon.png')
const contextMenu = Menu.buildFromTemplate([
  { label: '显示窗口', click: () => win.show() },
  { label: '退出', click: () => app.quit() }
])
tray.setContextMenu(contextMenu)
tray.setToolTip('我的应用')
```

---

## 五、进程间通信（IPC）

### 方式一：invoke / handle（推荐）
```js
// main.js
ipcMain.handle('save-file', async (event, data) => {
  const fs = require('fs')
  fs.writeFileSync('data.json', JSON.stringify(data))
  return { success: true }
})

// renderer.js
const result = await ipcRenderer.invoke('save-file', { key: 'value' })
```

### 方式二：send / on（双向）
```js
// main.js
ipcMain.on('msg', (event, arg) => {
  event.reply('msg-reply', '收到: ' + arg)
})

// renderer.js
ipcRenderer.send('msg', '你好')
ipcRenderer.on('msg-reply', (event, arg) => {
  console.log(arg)
})
```

### 主进程 → 渲染进程（推送）
```js
// main.js
win.webContents.send('update', { progress: 80 })

// renderer.js
ipcRenderer.on('update', (event, data) => {
  console.log(data.progress)
})
```

---

## 六、安全实践

### ✅ 生产环境推荐配置
```js
webPreferences: {
  nodeIntegration: false,       // 关闭 Node 集成
  contextIsolation: true,       // 开启上下文隔离
  preload: path.join(__dirname, 'preload.js'),  // 用 preload 暴露接口
  sandbox: true                 // 沙箱模式
}
```

### Preload 脚本模式
```js
// preload.js - 安全的桥接层
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  saveFile: (data) => ipcRenderer.invoke('save-file', data),
  getVersion: () => ipcRenderer.invoke('get-version'),
  onUpdate: (callback) => ipcRenderer.on('update', callback)
})
```

```js
// renderer.js - 只能通过暴露的 API
window.electronAPI.saveFile({ name: 'test' })
```

---

## 七、打包发布

### 使用 electron-builder（推荐）
```bash
npm install electron-builder --save-dev
```

```json
// package.json
{
  "build": {
    "appId": "com.example.myapp",
    "productName": "我的应用",
    "directories": { "output": "release" },
    "mac": { "target": ["dmg", "zip"] },
    "win": { "target": ["nsis", "portable"] },
    "linux": { "target": ["AppImage", "deb"] },
    "nsis": {
      "oneClick": false,
      "allowToChangeInstallationDirectory": true
    }
  },
  "scripts": {
    "build:mac": "electron-builder --mac",
    "build:win": "electron-builder --win",
    "build:linux": "electron-builder --linux"
  }
}
```

```bash
npm run build:mac       # 打包 macOS .dmg
npm run build:win       # 打包 Windows .exe
```

### 使用 electron-forge（官方推荐）
```bash
npm install @electron-forge/cli --save-dev
npx electron-forge import    # 自动配置
npm run make                 # 打包所有平台
```

---

## 八、常用开发工具

| 工具 | 用途 |
|------|------|
| **Electron DevTools** | `win.webContents.openDevTools()` |
| **electron-reload** | 热重载（`npm install electron-reload`） |
| **React/Vue DevTools** | 安装 Chrome 扩展到 Electron |
| **Spectron** | 集成测试 |
| **electron-log** | 日志记录 |
| **electron-store** | 持久化配置（本地 JSON 存储） |

### 热重载开发
```js
// main.js 开发环境
if (process.env.NODE_ENV === 'development') {
  require('electron-reload')(__dirname, {
    electron: path.join(__dirname, 'node_modules', '.bin', 'electron')
  })
}
```

---

## 九、错误排查

```
常见问题：

1. 白屏？
   → 检查 loadFile 路径是否正确
   → 检查 DeveTools 控制台有无报错

2. nodeIntegration 失效？
   → 检查 contextIsolation 是否设为 true
   → 用 preload 脚本暴露接口

3. 打包后文件找不到？
   → 用 __dirname 或 app.getAppPath() 获取正确路径
   → __static 在打包后可能失效

4. macOS 窗口不退出？
   → 按上面示例处理 window-all-closed 事件
```

---

## 十、项目结构推荐

```
my-app/
├── main/                   # 主进程代码
│   ├── main.js
│   ├── menu.js             # 菜单配置
│   └── ipc.js              # IPC 处理
├── renderer/               # 渲染进程（前端）
│   ├── index.html
│   ├── renderer.js
│   └── styles.css
├── preload/                # 预加载脚本
│   └── preload.js
├── assets/                 # 静态资源
│   ├── icons/
│   └── images/
├── package.json
└── electron-builder.yml    # 打包配置
```

> **一句话总结：** Electron 让前端工程师用 Web 技术写桌面应用，核心是理解主进程/渲染进程的分离，以及通过 IPC 安全地通信。VS Code 就是最好的学习范本。