Skip to content

Commit c6b2a3f

Browse files
committed
improvement(desktop): 优化标题栏和页面背景
- Electron 改用 hidden titleBarOverlay 保留原生窗口控制按钮 - 增加标题栏主题同步 IPC,随明暗主题更新按钮颜色 - 桌面标题栏在 overlay 模式下隐藏自绘窗口按钮 - 按页面类型区分应用外壳背景,年度总结使用独立暖色底色 - 设置弹窗改为并行刷新,减少打开时阻塞
1 parent 1985281 commit c6b2a3f

8 files changed

Lines changed: 120 additions & 63 deletions

File tree

desktop/src/main.cjs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const {
3434
const DEFAULT_BACKEND_HOST = "127.0.0.1";
3535
const LAN_BACKEND_HOST = "0.0.0.0";
3636
const DEFAULT_BACKEND_PORT = parsePort(process.env.WECHAT_TOOL_PORT) ?? 10392;
37+
const DESKTOP_TITLEBAR_HEIGHT = 32;
3738

3839
let backendProc = null;
3940
let wcdbSidecarProc = null;
@@ -49,6 +50,32 @@ let backendPortChangeInProgress = false;
4950
let outputDirChangeInProgress = false;
5051
let outputDirChangeProgressState = null;
5152

53+
function normalizeTitleBarTheme(value) {
54+
return String(value || "").trim().toLowerCase() === "dark" ? "dark" : "light";
55+
}
56+
57+
function getTitleBarOverlayOptions(theme) {
58+
const normalized = normalizeTitleBarTheme(theme);
59+
return {
60+
// Keep native window controls, but let the renderer/theme show through instead of
61+
// Electron painting the default gray title-bar strip behind the buttons.
62+
color: "rgba(0, 0, 0, 0)",
63+
symbolColor: normalized === "dark" ? "#d0d0d0" : "#111111",
64+
height: DESKTOP_TITLEBAR_HEIGHT,
65+
};
66+
}
67+
68+
function setWindowTitleBarTheme(win, theme) {
69+
if (!win || typeof win.setTitleBarOverlay !== "function") return false;
70+
try {
71+
win.setTitleBarOverlay(getTitleBarOverlayOptions(theme));
72+
return true;
73+
} catch (err) {
74+
logMain(`[main] setTitleBarOverlay failed: ${err?.message || err}`);
75+
return false;
76+
}
77+
}
78+
5279
const gotSingleInstanceLock = app.requestSingleInstanceLock();
5380
if (!gotSingleInstanceLock) {
5481
// If we allow a second instance to boot it will try to spawn another backend on the same port.
@@ -2207,7 +2234,8 @@ function createMainWindow() {
22072234
height: 800,
22082235
minWidth: 980,
22092236
minHeight: 700,
2210-
frame: false,
2237+
titleBarStyle: "hidden",
2238+
titleBarOverlay: getTitleBarOverlayOptions("light"),
22112239
backgroundColor: "#EDEDED",
22122240
webPreferences: {
22132241
preload: path.join(__dirname, "preload.cjs"),
@@ -2295,6 +2323,11 @@ function registerWindowIpc() {
22952323
return !!win?.isMaximized();
22962324
});
22972325

2326+
ipcMain.handle("window:setTitleBarTheme", (event, theme) => {
2327+
const win = getWin(event);
2328+
return setWindowTitleBarTheme(win, theme);
2329+
});
2330+
22982331
ipcMain.handle("app:getAutoLaunch", () => {
22992332
try {
23002333
const settings = app.getLoginItemSettings();

desktop/src/preload.cjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@ if (typeof window !== "undefined") {
6363
contextBridge.exposeInMainWorld("wechatDesktop", {
6464
// Marker used by the frontend to distinguish the Electron desktop shell from the pure web build.
6565
__brand: "WeChatDataAnalysisDesktop",
66+
windowControlsMode: "overlay",
6667
minimize: () => ipcRenderer.invoke("window:minimize"),
6768
toggleMaximize: () => ipcRenderer.invoke("window:toggleMaximize"),
6869
close: () => ipcRenderer.invoke("window:close"),
6970
isMaximized: () => ipcRenderer.invoke("window:isMaximized"),
71+
setTitleBarTheme: (theme) => ipcRenderer.invoke("window:setTitleBarTheme", String(theme || "")),
7072
isDebugEnabled: () => ipcRenderer.invoke("app:isDebugEnabled"),
7173
logDebug: (scope, message, details = {}) => sendDebugLog(scope, message, details),
7274

frontend/app.vue

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,31 @@ onBeforeUnmount(() => {
8484
window.removeEventListener('resize', updateDprVar)
8585
})
8686
87+
const setupShellBackgroundRoutes = new Set([
88+
'/',
89+
'/import',
90+
'/decrypt',
91+
'/detection-result',
92+
'/decrypt-result'
93+
])
94+
95+
const useSetupShellBackground = computed(() => {
96+
const path = String(route.path || '')
97+
return setupShellBackgroundRoutes.has(path)
98+
})
99+
100+
const useWrappedShellBackground = computed(() => {
101+
const path = String(route.path || '')
102+
return path === '/wrapped' || path.startsWith('/wrapped/')
103+
})
104+
87105
const rootClass = computed(() => {
88-
const base = 'theme-app-shell'
106+
let base = 'theme-app-shell'
107+
if (useSetupShellBackground.value) {
108+
base += ' theme-app-shell-setup'
109+
} else if (useWrappedShellBackground.value) {
110+
base += ' theme-app-shell-wrapped'
111+
}
89112
return isDesktop.value
90113
? `wechat-desktop h-screen flex overflow-hidden ${base}`
91114
: `h-screen flex overflow-hidden ${base}`
@@ -116,7 +139,7 @@ const showSidebar = computed(() => {
116139
--sidebar-rail-icon: 24px;
117140
}
118141
119-
/* Electron 桌面端使用自绘标题栏(frame: false)
142+
/* Electron 桌面端使用隐藏标题栏 + 原生窗口控制按钮 overlay
120143
* 页面里如果继续用 Tailwind 的 h-screen/min-h-screen(100vh),会把标题栏高度叠加进去,从而出现外层滚动条。
121144
* 这里把 “screen” 在桌面端视为内容区高度(100%),让标题栏高度自然内嵌在布局里。 */
122145
.wechat-desktop {
@@ -135,13 +158,23 @@ const showSidebar = computed(() => {
135158
}
136159
137160
.theme-app-shell {
161+
background: var(--app-shell-bg);
162+
}
163+
164+
.theme-app-shell-setup {
138165
background:
139166
radial-gradient(circle at top left, rgba(7, 193, 96, 0.08), transparent 32%),
140167
radial-gradient(circle at top right, rgba(16, 174, 239, 0.08), transparent 36%),
141168
linear-gradient(135deg, #f0fdf4 0%, #ecfdf5 45%, #dcfce7 100%);
142169
}
143170
144-
html[data-theme='dark'] .theme-app-shell {
171+
.theme-app-shell-wrapped {
172+
background: #fffdf7;
173+
}
174+
175+
html[data-theme='dark'] .theme-app-shell,
176+
html[data-theme='dark'] .theme-app-shell-setup,
177+
html[data-theme='dark'] .theme-app-shell-wrapped {
145178
background: var(--app-shell-bg);
146179
}
147180
</style>

frontend/components/DesktopTitleBar.vue

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div v-if="isDesktop" class="desktop-titlebar" @dblclick="toggleMaximize">
33
<div class="flex-1" />
44

5-
<div class="desktop-titlebar-controls">
5+
<div v-if="!usesNativeWindowControls" class="desktop-titlebar-controls">
66
<button
77
class="desktop-titlebar-btn"
88
type="button"
@@ -39,9 +39,12 @@
3939
<script setup>
4040
// Keep SSR/client initial DOM consistent; enable desktop titlebar after mount.
4141
const isDesktop = ref(false)
42+
const usesNativeWindowControls = ref(false)
4243
4344
onMounted(() => {
44-
isDesktop.value = !!window?.wechatDesktop
45+
const api = window?.wechatDesktop
46+
isDesktop.value = !!api
47+
usesNativeWindowControls.value = api?.windowControlsMode === 'overlay'
4548
})
4649
4750
const minimize = () => {
@@ -60,7 +63,7 @@ const closeWindow = () => {
6063
<style scoped>
6164
.desktop-titlebar {
6265
height: var(--desktop-titlebar-height, 32px);
63-
background: var(--desktop-titlebar-bg);
66+
background: transparent;
6467
display: flex;
6568
align-items: stretch;
6669
flex-shrink: 0;
@@ -92,19 +95,19 @@ const closeWindow = () => {
9295
}
9396
9497
.desktop-titlebar-btn:hover {
95-
background: var(--desktop-titlebar-hover);
98+
background: transparent;
9699
}
97100
98101
.desktop-titlebar-btn:active {
99-
background: var(--desktop-titlebar-active);
102+
background: transparent;
100103
}
101104
102105
.desktop-titlebar-btn-close:hover {
103-
background: #e81123;
106+
background: transparent;
104107
}
105108
106109
.desktop-titlebar-btn-close:active {
107-
background: #c50f1f;
110+
background: transparent;
108111
}
109112
110113
.desktop-titlebar-icon {
@@ -158,6 +161,6 @@ const closeWindow = () => {
158161
159162
.desktop-titlebar-btn-close:hover .desktop-titlebar-icon-close::before,
160163
.desktop-titlebar-btn-close:hover .desktop-titlebar-icon-close::after {
161-
background: #fff;
164+
background: #e81123;
162165
}
163166
</style>

frontend/components/SettingsDialog.vue

Lines changed: 31 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -363,25 +363,6 @@
363363
<section ref="startupSectionRef">
364364
<div class="mb-2.5 text-[12px] font-bold text-[#999] tracking-widest">启动偏好</div>
365365
<div class="overflow-hidden rounded-[10px] border border-[#e7e7e7] bg-white divide-y divide-[#ececec]">
366-
<div class="px-3.5 py-3">
367-
<div class="flex items-center justify-between gap-3">
368-
<div class="min-w-0 flex-1">
369-
<div class="text-[13px] font-medium text-[#222]">启动后自动开启实时获取</div>
370-
<div class="mt-0.5 text-[11px] text-[#909090]">进入聊天页后自动打开“实时开关”</div>
371-
</div>
372-
<button
373-
type="button"
374-
role="switch"
375-
:aria-checked="desktopAutoRealtime"
376-
class="settings-switch shrink-0"
377-
:class="switchTrackClass(desktopAutoRealtime)"
378-
@click="toggleDesktopAutoRealtime"
379-
>
380-
<span class="settings-switch-thumb" :class="desktopAutoRealtime ? 'translate-x-[20px]' : 'translate-x-0'" />
381-
</button>
382-
</div>
383-
</div>
384-
385366
<div class="px-3.5 py-3">
386367
<div class="flex items-center justify-between gap-3">
387368
<div class="min-w-0 flex-1">
@@ -459,7 +440,7 @@
459440
</template>
460441

461442
<script setup>
462-
import { DESKTOP_SETTING_AUTO_REALTIME_KEY, DESKTOP_SETTING_DEFAULT_TO_CHAT_KEY, SNS_SETTING_USE_CACHE_KEY, readLocalBoolSetting, writeLocalBoolSetting } from '~/lib/desktop-settings'
443+
import { DESKTOP_SETTING_DEFAULT_TO_CHAT_KEY, SNS_SETTING_USE_CACHE_KEY, readLocalBoolSetting, writeLocalBoolSetting } from '~/lib/desktop-settings'
463444
import { readApiBaseOverride, writeApiBaseOverride } from '~/lib/api-settings'
464445
import { invalidateApiBaseCache } from '~/composables/useApiBase'
465446
import { reportServerErrorFromError } from '~/lib/server-error-logging'
@@ -472,11 +453,12 @@ const props = defineProps({
472453
})
473454
474455
const emit = defineEmits(['close'])
456+
const api = useApi()
475457
476458
const settingNavItems = [
477459
{ key: 'desktop', label: '桌面行为', hint: '启动 / 关闭 / 端口' },
478460
{ key: 'mcp', label: 'MCP 接入', hint: '手机 / Skill / 工具' },
479-
{ key: 'startup', label: '启动偏好', hint: '自动实时 / 默认页面' },
461+
{ key: 'startup', label: '启动偏好', hint: '默认页面' },
480462
{ key: 'updates', label: '更新', hint: '版本信息 / 检查更新' },
481463
{ key: 'sns', label: '朋友圈', hint: '图片缓存策略' },
482464
]
@@ -498,7 +480,6 @@ const desktopVersionText = computed(() => {
498480
return v || ''
499481
})
500482
501-
const desktopAutoRealtime = ref(false)
502483
const desktopDefaultToChatWhenData = ref(false)
503484
const snsUseCache = ref(true)
504485
@@ -1261,12 +1242,6 @@ const onDesktopBackendPortReset = async () => {
12611242
await applyDesktopBackendPort()
12621243
}
12631244
1264-
const toggleDesktopAutoRealtime = () => {
1265-
const next = !desktopAutoRealtime.value
1266-
desktopAutoRealtime.value = next
1267-
writeLocalBoolSetting(DESKTOP_SETTING_AUTO_REALTIME_KEY, next)
1268-
}
1269-
12701245
const toggleDesktopDefaultToChat = () => {
12711246
const next = !desktopDefaultToChatWhenData.value
12721247
desktopDefaultToChatWhenData.value = next
@@ -1283,17 +1258,35 @@ const onDesktopCheckUpdates = async () => {
12831258
await desktopUpdate.manualCheck()
12841259
}
12851260
1286-
watch(() => props.open, async (isOpen) => {
1287-
if (!isOpen) return
1288-
await refreshMcpLanAccess()
1289-
await refreshMcpToken()
1290-
await refreshMcpSkillBundle()
1291-
await refreshBackendLogFileInfo()
1261+
const refreshSettingsDialogData = async () => {
1262+
if (!process.client || typeof window === 'undefined') return
1263+
1264+
const tasks = [
1265+
refreshDesktopBackendPort(),
1266+
refreshMcpLanAccess(),
1267+
refreshMcpToken(),
1268+
refreshBackendLogFileInfo(),
1269+
]
1270+
1271+
12921272
if (isDesktopEnv.value) {
1293-
await refreshDesktopOutputDir()
1294-
await refreshDesktopOutputDirProgress()
1273+
void desktopUpdate.initListeners()
1274+
tasks.push(refreshDesktopAutoLaunch())
1275+
tasks.push(refreshDesktopCloseBehavior())
1276+
tasks.push(refreshDesktopOutputDir())
1277+
tasks.push(refreshDesktopOutputDirProgress())
12951278
}
1296-
}, { immediate: true })
1279+
1280+
await Promise.allSettled(tasks)
1281+
1282+
// skill bundle 依赖 token / access host;先让弹窗可交互,再后台补齐这块文本。
1283+
void refreshMcpSkillBundle()
1284+
}
1285+
1286+
watch(() => props.open, async (isOpen) => {
1287+
if (!isOpen) return
1288+
await refreshSettingsDialogData()
1289+
}, { immediate: false })
12971290
12981291
onMounted(async () => {
12991292
if (process.client && typeof window !== 'undefined') {
@@ -1307,21 +1300,10 @@ onMounted(async () => {
13071300
}
13081301
}
13091302
1310-
desktopAutoRealtime.value = readLocalBoolSetting(DESKTOP_SETTING_AUTO_REALTIME_KEY, false)
13111303
desktopDefaultToChatWhenData.value = readLocalBoolSetting(DESKTOP_SETTING_DEFAULT_TO_CHAT_KEY, false)
13121304
snsUseCache.value = readLocalBoolSetting(SNS_SETTING_USE_CACHE_KEY, true)
13131305
1314-
await refreshDesktopBackendPort()
1315-
await refreshMcpLanAccess()
1316-
await refreshMcpToken()
1317-
await refreshMcpSkillBundle()
1318-
if (isDesktopEnv.value) {
1319-
void desktopUpdate.initListeners()
1320-
await refreshDesktopAutoLaunch()
1321-
await refreshDesktopCloseBehavior()
1322-
await refreshDesktopOutputDir()
1323-
await refreshDesktopOutputDirProgress()
1324-
}
1306+
if (props.open) await refreshSettingsDialogData()
13251307
13261308
await nextTick()
13271309
onContentScroll()

frontend/components/wrapped/shared/WrappedDeckBackground.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<!-- Shared backdrop for modern "Wrapped" slides (keeps cover + cards visually consistent). -->
3-
<div class="absolute inset-0 pointer-events-none select-none z-0" aria-hidden="true">
3+
<div class="fixed inset-0 pointer-events-none select-none z-0" aria-hidden="true">
44
<!-- Soft color blobs (brand + warm highlights) -->
55
<div class="absolute -top-24 -left-24 w-80 h-80 bg-[#07C160] opacity-[0.08] rounded-full blur-3xl"></div>
66
<div class="absolute -top-24 -right-24 w-96 h-96 bg-[#F2AA00] opacity-[0.06] rounded-full blur-3xl"></div>

frontend/lib/ui-theme.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,7 @@ export const applyUiTheme = (theme) => {
3333
root.dataset.theme = normalized
3434
root.classList.toggle('theme-dark', normalized === UI_THEME_DARK)
3535
root.style.colorScheme = normalized === UI_THEME_DARK ? 'dark' : 'light'
36+
try {
37+
window.wechatDesktop?.setTitleBarTheme?.(normalized)
38+
} catch {}
3639
}

frontend/pages/wrapped/index.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,8 @@ const slides = computed(() => {
303303
return out
304304
})
305305
306-
const currentBg = '#F3FFF8'
306+
// 年度总结使用独立的暖纸面底色,避免继承聊天页灰底或引导页绿底。
307+
const currentBg = '#FFFDF7'
307308
const deckTrackClass = computed(() => 'z-10')
308309
309310
const applyViewportBg = () => {

0 commit comments

Comments
 (0)