@lobehub/lobehub 2.0.0-next.353 → 2.0.0-next.354

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.agents/skills/add-provider-doc/SKILL.md +90 -0
  2. package/.agents/skills/add-setting-env/SKILL.md +106 -0
  3. package/.agents/skills/debug/SKILL.md +66 -0
  4. package/.agents/skills/desktop/SKILL.md +78 -0
  5. package/.agents/skills/desktop/references/feature-implementation.md +99 -0
  6. package/.agents/skills/desktop/references/local-tools.md +133 -0
  7. package/.agents/skills/desktop/references/menu-config.md +103 -0
  8. package/.agents/skills/desktop/references/window-management.md +143 -0
  9. package/.agents/skills/drizzle/SKILL.md +129 -0
  10. package/.agents/skills/drizzle/references/db-migrations.md +50 -0
  11. package/.agents/skills/hotkey/SKILL.md +90 -0
  12. package/{.cursor/rules/i18n.mdc → .agents/skills/i18n/SKILL.md} +14 -23
  13. package/.agents/skills/linear/SKILL.md +51 -0
  14. package/.agents/skills/microcopy/SKILL.md +83 -0
  15. package/.agents/skills/modal/SKILL.md +102 -0
  16. package/{.cursor/rules/project-structure.mdc → .agents/skills/project-overview/SKILL.md} +65 -37
  17. package/.agents/skills/react/SKILL.md +73 -0
  18. package/.agents/skills/react/references/layout-kit.md +100 -0
  19. package/.agents/skills/recent-data/SKILL.md +108 -0
  20. package/.agents/skills/testing/SKILL.md +89 -0
  21. package/.agents/skills/testing/references/agent-runtime-e2e.md +126 -0
  22. package/.agents/skills/testing/references/db-model-test.md +124 -0
  23. package/.agents/skills/testing/references/desktop-controller-test.md +124 -0
  24. package/.agents/skills/testing/references/electron-ipc-test.md +63 -0
  25. package/.agents/skills/testing/references/zustand-store-action-test.md +150 -0
  26. package/.agents/skills/typescript/SKILL.md +52 -0
  27. package/.agents/skills/zustand/SKILL.md +78 -0
  28. package/.agents/skills/zustand/references/action-patterns.md +125 -0
  29. package/.agents/skills/zustand/references/slice-organization.md +125 -0
  30. package/AGENTS.md +42 -55
  31. package/CHANGELOG.md +33 -0
  32. package/CLAUDE.md +57 -46
  33. package/GEMINI.md +47 -39
  34. package/changelog/v1.json +9 -0
  35. package/package.json +1 -1
  36. package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -3
  37. package/src/features/ShareModal/SharePdf/PdfPreview.tsx +1 -2
  38. package/src/libs/pdfjs/index.tsx +25 -0
  39. package/src/store/test-coverage.md +5 -5
  40. package/.cursor/rules/add-provider-doc.mdc +0 -183
  41. package/.cursor/rules/add-setting-env.mdc +0 -175
  42. package/.cursor/rules/cursor-rules.mdc +0 -28
  43. package/.cursor/rules/db-migrations.mdc +0 -46
  44. package/.cursor/rules/debug-usage.mdc +0 -86
  45. package/.cursor/rules/desktop-controller-tests.mdc +0 -189
  46. package/.cursor/rules/desktop-feature-implementation.mdc +0 -155
  47. package/.cursor/rules/desktop-local-tools-implement.mdc +0 -81
  48. package/.cursor/rules/desktop-menu-configuration.mdc +0 -209
  49. package/.cursor/rules/desktop-window-management.mdc +0 -301
  50. package/.cursor/rules/drizzle-schema-style-guide.mdc +0 -218
  51. package/.cursor/rules/hotkey.mdc +0 -162
  52. package/.cursor/rules/linear.mdc +0 -53
  53. package/.cursor/rules/microcopy-cn.mdc +0 -158
  54. package/.cursor/rules/microcopy-en.mdc +0 -148
  55. package/.cursor/rules/modal-imperative.mdc +0 -162
  56. package/.cursor/rules/packages/react-layout-kit.mdc +0 -122
  57. package/.cursor/rules/project-introduce.mdc +0 -36
  58. package/.cursor/rules/react.mdc +0 -169
  59. package/.cursor/rules/recent-data-usage.mdc +0 -139
  60. package/.cursor/rules/rules-index.mdc +0 -44
  61. package/.cursor/rules/testing-guide/agent-runtime-e2e.mdc +0 -285
  62. package/.cursor/rules/testing-guide/db-model-test.mdc +0 -455
  63. package/.cursor/rules/testing-guide/electron-ipc-test.mdc +0 -80
  64. package/.cursor/rules/testing-guide/testing-guide.mdc +0 -534
  65. package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +0 -574
  66. package/.cursor/rules/typescript.mdc +0 -55
  67. package/.cursor/rules/zustand-action-patterns.mdc +0 -328
  68. package/.cursor/rules/zustand-slice-organization.mdc +0 -308
  69. package/src/libs/pdfjs/pdf.worker.ts +0 -1
  70. package/src/libs/pdfjs/worker.ts +0 -12
  71. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/AGENTS.md +0 -0
  72. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/SKILL.md +0 -0
  73. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-event-handler-refs.md +0 -0
  74. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-use-latest.md +0 -0
  75. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-api-routes.md +0 -0
  76. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-defer-await.md +0 -0
  77. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-dependencies.md +0 -0
  78. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-parallel.md +0 -0
  79. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-suspense-boundaries.md +0 -0
  80. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-barrel-imports.md +0 -0
  81. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-conditional.md +0 -0
  82. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-defer-third-party.md +0 -0
  83. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-dynamic-imports.md +0 -0
  84. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-preload.md +0 -0
  85. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-event-listeners.md +0 -0
  86. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-localstorage-schema.md +0 -0
  87. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-passive-event-listeners.md +0 -0
  88. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-swr-dedup.md +0 -0
  89. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-batch-dom-css.md +0 -0
  90. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-function-results.md +0 -0
  91. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-property-access.md +0 -0
  92. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-storage.md +0 -0
  93. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-combine-iterations.md +0 -0
  94. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-early-exit.md +0 -0
  95. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-hoist-regexp.md +0 -0
  96. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-index-maps.md +0 -0
  97. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-length-check-first.md +0 -0
  98. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-min-max-loop.md +0 -0
  99. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-set-map-lookups.md +0 -0
  100. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-tosorted-immutable.md +0 -0
  101. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-activity.md +0 -0
  102. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-animate-svg-wrapper.md +0 -0
  103. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-conditional-render.md +0 -0
  104. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-content-visibility.md +0 -0
  105. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hoist-jsx.md +0 -0
  106. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hydration-no-flicker.md +0 -0
  107. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-svg-precision.md +0 -0
  108. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-defer-reads.md +0 -0
  109. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-dependencies.md +0 -0
  110. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-derived-state.md +0 -0
  111. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-functional-setstate.md +0 -0
  112. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-lazy-state-init.md +0 -0
  113. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-memo.md +0 -0
  114. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-transitions.md +0 -0
  115. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-after-nonblocking.md +0 -0
  116. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-lru.md +0 -0
  117. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-react.md +0 -0
  118. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-parallel-fetching.md +0 -0
  119. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-serialization.md +0 -0
@@ -1,209 +0,0 @@
1
- ---
2
- description:
3
- globs:
4
- alwaysApply: false
5
- ---
6
-
7
- # 桌面端菜单配置指南
8
-
9
- ## 菜单系统概述
10
-
11
- LobeChat 桌面应用有三种主要的菜单类型:
12
-
13
- 1. **应用菜单 (App Menu)**:显示在应用窗口顶部(macOS)或窗口标题栏(Windows/Linux)
14
- 2. **上下文菜单 (Context Menu)**:右键点击时显示的菜单
15
- 3. **托盘菜单 (Tray Menu)**:点击系统托盘图标显示的菜单
16
-
17
- ## 菜单相关文件结构
18
-
19
- ```plaintext
20
- apps/desktop/src/main/
21
- ├── menus/ # 菜单定义
22
- │ ├── appMenu.ts # 应用菜单配置
23
- │ ├── contextMenu.ts # 上下文菜单配置
24
- │ └── factory.ts # 菜单工厂函数
25
- ├── controllers/
26
- │ ├── MenuCtr.ts # 菜单控制器
27
- │ └── TrayMenuCtr.ts # 托盘菜单控制器
28
- ```
29
-
30
- ## 菜单配置流程
31
-
32
- ### 1. 应用菜单配置
33
-
34
- 应用菜单在 `apps/desktop/src/main/menus/appMenu.ts` 中定义:
35
-
36
- 1. **导入依赖**
37
-
38
- ```typescript
39
- import { BrowserWindow, Menu, MenuItem, MenuItemConstructorOptions, app } from 'electron';
40
- import { is } from 'electron-util';
41
- ```
42
-
43
- 2. **定义菜单项**
44
- - 使用 `MenuItemConstructorOptions` 类型定义菜单结构
45
- - 每个菜单项可以包含:label, accelerator (快捷键), role, submenu, click 等属性
46
-
47
- 3. **创建菜单工厂函数**
48
-
49
- ```typescript
50
- export const createAppMenu = (win: BrowserWindow) => {
51
- const template = [
52
- // 定义菜单项...
53
- ];
54
-
55
- return Menu.buildFromTemplate(template);
56
- };
57
- ```
58
-
59
- 4. **注册菜单**
60
- - 在 `MenuCtr.ts` 控制器中使用 `Menu.setApplicationMenu(menu)` 设置应用菜单
61
-
62
- ### 2. 上下文菜单配置
63
-
64
- 上下文菜单通常在特定元素上右键点击时显示:
65
-
66
- 1. **在主进程中定义菜单模板**
67
-
68
- ```typescript
69
- // apps/desktop/src/main/menus/contextMenu.ts
70
- export const createContextMenu = () => {
71
- const template = [
72
- // 定义菜单项...
73
- ];
74
-
75
- return Menu.buildFromTemplate(template);
76
- };
77
- ```
78
-
79
- 2. **在适当的事件处理器中显示菜单**
80
-
81
- ```typescript
82
- const menu = createContextMenu();
83
- menu.popup();
84
- ```
85
-
86
- ### 3. 托盘菜单配置
87
-
88
- 托盘菜单在 `TrayMenuCtr.ts` 中配置:
89
-
90
- 1. **创建托盘图标**
91
-
92
- ```typescript
93
- this.tray = new Tray(trayIconPath);
94
- ```
95
-
96
- 2. **定义托盘菜单**
97
-
98
- ```typescript
99
- const contextMenu = Menu.buildFromTemplate([
100
- { label: '显示主窗口', click: this.showMainWindow },
101
- { type: 'separator' },
102
- { label: '退出', click: () => app.quit() },
103
- ]);
104
- ```
105
-
106
- 3. **设置托盘菜单**
107
-
108
- ```typescript
109
- this.tray.setContextMenu(contextMenu);
110
- ```
111
-
112
- ## 多语言支持
113
-
114
- 为菜单添加多语言支持:
115
-
116
- 1. **导入本地化工具**
117
-
118
- ```typescript
119
- import { i18n } from '../locales';
120
- ```
121
-
122
- 2. **使用翻译函数**
123
-
124
- ```typescript
125
- const template = [
126
- {
127
- label: i18n.t('menu.file'),
128
- submenu: [
129
- { label: i18n.t('menu.new'), click: createNew },
130
- // ...
131
- ],
132
- },
133
- // ...
134
- ];
135
- ```
136
-
137
- 3. **在语言切换时更新菜单** 在 `MenuCtr.ts` 中监听语言变化事件并重新创建菜单
138
-
139
- ## 添加新菜单项流程
140
-
141
- 1. **确定菜单位置**
142
- - 决定添加到哪个菜单(应用菜单、上下文菜单或托盘菜单)
143
- - 确定在菜单中的位置(主菜单项或子菜单项)
144
-
145
- 2. **定义菜单项**
146
-
147
- ```typescript
148
- const newMenuItem: MenuItemConstructorOptions = {
149
- label: '新功能',
150
- accelerator: 'CmdOrCtrl+N',
151
- click: (_, window) => {
152
- // 处理点击事件
153
- if (window) window.webContents.send('trigger-new-feature');
154
- },
155
- };
156
- ```
157
-
158
- 3. **添加到菜单模板** 将新菜单项添加到相应的菜单模板中
159
-
160
- 4. **对于与渲染进程交互的功能**
161
- - 使用 `window.webContents.send()` 发送 IPC 消息到渲染进程
162
- - 在渲染进程中监听该消息并处理
163
-
164
- ## 菜单项启用/禁用控制
165
-
166
- 动态控制菜单项状态:
167
-
168
- 1. **保存对菜单项的引用**
169
-
170
- ```typescript
171
- this.menuItems = {};
172
- const menu = Menu.buildFromTemplate(template);
173
- this.menuItems.newFeature = menu.getMenuItemById('new-feature');
174
- ```
175
-
176
- 2. **根据条件更新状态**
177
-
178
- ```typescript
179
- updateMenuState(state) {
180
- if (this.menuItems.newFeature) {
181
- this.menuItems.newFeature.enabled = state.canUseNewFeature;
182
- }
183
- }
184
- ```
185
-
186
- ## 最佳实践
187
-
188
- 1. **使用标准角色**
189
- - 尽可能使用 Electron 预定义的角色(如 `role: 'copy'`)以获得本地化和一致的行为
190
-
191
- 2. **平台特定菜单**
192
- - 使用 `process.platform` 检查为不同平台提供不同菜单
193
-
194
- ```typescript
195
- if (process.platform === 'darwin') {
196
- template.unshift({ role: 'appMenu' });
197
- }
198
- ```
199
-
200
- 3. **快捷键冲突**
201
- - 避免与系统快捷键冲突
202
- - 使用 `CmdOrCtrl` 代替 `Ctrl` 以支持 macOS 和 Windows/Linux
203
-
204
- 4. **保持菜单简洁**
205
- - 避免过多嵌套的子菜单
206
- - 将相关功能分组在一起
207
-
208
- 5. **添加分隔符**
209
- - 使用 `{ type: 'separator' }` 在逻辑上分隔不同组的菜单项
@@ -1,301 +0,0 @@
1
- ---
2
- description:
3
- globs:
4
- alwaysApply: false
5
- ---
6
-
7
- # 桌面端窗口管理指南
8
-
9
- ## 窗口管理概述
10
-
11
- LobeChat 桌面应用使用 Electron 的 `BrowserWindow` 管理应用窗口。主要的窗口管理功能包括:
12
-
13
- 1. **窗口创建和配置**
14
- 2. **窗口状态管理**(大小、位置、最大化等)
15
- 3. **多窗口协调**
16
- 4. **窗口事件处理**
17
-
18
- ## 相关文件结构
19
-
20
- ```plaintext
21
- apps/desktop/src/main/
22
- ├── appBrowsers.ts # 窗口管理的核心文件
23
- ├── controllers/
24
- │ └── BrowserWindowsCtr.ts # 窗口控制器
25
- └── modules/
26
- └── browserWindowManager.ts # 窗口管理模块
27
- ```
28
-
29
- ## 窗口管理流程
30
-
31
- ### 1. 窗口创建
32
-
33
- 在 `appBrowsers.ts` 或 `BrowserWindowsCtr.ts` 中定义窗口创建逻辑:
34
-
35
- ```typescript
36
- export const createMainWindow = () => {
37
- const mainWindow = new BrowserWindow({
38
- width: 1200,
39
- height: 800,
40
- minWidth: 600,
41
- minHeight: 400,
42
- webPreferences: {
43
- preload: path.join(__dirname, '../preload/index.js'),
44
- contextIsolation: true,
45
- nodeIntegration: false,
46
- },
47
- // 其他窗口配置项...
48
- });
49
-
50
- // 加载应用内容
51
- if (isDev) {
52
- mainWindow.loadURL('http://localhost:3000');
53
- mainWindow.webContents.openDevTools();
54
- } else {
55
- mainWindow.loadFile(path.join(__dirname, '../../renderer/index.html'));
56
- }
57
-
58
- return mainWindow;
59
- };
60
- ```
61
-
62
- ### 2. 窗口状态管理
63
-
64
- 实现窗口状态持久化保存和恢复:
65
-
66
- 1. **保存窗口状态**
67
-
68
- ```typescript
69
- const saveWindowState = (window: BrowserWindow) => {
70
- if (!window.isMinimized() && !window.isMaximized()) {
71
- const position = window.getPosition();
72
- const size = window.getSize();
73
-
74
- settings.set('windowState', {
75
- x: position[0],
76
- y: position[1],
77
- width: size[0],
78
- height: size[1],
79
- });
80
- }
81
- };
82
- ```
83
-
84
- 2. **恢复窗口状态**
85
-
86
- ```typescript
87
- const restoreWindowState = (window: BrowserWindow) => {
88
- const savedState = settings.get('windowState');
89
-
90
- if (savedState) {
91
- window.setBounds({
92
- x: savedState.x,
93
- y: savedState.y,
94
- width: savedState.width,
95
- height: savedState.height,
96
- });
97
- }
98
- };
99
- ```
100
-
101
- 3. **监听窗口事件**
102
-
103
- ```typescript
104
- window.on('close', () => saveWindowState(window));
105
- window.on('moved', () => saveWindowState(window));
106
- window.on('resized', () => saveWindowState(window));
107
- ```
108
-
109
- ### 3. 实现多窗口管理
110
-
111
- 对于需要多窗口支持的功能:
112
-
113
- 1. **跟踪窗口**
114
-
115
- ```typescript
116
- export class WindowManager {
117
- private windows: Map<string, BrowserWindow> = new Map();
118
-
119
- createWindow(id: string, options: BrowserWindowConstructorOptions) {
120
- const window = new BrowserWindow(options);
121
- this.windows.set(id, window);
122
-
123
- window.on('closed', () => {
124
- this.windows.delete(id);
125
- });
126
-
127
- return window;
128
- }
129
-
130
- getWindow(id: string) {
131
- return this.windows.get(id);
132
- }
133
-
134
- getAllWindows() {
135
- return Array.from(this.windows.values());
136
- }
137
- }
138
- ```
139
-
140
- 2. **窗口间通信**
141
-
142
- ```typescript
143
- // 从一个窗口向另一个窗口发送消息
144
- sendMessageToWindow(targetWindowId, channel, data) {
145
- const targetWindow = this.getWindow(targetWindowId);
146
- if (targetWindow) {
147
- targetWindow.webContents.send(channel, data);
148
- }
149
- }
150
- ```
151
-
152
- ### 4. 窗口与渲染进程通信
153
-
154
- 通过 IPC 实现窗口操作:
155
-
156
- 1. **在主进程中注册 IPC 处理器**
157
-
158
- ```typescript
159
- // apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
160
- import { BrowserWindow } from 'electron';
161
-
162
- import { ControllerModule, IpcMethod } from '@/controllers';
163
-
164
- export default class BrowserWindowsCtr extends ControllerModule {
165
- static override readonly groupName = 'windows';
166
-
167
- @IpcMethod()
168
- minimizeWindow() {
169
- const focusedWindow = BrowserWindow.getFocusedWindow();
170
- focusedWindow?.minimize();
171
- return { success: true };
172
- }
173
-
174
- @IpcMethod()
175
- maximizeWindow() {
176
- const focusedWindow = BrowserWindow.getFocusedWindow();
177
- if (focusedWindow?.isMaximized()) focusedWindow.restore();
178
- else focusedWindow?.maximize();
179
- return { success: true };
180
- }
181
-
182
- @IpcMethod()
183
- closeWindow() {
184
- BrowserWindow.getFocusedWindow()?.close();
185
- return { success: true };
186
- }
187
- }
188
- ```
189
-
190
- - `@IpcMethod()` 根据控制器的 `groupName` 自动将方法映射为 `windows.minimizeWindow` 形式的通道名称。
191
- - 控制器需继承 `ControllerModule`,并在 `controllers/registry.ts` 中通过 `controllerIpcConstructors` 注册,便于类型生成。
192
-
193
- 2. **在渲染进程中调用**
194
-
195
- ```typescript
196
- // src/services/electron/windowService.ts
197
- import { ensureElectronIpc } from '@/utils/electron/ipc';
198
-
199
- const ipc = ensureElectronIpc();
200
-
201
- export const windowService = {
202
- minimize: () => ipc.windows.minimizeWindow(),
203
- maximize: () => ipc.windows.maximizeWindow(),
204
- close: () => ipc.windows.closeWindow(),
205
- };
206
- ```
207
-
208
- - `ensureElectronIpc()` 会基于 `DesktopIpcServices` 运行时生成 Proxy,并通过 `window.electronAPI.invoke` 与主进程通信;不再直接使用 `dispatch`。
209
-
210
- ### 5. 自定义窗口控制 (无边框窗口)
211
-
212
- 对于自定义窗口标题栏:
213
-
214
- 1. **创建无边框窗口**
215
-
216
- ```typescript
217
- const window = new BrowserWindow({
218
- frame: false,
219
- titleBarStyle: 'hidden',
220
- // 其他选项...
221
- });
222
- ```
223
-
224
- 2. **在渲染进程中实现拖拽区域**
225
-
226
- ```css
227
- /* CSS */
228
- .titlebar {
229
- -webkit-app-region: drag;
230
- }
231
-
232
- .titlebar-button {
233
- -webkit-app-region: no-drag;
234
- }
235
- ```
236
-
237
- ## 最佳实践
238
-
239
- 1. **性能考虑**
240
- - 避免创建过多窗口
241
- - 使用 `show: false` 创建窗口,在内容加载完成后再显示,避免白屏
242
-
243
- 2. **安全性**
244
- - 始终设置适当的 `webPreferences` 确保安全
245
-
246
- ```typescript
247
- webPreferences: {
248
- preload: path.join(__dirname, '../preload/index.js'),
249
- contextIsolation: true,
250
- nodeIntegration: false,
251
- sandbox: true,
252
- }
253
- ```
254
-
255
- 3. **跨平台兼容性**
256
- - 考虑不同操作系统的窗口行为差异
257
- - 使用 `process.platform` 为不同平台提供特定实现
258
-
259
- 4. **崩溃恢复**
260
- - 监听 `webContents.on('crashed')` 事件处理崩溃
261
- - 提供崩溃恢复选项
262
-
263
- 5. **内存管理**
264
- - 确保窗口关闭时清理所有相关资源
265
- - 使用 `window.on('closed')` 而不是 `window.on('close')` 进行最终清理
266
-
267
- ## 示例:创建设置窗口
268
-
269
- ```typescript
270
- // apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
271
- import type { OpenSettingsWindowOptions } from '@lobechat/electron-client-ipc';
272
-
273
- import { ControllerModule, IpcMethod } from '@/controllers';
274
-
275
- export default class BrowserWindowsCtr extends ControllerModule {
276
- static override readonly groupName = 'windows';
277
-
278
- @IpcMethod()
279
- async openSettingsWindow(options?: string | OpenSettingsWindowOptions) {
280
- const normalizedOptions =
281
- typeof options === 'string' || options === undefined
282
- ? { tab: typeof options === 'string' ? options : undefined }
283
- : options;
284
-
285
- const mainWindow = this.app.browserManager.getMainWindow();
286
- const query = new URLSearchParams();
287
- if (normalizedOptions.tab) query.set('active', normalizedOptions.tab);
288
- if (normalizedOptions.searchParams) {
289
- for (const [key, value] of Object.entries(normalizedOptions.searchParams)) {
290
- if (value) query.set(key, value);
291
- }
292
- }
293
-
294
- const fullPath = `/settings${query.size ? `?${query.toString()}` : ''}`;
295
- await mainWindow.loadUrl(fullPath);
296
- mainWindow.show();
297
-
298
- return { success: true };
299
- }
300
- }
301
- ```