# Window Controls 迁移计划(对齐 ClawX) > 目标:将 zn-ai 的窗口控制体系**全面对齐 ClawX**,废弃 zn-ai 现有相关代码,按平台重建跨平台标题栏方案。UI 视觉延用 zn-ai 设计,图标库统一使用 `@lucide/vue`。 --- ## 1. 现状分析 ### 1.1 ClawX 实现思路 | 平台 | 方案 | 关键配置 | |------|------|----------| | **macOS** | 原生 traffic lights | `titleBarStyle: 'hiddenInset'` | | **Windows** | 自定义 React 标题栏 | `titleBarStyle: 'hidden'` + `frame: false` | | **Linux** | 保留原生窗口边框 | `frame: true`(默认) | - **macOS**:前端仅渲染一个 `drag-region`(高度 40px),红绿灯由系统原生绘制。 - **Windows**:前端自定义最小化、最大化/恢复、关闭三个按钮,通过 `invoke` IPC 调用主进程 `BrowserWindow` API。 - **Linux**:出于 IME 兼容性考虑,不隐藏原生边框,前端不渲染任何自定义标题栏。 ### 1.2 zn-ai 现状(待废弃) | 项 | 现状 | 决策 | |----|------|------| | 主进程窗口配置 | 所有平台统一 `frame: false` + `titleBarStyle: 'hidden'` | **废弃,重写** | | 前端组件 | `HeaderBar/index.vue` 同时包含 macOS 自定义按钮 + Windows 自定义按钮 | **废弃,删除** | | 窗口管理 Hook | `useWinManager.ts` 封装 `ref` + `onMounted` 状态管理 | **废弃,删除** | | 拖拽区域组件 | `DragRegion/index.vue` 独立组件 | **废弃,删除**(直接内联到 TitleBar) | | Layout 集成 | `Layout/index.vue` 嵌套 `header-bar` + `drag-region` | **重写** | | IPC 通信 | `WINDOW_CLOSE` / `WINDOW_MINIMIZE` / `WINDOW_MAXIMIZE` / `IS_WINDOW_MAXIMIZED`(`send`/`on` 混合) | **废弃,改为 `invoke/handle` 对齐 ClawX** | | 图标库 | `@iconify/vue` + `@iconify-json/material-symbols` | **废弃,替换为 `@lucide/vue`** | ### 1.3 核心差异 1. **macOS**:ClawX 使用原生 traffic lights;zn-ai 使用自定义按钮(将原生按钮移出可视区域),行为不完整。 2. **Linux**:ClawX 保留原生边框;zn-ai 隐藏了原生边框,存在 IME 兼容性风险。 3. **IPC 模式**:ClawX 使用 `ipcMain.handle` + `invokeIpc`(请求-响应),zn-ai 使用 `ipcMain.on` + `ipcRenderer.send`(事件驱动),模式不统一。 4. **组件层级**:ClawX 的 `TitleBar` 是一个自包含组件,内部自带 `drag-region`/`no-drag` 逻辑;zn-ai 将其拆成了 `HeaderBar` + `DragRegion` + `useWinManager` 三个文件,过度拆分。 --- ## 2. 迁移方案 ### 2.1 设计原则 - **全面对齐 ClawX**:架构、IPC 模式、平台分支逻辑直接复刻 ClawX 实现。 - **zn-ai 既有代码可抛弃**:`HeaderBar`、`DragRegion`、`useWinManager` 及相关 IPC 直接删除,不用做兼容改造。 - **UI 视觉延用 zn-ai**:Windows 按钮的 hover 颜色、尺寸保持 zn-ai 现有设计(`#999` / `#ff0000`)。 - **图标统一使用 `@lucide/vue`**:废弃 `@iconify/vue`。 ### 2.2 废弃清单(Delete List) | 文件/目录 | 说明 | |-----------|------| | `src/components/HeaderBar/index.vue` | 既有自定义按钮组件,完全废弃 | | `src/components/DragRegion/index.vue` | 拖拽区域组件,逻辑内联到新的 `TitleBar` | | `src/hooks/useWinManager.ts` | 窗口管理 Hook,逻辑内联到新的 `TitleBar` | | `src/main.ts` 中 `HeaderBar` / `DragRegion` 的全局注册 | 删除全局组件注册 | ### 2.3 废弃的 IPC 与常量 zn-ai 现有窗口控制 IPC(基于 `send`/`on`): ```ts // constants.ts 中废弃以下事件 WINDOW_MINIMIZE = 'window-minimize' WINDOW_MAXIMIZE = 'window-maximize' WINDOW_CLOSE = 'window-close' IS_WINDOW_MAXIMIZED = 'is-window-maximized' ``` **废弃后替换为 ClawX 风格的 `invoke` channel**: ```ts // 新增(对齐 ClawX) 'window:minimize' 'window:maximize' 'window:close' 'window:isMaximized' ``` > 若项目中其他模块也使用了旧的 `WINDOW_MINIMIZE` 等常量,需要一并迁移。经排查,旧常量仅在 `HeaderBar` / `useWinManager` / `window-service` / `preload` 中使用,可随本次重构一并删除。 ### 2.4 新建/重写清单(Create/Rewrite List) | 新建/重写项 | 说明 | |-------------|------| | `src/components/layout/TitleBar/index.vue` | **新建**。对齐 ClawX `TitleBar.tsx`,Vue 实现。 | | `src/components/layout/Layout/index.vue` | **重写**。移除 `header-bar`/`drag-region` 引用,改用 `TitleBar`。 | | `electron/service/window-service/index.ts` | **重写 `SHARED_WINDOW_OPTIONS`**。按平台设置 `frame`/`titleBarStyle`/`trafficLightPosition`。 | | `electron/main.ts` 或新建 `electron/main/ipc-handlers.ts` | **新增/重写**。注册 `window:*` 的 `ipcMain.handle`。 | | `electron/preload/index.ts` | **重写**。移除旧 `send/on` API,新增 `invoke('window:*')` 和 `platform`。 | | `src/lib/api-client.ts`(或类似文件) | **新增 `invokeIpc` 辅助函数**。对齐 ClawX 的 `invokeIpc` 调用风格。 | | `src/styles/index.css` | 保留 `.drag-region` / `.no-drag`,供 `TitleBar` 使用。 | ### 2.5 主进程窗口配置改造 **目标代码(对齐 ClawX)**: ```ts // electron/service/window-service/index.ts const isMac = process.platform === 'darwin'; const isWindows = process.platform === 'win32'; const useCustomTitleBar = isWindows; const SHARED_WINDOW_OPTIONS = { frame: isMac || !useCustomTitleBar, titleBarStyle: isMac ? 'hiddenInset' : useCustomTitleBar ? 'hidden' : 'default', trafficLightPosition: isMac ? { x: 16, y: 16 } : undefined, show: false, title: 'NIANXX', darkTheme: themeManager.isDark, backgroundColor: themeManager.isDark ? '#2C2C2C' : '#FFFFFF', webPreferences: { nodeIntegration: false, contextIsolation: true, sandbox: true, backgroundThrottling: false, preload: MAIN_WINDOW_VITE_DEV_SERVER_URL ? path.join(process.cwd(), 'dist-electron/preload/preload.js') : path.join(__dirname, 'preload.js'), }, } as BrowserWindowConstructorOptions; ``` **注意**: - 删除旧的 `trafficLightPosition: { x: -100, y: -100 }`。 - macOS 使用 `hiddenInset` 后,红绿灯由系统原生绘制,前端无需硬编码左侧占位。 ### 2.6 新增 IPC Handlers(对齐 ClawX) 新建或复用文件(建议新建 `electron/ipc/window-handlers.ts`,方便模块化): ```ts import { ipcMain, BrowserWindow } from 'electron'; export function registerWindowHandlers(mainWindow: BrowserWindow): void { ipcMain.handle('window:minimize', () => { mainWindow.minimize(); }); ipcMain.handle('window:maximize', () => { if (mainWindow.isMaximized()) { mainWindow.unmaximize(); } else { mainWindow.maximize(); } }); ipcMain.handle('window:close', () => { mainWindow.close(); }); ipcMain.handle('window:isMaximized', () => { return mainWindow.isMaximized(); }); } ``` 在 `electron/main.ts` 或 `electron/wins/index.ts` 的适当时机调用 `registerWindowHandlers(mainWindow)`。 ### 2.7 Preload 改造(对齐 ClawX `invokeIpc` 风格) ```ts // electron/preload/index.ts const api: WindowApi = { // ... 保留既有 API platform: process.platform, // 窗口控制 — 对齐 ClawX 的 invoke 风格 windowMinimize: () => ipcRenderer.invoke('window:minimize'), windowMaximize: () => ipcRenderer.invoke('window:maximize'), windowClose: () => ipcRenderer.invoke('window:close'), windowIsMaximized: () => ipcRenderer.invoke('window:isMaximized'), // 移除旧的 minimizeWindow / maximizeWindow / closeWindow / onWindowMaximized / isWindowMaximized } ``` ### 2.8 前端 `invokeIpc` 辅助函数(可选,强烈建议) ClawX 使用统一的 `invokeIpc` 函数调用主进程,zn-ai 建议新增该辅助函数以保持一致风格: ```ts // src/lib/api-client.ts export function invokeIpc(channel: string, ...args: any[]): Promise { return window.api.invoke(channel, ...args); } ``` > 如果 zn-ai 已有类似封装(如 `window.api.invoke`),可直接使用,无需额外文件。 ### 2.9 新建 TitleBar 组件(对齐 ClawX) **新建文件**:`src/components/layout/TitleBar/index.vue` ```vue