From 5cd5d6685bce46159ec91d833a4a71d2310a8a86 Mon Sep 17 00:00:00 2001 From: Xu Liang Date: Fri, 1 May 2026 18:02:52 +0800 Subject: [PATCH] feat(desktop-client): adapt title bar and drag region for Windows/Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add appTitleBarStyle() and appTitleBarOverlay() helpers for cross-platform title bar - Use titleBarOverlay on Windows/Linux so system caption buttons render correctly - Disable minimize button on login window (non-resizable, no need to minimize) - Detect platform at renderer startup and set data-platform on for CSS targeting - DesktopShell: flip drag bar layout — full-width minus caption area on Win/Linux, traffic-light gap on macOS - LoginView: move top-bar to Win/Linux caption area (top: 34px), mirror to left on macOS Co-Authored-By: Claude Sonnet 4.6 --- apps/desktop-client/src/main/bootstrap.ts | 30 ++++++++++++++++--- apps/desktop-client/src/renderer/App.vue | 2 ++ .../src/renderer/components/DesktopShell.vue | 10 +++++-- .../src/renderer/views/LoginView.vue | 9 ++++-- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/apps/desktop-client/src/main/bootstrap.ts b/apps/desktop-client/src/main/bootstrap.ts index 888967e..a3c77c0 100644 --- a/apps/desktop-client/src/main/bootstrap.ts +++ b/apps/desktop-client/src/main/bootstrap.ts @@ -132,6 +132,23 @@ const SETTINGS_WINDOW_SIZE: WindowSizePreset = { resizable: true, }; +const TITLE_BAR_OVERLAY_HEIGHT = 24; + +function appTitleBarStyle(): "hidden" | "hiddenInset" { + return process.platform === "darwin" ? "hiddenInset" : "hidden"; +} + +function appTitleBarOverlay(color: string): { color: string; symbolColor: string; height: number } | undefined { + if (process.platform === "darwin") { + return undefined; + } + return { + color, + symbolColor: nativeTheme.shouldUseDarkColors ? "#f8fafc" : "#111827", + height: TITLE_BAR_OVERLAY_HEIGHT, + }; +} + function suppressNativeWindowMenu(window?: ElectronBrowserWindow): void { if (process.platform === "darwin") { return; @@ -459,6 +476,7 @@ async function mountRendererWebContents(window: ElectronBrowserWindow, mode: Ren async function createAppWindow(mode: WindowMode): Promise { const initial = WINDOW_SIZES[mode]; + const backgroundColor = mode === "login" ? "#eaf2ff" : nativeTheme.shouldUseDarkColors ? "#15191a" : "#f0f2f5"; const window = new BrowserWindow({ width: initial.width, height: initial.height, @@ -467,13 +485,15 @@ async function createAppWindow(mode: WindowMode): Promise show: false, title: "省心推", icon: createDesktopHealthIcon("normal"), - titleBarStyle: "hiddenInset", + titleBarStyle: appTitleBarStyle(), + titleBarOverlay: appTitleBarOverlay(mode === "login" ? "#00000000" : backgroundColor), trafficLightPosition: { x: 12, y: 14 }, resizable: initial.resizable, + minimizable: mode !== "login", maximizable: initial.resizable, fullscreenable: initial.resizable, autoHideMenuBar: true, - backgroundColor: mode === "login" ? "#eaf2ff" : nativeTheme.shouldUseDarkColors ? "#15191a" : "#f0f2f5", + backgroundColor, webPreferences: { preload: preloadPath(), sandbox: true, @@ -536,6 +556,7 @@ async function createAppWindow(mode: WindowMode): Promise async function createSettingsWindow(): Promise { const initial = SETTINGS_WINDOW_SIZE; + const backgroundColor = nativeTheme.shouldUseDarkColors ? "#15191a" : "#f2f4f7"; const window = new BrowserWindow({ width: initial.width, height: initial.height, @@ -544,13 +565,14 @@ async function createSettingsWindow(): Promise { show: false, title: "省心推设置", icon: createDesktopHealthIcon("normal"), - titleBarStyle: "hiddenInset", + titleBarStyle: appTitleBarStyle(), + titleBarOverlay: appTitleBarOverlay(backgroundColor), trafficLightPosition: { x: 14, y: 15 }, resizable: initial.resizable, maximizable: initial.resizable, fullscreenable: false, autoHideMenuBar: true, - backgroundColor: nativeTheme.shouldUseDarkColors ? "#15191a" : "#f2f4f7", + backgroundColor, webPreferences: { preload: preloadPath(), sandbox: true, diff --git a/apps/desktop-client/src/renderer/App.vue b/apps/desktop-client/src/renderer/App.vue index 85d0109..abe7e0b 100644 --- a/apps/desktop-client/src/renderer/App.vue +++ b/apps/desktop-client/src/renderer/App.vue @@ -33,6 +33,8 @@ function syncWindowMode(authenticated: boolean): void { const rendererWindowMode = resolveRendererWindowMode(); document.documentElement.dataset.windowMode = rendererWindowMode; +document.documentElement.dataset.platform = + navigator.platform.toLowerCase().includes("mac") ? "darwin" : "win32"; const usesAutomaticWindowMode = rendererWindowMode === "auto"; const showsSettingsSurface = rendererWindowMode === "settings"; const showsMainSurface = computed(() => diff --git a/apps/desktop-client/src/renderer/components/DesktopShell.vue b/apps/desktop-client/src/renderer/components/DesktopShell.vue index 7b4d302..e992db3 100644 --- a/apps/desktop-client/src/renderer/components/DesktopShell.vue +++ b/apps/desktop-client/src/renderer/components/DesktopShell.vue @@ -170,11 +170,17 @@ function openSettingsWindow() { .window-drag-bar { position: fixed; top: 0; + left: 0; + right: 144px; + height: 24px; + z-index: 1000; + -webkit-app-region: drag; +} + +:global(html[data-platform="darwin"]) .window-drag-bar { left: 80px; right: 0; height: 28px; - z-index: 1000; - -webkit-app-region: drag; } .app-shell :where(button, a, input, select, textarea, [role="button"], [role="link"]) { diff --git a/apps/desktop-client/src/renderer/views/LoginView.vue b/apps/desktop-client/src/renderer/views/LoginView.vue index 894cac9..25cf948 100644 --- a/apps/desktop-client/src/renderer/views/LoginView.vue +++ b/apps/desktop-client/src/renderer/views/LoginView.vue @@ -260,12 +260,17 @@ onBeforeUnmount(() => { .top-bar { position: absolute; - top: 10px; - right: 10px; + top: 34px; + right: 14px; z-index: 10; -webkit-app-region: no-drag; } +:global(html[data-platform="darwin"]) .top-bar { + left: 52px; + right: auto; +} + .menu-trigger { width: 28px; height: 28px;