From 96d296356e294e6e436516433cd258d81652f418 Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Sun, 7 Jun 2026 14:57:10 +0800 Subject: [PATCH 01/16] Update main.ts --- dashboard/src/main.ts | 89 ++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/dashboard/src/main.ts b/dashboard/src/main.ts index ce5514207c..021e972146 100644 --- a/dashboard/src/main.ts +++ b/dashboard/src/main.ts @@ -47,25 +47,31 @@ import { waitForRouterReadyInBackground } from './utils/routerReadiness.mjs'; }, }; -// 初始化新的i18n系统,等待完成后再挂载应用 -setupI18n().then(async () => { - console.log('🌍 新i18n系统初始化完成'); - - const app = createApp(App); - const pinia = createPinia(); - app.use(pinia); - app.use(router); - app.use(print); - app.use(VueApexCharts); - app.use(vuetify); - app.use(confirmPlugin); - await router.isReady(); - app.mount('#app'); - - // 挂载后同步 Vuetify 主题 +/** + * 挂载后初始化主题并注册全局系统主题监听器。 + * 职责: + * - 同步 Vuetify theme 名称与 store 中的 uiTheme + * - 当 themeMode === 'system' 时,监听系统色彩模式变化,实时更新两者 + * - 应用自定义 primary/secondary 色 + * 注意:VerticalHeader.vue / ThemeSwitcher.vue 不再自行注册 matchMedia 监听器, + * 避免与此处产生竞态。 + */ +function setupThemeSync(pinia: ReturnType) { import('./stores/customizer').then(({ useCustomizerStore }) => { const customizer = useCustomizerStore(pinia); + + // 1. 若当前是 system 模式,重新用 matchMedia 计算,防止 SSR / 构建时偏差 + if (customizer.themeMode === 'system') { + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + const uiTheme = prefersDark ? 'PurpleThemeDark' : 'PurpleTheme'; + customizer.uiTheme = uiTheme; + localStorage.setItem('uiTheme', uiTheme); + } + + // 2. 将 Vuetify 主题对齐到 store vuetify.theme.global.name.value = customizer.uiTheme; + + // 3. 应用用户自定义色 const storedPrimary = localStorage.getItem('themePrimary'); const storedSecondary = localStorage.getItem('themeSecondary'); if (storedPrimary || storedSecondary) { @@ -79,10 +85,38 @@ setupI18n().then(async () => { if (storedSecondary && theme.colors.darksecondary) theme.colors.darksecondary = storedSecondary; }); } + + // 4. 全局唯一 matchMedia 监听器:仅在 system 模式下响应系统切换 + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + mediaQuery.addEventListener('change', (e) => { + if (customizer.themeMode !== 'system') return; + const uiTheme = e.matches ? 'PurpleThemeDark' : 'PurpleTheme'; + customizer.uiTheme = uiTheme; + localStorage.setItem('uiTheme', uiTheme); + vuetify.theme.global.name.value = uiTheme; + }); }); +} + +// 初始化新的i18n系统,等待完成后再挂载应用 +setupI18n().then(async () => { + console.log('🌍 新i18n系统初始化完成'); + + const app = createApp(App); + const pinia = createPinia(); + app.use(pinia); + app.use(router); + app.use(print); + app.use(VueApexCharts); + app.use(vuetify); + app.use(confirmPlugin); + await router.isReady(); + app.mount('#app'); + + setupThemeSync(pinia); }).catch(error => { console.error('❌ 新i18n系统初始化失败:', error); - + // 即使i18n初始化失败,也要挂载应用(使用回退机制) const app = createApp(App); const pinia = createPinia(); @@ -94,25 +128,8 @@ setupI18n().then(async () => { app.use(confirmPlugin); app.mount('#app'); waitForRouterReadyInBackground(router); - - // 挂载后同步 Vuetify 主题 - import('./stores/customizer').then(({ useCustomizerStore }) => { - const customizer = useCustomizerStore(pinia); - vuetify.theme.global.name.value = customizer.uiTheme; - const storedPrimary = localStorage.getItem('themePrimary'); - const storedSecondary = localStorage.getItem('themeSecondary'); - if (storedPrimary || storedSecondary) { - const themes = vuetify.theme.themes.value; - ['PurpleTheme', 'PurpleThemeDark'].forEach((name) => { - const theme = themes[name]; - if (!theme?.colors) return; - if (storedPrimary) theme.colors.primary = storedPrimary; - if (storedSecondary) theme.colors.secondary = storedSecondary; - if (storedPrimary && theme.colors.darkprimary) theme.colors.darkprimary = storedPrimary; - if (storedSecondary && theme.colors.darksecondary) theme.colors.darksecondary = storedSecondary; - }); - } - }); + + setupThemeSync(pinia); }); From 1e4d9e444c748a2d17d5215909917bf8aea1cbf3 Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Sun, 7 Jun 2026 14:57:34 +0800 Subject: [PATCH 02/16] Update config.ts --- dashboard/src/config.ts | 50 +++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/dashboard/src/config.ts b/dashboard/src/config.ts index f52812ad8e..01821428f5 100644 --- a/dashboard/src/config.ts +++ b/dashboard/src/config.ts @@ -1,28 +1,60 @@ +export type ThemeMode = 'light' | 'dark' | 'system'; + export type ConfigProps = { Sidebar_drawer: boolean; Customizer_drawer: boolean; mini_sidebar: boolean; fontTheme: string; uiTheme: string; + themeMode: ThemeMode; inputBg: boolean; }; -function checkUITheme() { - /* 检查localStorage有无记忆的主题选项,如有则使用,否则使用默认值 */ - const theme = localStorage.getItem("uiTheme"); - if (!theme || !(['PurpleTheme', 'PurpleThemeDark'].includes(theme))) { - localStorage.setItem("uiTheme", "PurpleTheme"); // todo: 这部分可以根据vuetify.ts的默认主题动态调整 - return 'PurpleTheme'; - } else return theme; +/** 读 localStorage 中的 themeMode,默认 'system' */ +function checkThemeMode(): ThemeMode { + const mode = localStorage.getItem('themeMode') as ThemeMode | null; + if (mode === 'light' || mode === 'dark' || mode === 'system') return mode; + + // 迁移旧数据:如果存在旧的 uiTheme 但没有 themeMode,按旧值推断意图 + const legacyTheme = localStorage.getItem('uiTheme'); + if (legacyTheme === 'PurpleThemeDark') { + localStorage.setItem('themeMode', 'dark'); + return 'dark'; + } + if (legacyTheme === 'PurpleTheme') { + localStorage.setItem('themeMode', 'light'); + return 'light'; + } + + localStorage.setItem('themeMode', 'system'); + return 'system'; } +/** 根据 themeMode 计算出实际应使用的 Vuetify 主题名 */ +function resolveUiTheme(mode: ThemeMode): string { + if (mode === 'dark') return 'PurpleThemeDark'; + if (mode === 'light') return 'PurpleTheme'; + // system:用 matchMedia 判断当前系统偏好 + const prefersDark = + typeof window !== 'undefined' && + window.matchMedia('(prefers-color-scheme: dark)').matches; + return prefersDark ? 'PurpleThemeDark' : 'PurpleTheme'; +} + +const themeMode = checkThemeMode(); +const uiTheme = resolveUiTheme(themeMode); + +// 保证 uiTheme 在 localStorage 中与计算结果一致 +localStorage.setItem('uiTheme', uiTheme); + const config: ConfigProps = { Sidebar_drawer: true, Customizer_drawer: false, mini_sidebar: false, fontTheme: 'Roboto', - uiTheme: checkUITheme(), - inputBg: false + uiTheme, + themeMode, + inputBg: false, }; export default config; From c05ae246f7b532ae453af37639c7b2db13204ef2 Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Sun, 7 Jun 2026 14:58:26 +0800 Subject: [PATCH 03/16] Update header.json --- dashboard/src/i18n/locales/en-US/core/header.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dashboard/src/i18n/locales/en-US/core/header.json b/dashboard/src/i18n/locales/en-US/core/header.json index d83f06152f..082bdd7e1f 100644 --- a/dashboard/src/i18n/locales/en-US/core/header.json +++ b/dashboard/src/i18n/locales/en-US/core/header.json @@ -8,8 +8,10 @@ "update": "Update", "account": "Account", "theme": { + "title": "Theme", "light": "Light Mode", - "dark": "Dark Mode" + "dark": "Dark Mode", + "system": "Follow System" } }, "updateDialog": { From df019d225ddd96d0d1034b88307be9eac1bc6873 Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Sun, 7 Jun 2026 14:59:21 +0800 Subject: [PATCH 04/16] Update auth.json --- dashboard/src/i18n/locales/en-US/features/auth.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dashboard/src/i18n/locales/en-US/features/auth.json b/dashboard/src/i18n/locales/en-US/features/auth.json index 526c080be0..f85cc81e96 100644 --- a/dashboard/src/i18n/locales/en-US/features/auth.json +++ b/dashboard/src/i18n/locales/en-US/features/auth.json @@ -57,7 +57,11 @@ "subtitle": "Welcome" }, "theme": { + "light": "Light Mode", + "dark": "Dark Mode", + "system": "Follow System", "switchToDark": "Switch to Dark Theme", - "switchToLight": "Switch to Light Theme" + "switchToLight": "Switch to Light Theme", + "title": "Theme" } -} +} From f3c00fae6ab986662b941428e36267366b712ec9 Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Sun, 7 Jun 2026 14:59:55 +0800 Subject: [PATCH 05/16] Update header.json --- dashboard/src/i18n/locales/ru-RU/core/header.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dashboard/src/i18n/locales/ru-RU/core/header.json b/dashboard/src/i18n/locales/ru-RU/core/header.json index 3124cd9719..a851897df7 100644 --- a/dashboard/src/i18n/locales/ru-RU/core/header.json +++ b/dashboard/src/i18n/locales/ru-RU/core/header.json @@ -8,8 +8,10 @@ "update": "Обновить", "account": "Аккаунт", "theme": { + "title": "Тема", "light": "Светлая тема", - "dark": "Темная тема" + "dark": "Темная тема", + "system": "Как в системе" } }, "updateDialog": { From bfdeed231f7464e500fd0e055b934fff8436b9ca Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Sun, 7 Jun 2026 15:00:16 +0800 Subject: [PATCH 06/16] Update auth.json --- dashboard/src/i18n/locales/ru-RU/features/auth.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dashboard/src/i18n/locales/ru-RU/features/auth.json b/dashboard/src/i18n/locales/ru-RU/features/auth.json index 97f2a71f74..9c5f9f638f 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/auth.json +++ b/dashboard/src/i18n/locales/ru-RU/features/auth.json @@ -57,7 +57,11 @@ "subtitle": "Добро пожаловать" }, "theme": { + "light": "Светлая тема", + "dark": "Темная тема", + "system": "Как в системе", "switchToDark": "Перейти на темную тему", - "switchToLight": "Перейти на светлую тему" + "switchToLight": "Перейти на светлую тему", + "title": "Тема" } } From b855f3d99d426538aef4f5d870632fd9d1c534ff Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Sun, 7 Jun 2026 15:01:06 +0800 Subject: [PATCH 07/16] Update header.json --- dashboard/src/i18n/locales/zh-CN/core/header.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dashboard/src/i18n/locales/zh-CN/core/header.json b/dashboard/src/i18n/locales/zh-CN/core/header.json index 8897ad0af5..611213b782 100644 --- a/dashboard/src/i18n/locales/zh-CN/core/header.json +++ b/dashboard/src/i18n/locales/zh-CN/core/header.json @@ -8,8 +8,10 @@ "update": "更新", "account": "账户", "theme": { + "title": "主题", "light": "浅色模式", - "dark": "深色模式" + "dark": "深色模式", + "system": "跟随系统" } }, "updateDialog": { From 7836855fd6161951850534b7cc25d75862a4fb0b Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Sun, 7 Jun 2026 15:01:32 +0800 Subject: [PATCH 08/16] Update auth.json --- dashboard/src/i18n/locales/zh-CN/features/auth.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dashboard/src/i18n/locales/zh-CN/features/auth.json b/dashboard/src/i18n/locales/zh-CN/features/auth.json index ad886f105b..1631437134 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/auth.json +++ b/dashboard/src/i18n/locales/zh-CN/features/auth.json @@ -57,7 +57,11 @@ "subtitle": "欢迎使用" }, "theme": { + "light": "浅色模式", + "dark": "深色模式", + "system": "跟随系统", "switchToDark": "切换到深色主题", - "switchToLight": "切换到浅色主题" + "switchToLight": "切换到浅色主题", + "title": "主题" } } From f3946457f87f475dcf6aab59401acb187974db5a Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Sun, 7 Jun 2026 15:03:23 +0800 Subject: [PATCH 09/16] Update VerticalHeader.vue --- .../full/vertical-header/VerticalHeader.vue | 115 +++++++++++++----- 1 file changed, 87 insertions(+), 28 deletions(-) diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue index c41b566e70..c34f2c4266 100644 --- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue +++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue @@ -725,13 +725,16 @@ function updateDashboard() { }); } -function toggleDarkMode() { - const newTheme = - customizer.uiTheme === "PurpleThemeDark" - ? "PurpleTheme" - : "PurpleThemeDark"; - customizer.SET_UI_THEME(newTheme); - theme.global.name.value = newTheme; +// 主题选项配置 +const themeOptions = [ + { mode: 'light' as const, icon: 'mdi-white-balance-sunny', labelKey: 'core.header.buttons.theme.light' }, + { mode: 'dark' as const, icon: 'mdi-weather-night', labelKey: 'core.header.buttons.theme.dark' }, + { mode: 'system' as const, icon: 'mdi-sync', labelKey: 'core.header.buttons.theme.system' }, +] as const; + +function setThemeMode(mode: 'light' | 'dark' | 'system') { + customizer.SET_THEME_MODE(mode); + theme.global.name.value = customizer.uiTheme; } function openReleaseNotesDialog(body: string, tag: string) { @@ -1077,29 +1080,68 @@ onMounted(async () => { - - + -