From a16fa0265bd15069b0e65cec41b4012e678418ac Mon Sep 17 00:00:00 2001 From: Arvin Xu Date: Mon, 26 May 2025 14:53:02 +0800 Subject: [PATCH] =?UTF-8?q?=20=F0=9F=90=9B=20fix:=20auto=20sync=20theme=20?= =?UTF-8?q?mode=20in=20desktop=20(#7970)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * auto sync theme * improve style * improve code --- .../rules/desktop-feature-implementation.mdc | 154 +++++++++ .cursor/rules/desktop-menu-configuration.mdc | 197 ++++++++++++ .cursor/rules/desktop-window-management.mdc | 296 ++++++++++++++++++ .../desktop/src/main/controllers/SystemCtr.ts | 7 +- .../electron-client-ipc/src/events/index.ts | 5 +- .../electron-client-ipc/src/events/system.ts | 7 +- .../electron-client-ipc/src/types/system.ts | 2 + .../hooks/useWatchThemeUpdate.ts | 21 ++ src/features/ElectronTitlebar/index.tsx | 2 + src/store/global/actions/general.ts | 15 +- src/styles/global.ts | 8 - 11 files changed, 700 insertions(+), 14 deletions(-) create mode 100644 .cursor/rules/desktop-feature-implementation.mdc create mode 100644 .cursor/rules/desktop-menu-configuration.mdc create mode 100644 .cursor/rules/desktop-window-management.mdc create mode 100644 src/features/ElectronTitlebar/hooks/useWatchThemeUpdate.ts diff --git a/.cursor/rules/desktop-feature-implementation.mdc b/.cursor/rules/desktop-feature-implementation.mdc new file mode 100644 index 0000000000..7ab82d9114 --- /dev/null +++ b/.cursor/rules/desktop-feature-implementation.mdc @@ -0,0 +1,154 @@ +--- +description: +globs: +alwaysApply: false +--- +**桌面端新功能实现指南** + +## 桌面端应用架构概述 + +LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架构: + +1. **主进程 (Main Process)**: + - 位置:`apps/desktop/src/main` + - 职责:控制应用生命周期、系统API交互、窗口管理、后台服务 + +2. **渲染进程 (Renderer Process)**: + - 复用 Web 端代码,位于 `src` 目录 + - 通过 IPC 与主进程通信 + +3. **预加载脚本 (Preload)**: + - 位置:`apps/desktop/src/preload` + - 职责:安全地暴露主进程功能给渲染进程 + +## 添加新桌面端功能流程 + +### 1. 确定功能需求与设计 + +首先确定新功能的需求和设计,包括: +- 功能描述和用例 +- 是否需要系统级API(如文件系统、网络等) +- UI/UX设计(如必要) +- 与现有功能的交互方式 + +### 2. 在主进程中实现核心功能 + +1. **创建控制器 (Controller)** + - 位置:`apps/desktop/src/main/controllers/` + - 示例:创建 `NewFeatureCtr.ts` + - 规范:按 `_template.ts` 模板格式实现 + - 注册:在 `apps/desktop/src/main/controllers/index.ts` 导出 + +2. **定义 IPC 事件处理器** + - 使用 `@ipcClientEvent('eventName')` 装饰器注册事件处理函数 + - 处理函数应接收前端传递的参数并返回结果 + - 处理可能的错误情况 + +3. **实现业务逻辑** + - 可能需要调用 Electron API 或 Node.js 原生模块 + - 对于复杂功能,可以创建专门的服务类 (`services/`) + +### 3. 定义 IPC 通信类型 + +1. **在共享类型定义中添加新类型** + - 位置:`packages/electron-client-ipc/src/types.ts` + - 添加参数类型接口(如 `NewFeatureParams`) + - 添加返回结果类型接口(如 `NewFeatureResult`) + +### 4. 在渲染进程实现前端功能 + +1. **创建服务层** + - 位置:`src/services/electron/` + - 添加服务方法调用 IPC + - 使用 `dispatch` 或 `invoke` 函数 + + ```typescript + // src/services/electron/newFeatureService.ts + import { dispatch } from '@lobechat/electron-client-ipc'; + import { NewFeatureParams } from 'types'; + + export const newFeatureService = async (params: NewFeatureParams) => { + return dispatch('newFeatureEventName', params); + }; + ``` + +2. **实现 Store Action** + - 位置:`src/store/` + - 添加状态更新逻辑和错误处理 + +3. **添加 UI 组件** + - 根据需要在适当位置添加UI组件 + - 通过 Store 或 Service 层调用功能 + +### 5. 如果是新增内置工具,遵循工具实现流程 + +参考 [desktop-local-tools-implement.mdc](mdc:desktop-local-tools-implement.mdc) 了解更多关于添加内置工具的详细步骤。 + +### 6. 添加测试 + +1. **单元测试** + - 位置:`apps/desktop/src/main/controllers/__tests__/` + - 测试主进程组件功能 + +2. **集成测试** + - 测试 IPC 通信和功能完整流程 + +## 最佳实践 + +1. **安全性考虑** + - 谨慎处理用户数据和文件系统访问 + - 适当验证和清理输入数据 + - 限制暴露给渲染进程的API范围 + +2. **性能优化** + - 对于耗时操作,考虑使用异步方法 + - 大型数据传输考虑分批处理 + +3. **用户体验** + - 为长时间操作添加进度指示 + - 提供适当的错误反馈 + - 考虑操作的可撤销性 + +4. **代码组织** + - 遵循项目现有的命名和代码风格约定 + - 为新功能添加适当的文档和注释 + - 功能模块化,避免过度耦合 + +## 示例:实现系统通知功能 + +```typescript +// apps/desktop/src/main/controllers/NotificationCtr.ts +import { BrowserWindow, Notification } from 'electron'; +import { ipcClientEvent } from 'electron-client-ipc'; + +interface ShowNotificationParams { + title: string; + body: string; +} + +export class NotificationCtr { + @ipcClientEvent('showNotification') + async handleShowNotification({ title, body }: ShowNotificationParams) { + try { + if (!Notification.isSupported()) { + return { success: false, error: 'Notifications not supported' }; + } + + const notification = new Notification({ + title, + body, + }); + + notification.show(); + + return { success: true }; + } catch (error) { + console.error('Failed to show notification:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }; + } + } +} +``` diff --git a/.cursor/rules/desktop-menu-configuration.mdc b/.cursor/rules/desktop-menu-configuration.mdc new file mode 100644 index 0000000000..2da65e4c8b --- /dev/null +++ b/.cursor/rules/desktop-menu-configuration.mdc @@ -0,0 +1,197 @@ +--- +description: +globs: +alwaysApply: false +--- +**桌面端菜单配置指南** + +## 菜单系统概述 + +LobeChat 桌面应用有三种主要的菜单类型: + +1. **应用菜单 (App Menu)**:显示在应用窗口顶部(macOS)或窗口标题栏(Windows/Linux) +2. **上下文菜单 (Context Menu)**:右键点击时显示的菜单 +3. **托盘菜单 (Tray Menu)**:点击系统托盘图标显示的菜单 + +## 菜单相关文件结构 + +``` +apps/desktop/src/main/ +├── menus/ # 菜单定义 +│ ├── appMenu.ts # 应用菜单配置 +│ ├── contextMenu.ts # 上下文菜单配置 +│ └── factory.ts # 菜单工厂函数 +├── controllers/ +│ ├── MenuCtr.ts # 菜单控制器 +│ └── TrayMenuCtr.ts # 托盘菜单控制器 +``` + +## 菜单配置流程 + +### 1. 应用菜单配置 + +应用菜单在 `apps/desktop/src/main/menus/appMenu.ts` 中定义: + +1. **导入依赖** + ```typescript + import { app, BrowserWindow, Menu, MenuItem, MenuItemConstructorOptions } from 'electron'; + import { is } from 'electron-util'; + ``` + +2. **定义菜单项** + - 使用 `MenuItemConstructorOptions` 类型定义菜单结构 + - 每个菜单项可以包含:label, accelerator (快捷键), role, submenu, click 等属性 + +3. **创建菜单工厂函数** + ```typescript + export const createAppMenu = (win: BrowserWindow) => { + const template = [ + // 定义菜单项... + ]; + + return Menu.buildFromTemplate(template); + }; + ``` + +4. **注册菜单** + - 在 `MenuCtr.ts` 控制器中使用 `Menu.setApplicationMenu(menu)` 设置应用菜单 + +### 2. 上下文菜单配置 + +上下文菜单通常在特定元素上右键点击时显示: + +1. **在主进程中定义菜单模板** + ```typescript + // apps/desktop/src/main/menus/contextMenu.ts + export const createContextMenu = () => { + const template = [ + // 定义菜单项... + ]; + + return Menu.buildFromTemplate(template); + }; + ``` + +2. **在适当的事件处理器中显示菜单** + ```typescript + const menu = createContextMenu(); + menu.popup(); + ``` + +### 3. 托盘菜单配置 + +托盘菜单在 `TrayMenuCtr.ts` 中配置: + +1. **创建托盘图标** + ```typescript + this.tray = new Tray(trayIconPath); + ``` + +2. **定义托盘菜单** + ```typescript + const contextMenu = Menu.buildFromTemplate([ + { label: '显示主窗口', click: this.showMainWindow }, + { type: 'separator' }, + { label: '退出', click: () => app.quit() }, + ]); + ``` + +3. **设置托盘菜单** + ```typescript + this.tray.setContextMenu(contextMenu); + ``` + +## 多语言支持 + +为菜单添加多语言支持: + +1. **导入本地化工具** + ```typescript + import { i18n } from '../locales'; + ``` + +2. **使用翻译函数** + ```typescript + const template = [ + { + label: i18n.t('menu.file'), + submenu: [ + { label: i18n.t('menu.new'), click: createNew }, + // ... + ] + }, + // ... + ]; + ``` + +3. **在语言切换时更新菜单** + 在 `MenuCtr.ts` 中监听语言变化事件并重新创建菜单 + +## 添加新菜单项流程 + +1. **确定菜单位置** + - 决定添加到哪个菜单(应用菜单、上下文菜单或托盘菜单) + - 确定在菜单中的位置(主菜单项或子菜单项) + +2. **定义菜单项** + ```typescript + const newMenuItem: MenuItemConstructorOptions = { + label: '新功能', + accelerator: 'CmdOrCtrl+N', + click: (_, window) => { + // 处理点击事件 + if (window) window.webContents.send('trigger-new-feature'); + } + }; + ``` + +3. **添加到菜单模板** + 将新菜单项添加到相应的菜单模板中 + +4. **对于与渲染进程交互的功能** + - 使用 `window.webContents.send()` 发送 IPC 消息到渲染进程 + - 在渲染进程中监听该消息并处理 + +## 菜单项启用/禁用控制 + +动态控制菜单项状态: + +1. **保存对菜单项的引用** + ```typescript + this.menuItems = {}; + const menu = Menu.buildFromTemplate(template); + this.menuItems.newFeature = menu.getMenuItemById('new-feature'); + ``` + +2. **根据条件更新状态** + ```typescript + updateMenuState(state) { + if (this.menuItems.newFeature) { + this.menuItems.newFeature.enabled = state.canUseNewFeature; + } + } + ``` + +## 最佳实践 + +1. **使用标准角色** + - 尽可能使用 Electron 预定义的角色(如 `role: 'copy'`)以获得本地化和一致的行为 + +2. **平台特定菜单** + - 使用 `process.platform` 检查为不同平台提供不同菜单 + ```typescript + if (process.platform === 'darwin') { + template.unshift({ role: 'appMenu' }); + } + ``` + +3. **快捷键冲突** + - 避免与系统快捷键冲突 + - 使用 `CmdOrCtrl` 代替 `Ctrl` 以支持 macOS 和 Windows/Linux + +4. **保持菜单简洁** + - 避免过多嵌套的子菜单 + - 将相关功能分组在一起 + +5. **添加分隔符** + - 使用 `{ type: 'separator' }` 在逻辑上分隔不同组的菜单项 diff --git a/.cursor/rules/desktop-window-management.mdc b/.cursor/rules/desktop-window-management.mdc new file mode 100644 index 0000000000..bd11280d04 --- /dev/null +++ b/.cursor/rules/desktop-window-management.mdc @@ -0,0 +1,296 @@ +--- +description: +globs: +alwaysApply: false +--- +**桌面端窗口管理指南** + +## 窗口管理概述 + +LobeChat 桌面应用使用 Electron 的 `BrowserWindow` 管理应用窗口。主要的窗口管理功能包括: + +1. **窗口创建和配置** +2. **窗口状态管理**(大小、位置、最大化等) +3. **多窗口协调** +4. **窗口事件处理** + +## 相关文件结构 + +``` +apps/desktop/src/main/ +├── appBrowsers.ts # 窗口管理的核心文件 +├── controllers/ +│ └── BrowserWindowsCtr.ts # 窗口控制器 +└── modules/ + └── browserWindowManager.ts # 窗口管理模块 +``` + +## 窗口管理流程 + +### 1. 窗口创建 + +在 `appBrowsers.ts` 或 `BrowserWindowsCtr.ts` 中定义窗口创建逻辑: + +```typescript +export const createMainWindow = () => { + const mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 600, + minHeight: 400, + webPreferences: { + preload: path.join(__dirname, '../preload/index.js'), + contextIsolation: true, + nodeIntegration: false, + }, + // 其他窗口配置项... + }); + + // 加载应用内容 + if (isDev) { + mainWindow.loadURL('http://localhost:3000'); + mainWindow.webContents.openDevTools(); + } else { + mainWindow.loadFile(path.join(__dirname, '../../renderer/index.html')); + } + + return mainWindow; +}; +``` + +### 2. 窗口状态管理 + +实现窗口状态持久化保存和恢复: + +1. **保存窗口状态** + ```typescript + const saveWindowState = (window: BrowserWindow) => { + if (!window.isMinimized() && !window.isMaximized()) { + const position = window.getPosition(); + const size = window.getSize(); + + settings.set('windowState', { + x: position[0], + y: position[1], + width: size[0], + height: size[1], + }); + } + }; + ``` + +2. **恢复窗口状态** + ```typescript + const restoreWindowState = (window: BrowserWindow) => { + const savedState = settings.get('windowState'); + + if (savedState) { + window.setBounds({ + x: savedState.x, + y: savedState.y, + width: savedState.width, + height: savedState.height, + }); + } + }; + ``` + +3. **监听窗口事件** + ```typescript + window.on('close', () => saveWindowState(window)); + window.on('moved', () => saveWindowState(window)); + window.on('resized', () => saveWindowState(window)); + ``` + +### 3. 实现多窗口管理 + +对于需要多窗口支持的功能: + +1. **跟踪窗口** + ```typescript + export class WindowManager { + private windows: Map = new Map(); + + createWindow(id: string, options: BrowserWindowConstructorOptions) { + const window = new BrowserWindow(options); + this.windows.set(id, window); + + window.on('closed', () => { + this.windows.delete(id); + }); + + return window; + } + + getWindow(id: string) { + return this.windows.get(id); + } + + getAllWindows() { + return Array.from(this.windows.values()); + } + } + ``` + +2. **窗口间通信** + ```typescript + // 从一个窗口向另一个窗口发送消息 + sendMessageToWindow(targetWindowId, channel, data) { + const targetWindow = this.getWindow(targetWindowId); + if (targetWindow) { + targetWindow.webContents.send(channel, data); + } + } + ``` + +### 4. 窗口与渲染进程通信 + +通过 IPC 实现窗口操作: + +1. **在主进程中注册 IPC 处理器** + ```typescript + // BrowserWindowsCtr.ts + @ipcClientEvent('minimizeWindow') + handleMinimizeWindow() { + const focusedWindow = BrowserWindow.getFocusedWindow(); + if (focusedWindow) { + focusedWindow.minimize(); + } + return { success: true }; + } + + @ipcClientEvent('maximizeWindow') + handleMaximizeWindow() { + const focusedWindow = BrowserWindow.getFocusedWindow(); + if (focusedWindow) { + if (focusedWindow.isMaximized()) { + focusedWindow.restore(); + } else { + focusedWindow.maximize(); + } + } + return { success: true }; + } + + @ipcClientEvent('closeWindow') + handleCloseWindow() { + const focusedWindow = BrowserWindow.getFocusedWindow(); + if (focusedWindow) { + focusedWindow.close(); + } + return { success: true }; + } + ``` + +2. **在渲染进程中调用** + ```typescript + // src/services/electron/windowService.ts + import { dispatch } from '@lobechat/electron-client-ipc'; + + export const windowService = { + minimize: () => dispatch('minimizeWindow'), + maximize: () => dispatch('maximizeWindow'), + close: () => dispatch('closeWindow'), + }; + ``` + +### 5. 自定义窗口控制 (无边框窗口) + +对于自定义窗口标题栏: + +1. **创建无边框窗口** + ```typescript + const window = new BrowserWindow({ + frame: false, + titleBarStyle: 'hidden', + // 其他选项... + }); + ``` + +2. **在渲染进程中实现拖拽区域** + ```css + /* CSS */ + .titlebar { + -webkit-app-region: drag; + } + + .titlebar-button { + -webkit-app-region: no-drag; + } + ``` + +## 最佳实践 + +1. **性能考虑** + - 避免创建过多窗口 + - 使用 `show: false` 创建窗口,在内容加载完成后再显示,避免白屏 + +2. **安全性** + - 始终设置适当的 `webPreferences` 确保安全 + ```typescript + webPreferences: { + preload: path.join(__dirname, '../preload/index.js'), + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + } + ``` + +3. **跨平台兼容性** + - 考虑不同操作系统的窗口行为差异 + - 使用 `process.platform` 为不同平台提供特定实现 + +4. **崩溃恢复** + - 监听 `webContents.on('crashed')` 事件处理崩溃 + - 提供崩溃恢复选项 + +5. **内存管理** + - 确保窗口关闭时清理所有相关资源 + - 使用 `window.on('closed')` 而不是 `window.on('close')` 进行最终清理 + +## 示例:创建设置窗口 + +```typescript +// apps/desktop/src/main/controllers/BrowserWindowsCtr.ts + +@ipcClientEvent('openSettings') +handleOpenSettings() { + // 检查设置窗口是否已经存在 + if (this.settingsWindow && !this.settingsWindow.isDestroyed()) { + // 如果窗口已存在,将其置于前台 + this.settingsWindow.focus(); + return { success: true }; + } + + // 创建新窗口 + this.settingsWindow = new BrowserWindow({ + width: 800, + height: 600, + title: 'Settings', + parent: this.mainWindow, // 设置父窗口,使其成为模态窗口 + modal: true, + webPreferences: { + preload: path.join(__dirname, '../preload/index.js'), + contextIsolation: true, + nodeIntegration: false, + }, + }); + + // 加载设置页面 + if (isDev) { + this.settingsWindow.loadURL('http://localhost:3000/settings'); + } else { + this.settingsWindow.loadFile( + path.join(__dirname, '../../renderer/index.html'), + { hash: 'settings' } + ); + } + + // 监听窗口关闭事件 + this.settingsWindow.on('closed', () => { + this.settingsWindow = null; + }); + + return { success: true }; +} +``` diff --git a/apps/desktop/src/main/controllers/SystemCtr.ts b/apps/desktop/src/main/controllers/SystemCtr.ts index 5904d516e6..ded414e98c 100644 --- a/apps/desktop/src/main/controllers/SystemCtr.ts +++ b/apps/desktop/src/main/controllers/SystemCtr.ts @@ -1,4 +1,4 @@ -import { ElectronAppState } from '@lobechat/electron-client-ipc'; +import { ElectronAppState, ThemeMode } from '@lobechat/electron-client-ipc'; import { app, shell, systemPreferences } from 'electron'; import { macOS } from 'electron-is'; import { readFileSync, writeFileSync } from 'node:fs'; @@ -68,6 +68,11 @@ export default class SystemController extends ControllerModule { return { success: true }; } + @ipcClientEvent('updateThemeMode') + async updateThemeModeHandler(themeMode: ThemeMode) { + this.app.browserManager.broadcastToAllWindows('themeChanged', { themeMode }); + } + @ipcServerEvent('getDatabasePath') async getDatabasePath() { return join(this.app.appStoragePath, LOCAL_DATABASE_DIR); diff --git a/packages/electron-client-ipc/src/events/index.ts b/packages/electron-client-ipc/src/events/index.ts index f1bffe1285..9f2ed03354 100644 --- a/packages/electron-client-ipc/src/events/index.ts +++ b/packages/electron-client-ipc/src/events/index.ts @@ -2,7 +2,7 @@ import { LocalFilesDispatchEvents } from './localFile'; import { MenuDispatchEvents } from './menu'; import { RemoteServerBroadcastEvents, RemoteServerDispatchEvents } from './remoteServer'; import { ShortcutDispatchEvents } from './shortcut'; -import { SystemDispatchEvents } from './system'; +import { SystemBroadcastEvents, SystemDispatchEvents } from './system'; import { TrayDispatchEvents } from './tray'; import { AutoUpdateBroadcastEvents, AutoUpdateDispatchEvents } from './update'; import { UploadFilesDispatchEvents } from './upload'; @@ -35,7 +35,8 @@ export type ClientEventReturnType = ReturnType // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface MainBroadcastEvents extends AutoUpdateBroadcastEvents, - RemoteServerBroadcastEvents {} + RemoteServerBroadcastEvents, + SystemBroadcastEvents {} export type MainBroadcastEventKey = keyof MainBroadcastEvents; diff --git a/packages/electron-client-ipc/src/events/system.ts b/packages/electron-client-ipc/src/events/system.ts index eebc69538b..e8912ed4a8 100644 --- a/packages/electron-client-ipc/src/events/system.ts +++ b/packages/electron-client-ipc/src/events/system.ts @@ -1,4 +1,4 @@ -import { ElectronAppState } from '../types'; +import { ElectronAppState, ThemeMode } from '../types'; export interface SystemDispatchEvents { checkSystemAccessibility: () => boolean | undefined; @@ -12,4 +12,9 @@ export interface SystemDispatchEvents { * @param locale 语言设置 */ updateLocale: (locale: string) => { success: boolean }; + updateThemeMode: (themeMode: ThemeMode) => void; +} + +export interface SystemBroadcastEvents { + themeChanged: (data: { themeMode: ThemeMode }) => void; } diff --git a/packages/electron-client-ipc/src/types/system.ts b/packages/electron-client-ipc/src/types/system.ts index 76d232b0d0..3ca866d23e 100644 --- a/packages/electron-client-ipc/src/types/system.ts +++ b/packages/electron-client-ipc/src/types/system.ts @@ -22,3 +22,5 @@ export interface UserPathData { userData: string; videos?: string; // User's home directory } + +export type ThemeMode = 'auto' | 'dark' | 'light'; diff --git a/src/features/ElectronTitlebar/hooks/useWatchThemeUpdate.ts b/src/features/ElectronTitlebar/hooks/useWatchThemeUpdate.ts new file mode 100644 index 0000000000..644467fcf6 --- /dev/null +++ b/src/features/ElectronTitlebar/hooks/useWatchThemeUpdate.ts @@ -0,0 +1,21 @@ +import { useWatchBroadcast } from '@lobechat/electron-client-ipc'; +import { useTheme } from 'antd-style'; +import { rgba } from 'polished'; +import { useEffect } from 'react'; + +import { useGlobalStore } from '@/store/global'; + +export const useWatchThemeUpdate = () => { + const switchThemeMode = useGlobalStore((s) => s.switchThemeMode); + + const token = useTheme(); + + useWatchBroadcast('themeChanged', ({ themeMode }) => { + switchThemeMode(themeMode, { skipBroadcast: true }); + }); + + useEffect(() => { + document.documentElement.style.background = 'none'; + document.body.style.background = rgba(token.colorBgLayout, 0.66); + }, [token]); +}; diff --git a/src/features/ElectronTitlebar/index.tsx b/src/features/ElectronTitlebar/index.tsx index 567b2ca130..bc736c899f 100644 --- a/src/features/ElectronTitlebar/index.tsx +++ b/src/features/ElectronTitlebar/index.tsx @@ -11,6 +11,7 @@ import { UpdateModal } from './UpdateModal'; import { UpdateNotification } from './UpdateNotification'; import WinControl from './WinControl'; import { TITLE_BAR_HEIGHT } from './const'; +import { useWatchThemeUpdate } from './hooks/useWatchThemeUpdate'; const isMac = isMacOS(); @@ -21,6 +22,7 @@ const TitleBar = memo(() => { ]); initElectronAppState(); + useWatchThemeUpdate(); const showWinControl = isAppStateInit && !isMac; return ( diff --git a/src/store/global/actions/general.ts b/src/store/global/actions/general.ts index cb01f61a68..371fab6a86 100644 --- a/src/store/global/actions/general.ts +++ b/src/store/global/actions/general.ts @@ -21,7 +21,7 @@ const n = setNamespace('g'); export interface GlobalGeneralAction { switchLocale: (locale: LocaleMode) => void; - switchThemeMode: (themeMode: ThemeMode) => void; + switchThemeMode: (themeMode: ThemeMode, params?: { skipBroadcast?: boolean }) => void; updateSystemStatus: (status: Partial, action?: any) => void; useCheckLatestVersion: (enabledCheck?: boolean) => SWRResponse; useInitSystemStatus: () => SWRResponse; @@ -50,10 +50,21 @@ export const generalActionSlice: StateCreator< })(); } }, - switchThemeMode: (themeMode) => { + switchThemeMode: (themeMode, { skipBroadcast } = {}) => { get().updateSystemStatus({ themeMode }); setCookie(LOBE_THEME_APPEARANCE, themeMode === 'auto' ? undefined : themeMode); + + if (isDesktop && !skipBroadcast) { + (async () => { + try { + const { dispatch } = await import('@lobechat/electron-client-ipc'); + await dispatch('updateThemeMode', themeMode); + } catch (error) { + console.error('Failed to update theme in main process:', error); + } + })(); + } }, updateSystemStatus: (status, action) => { if (!get().isStatusInit) return; diff --git a/src/styles/global.ts b/src/styles/global.ts index e5ba928d73..4cf41a437d 100644 --- a/src/styles/global.ts +++ b/src/styles/global.ts @@ -1,7 +1,4 @@ import { Theme, css } from 'antd-style'; -import { rgba } from 'polished'; - -import { isDesktop } from '@/const/version'; // fix ios input keyboard // overflow: hidden; @@ -25,15 +22,10 @@ export default ({ token }: { prefixCls: string; token: Theme }) => css` } } - html { - background: ${isDesktop ? 'none' : token.colorBgLayout}; - } - body { /* 提高合成层级,强制硬件加速,否则会有渲染黑边出现 */ will-change: opacity; transform: translateZ(0); - background: ${isDesktop ? rgba(token.colorBgLayout, 0.66) : token.colorBgLayout}; } * {