From d2b86c59917f885e4cffdb37a2f90ec437af2a96 Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Sat, 13 Jun 2026 16:41:54 +0800 Subject: [PATCH] feat(dashboard): add a configurable theme mode with light, dark, and system options and centralize theme synchronization with system preferences. (#8648) * Update main.ts * Update config.ts * Update header.json * Update auth.json * Update header.json * Update auth.json * Update header.json * Update auth.json * Update VerticalHeader.vue * Update customizer.ts * Update LoginPage.vue * Update SetupPage.vue * Update config.ts * Update customizer.ts * Update plugin-pages.md * docs: add Follow System note to plugin-pages theme section --- dashboard/src/config.ts | 45 +++++-- .../src/i18n/locales/en-US/core/header.json | 4 +- .../src/i18n/locales/en-US/features/auth.json | 8 +- .../src/i18n/locales/ru-RU/core/header.json | 4 +- .../src/i18n/locales/ru-RU/features/auth.json | 6 +- .../src/i18n/locales/zh-CN/core/header.json | 4 +- .../src/i18n/locales/zh-CN/features/auth.json | 6 +- .../full/vertical-header/VerticalHeader.vue | 115 +++++++++++++----- dashboard/src/main.ts | 89 ++++++++------ dashboard/src/stores/customizer.ts | 27 +++- .../views/authentication/auth/LoginPage.vue | 77 ++++++++++-- .../views/authentication/auth/SetupPage.vue | 78 ++++++++++-- docs/en/dev/star/guides/plugin-pages.md | 2 + docs/zh/dev/star/guides/plugin-pages.md | 2 + 14 files changed, 355 insertions(+), 112 deletions(-) diff --git a/dashboard/src/config.ts b/dashboard/src/config.ts index f52812ad8..99d598276 100644 --- a/dashboard/src/config.ts +++ b/dashboard/src/config.ts @@ -1,28 +1,55 @@ +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; +function checkThemeMode(): ThemeMode { + const mode = localStorage.getItem('themeMode') as ThemeMode | null; + if (mode === 'light' || mode === 'dark' || mode === 'system') return mode; + + 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'; } +export function resolveUiTheme(mode: ThemeMode): string { + if (mode === 'dark') return 'PurpleThemeDark'; + if (mode === 'light') return 'PurpleTheme'; + const prefersDark = + typeof window !== 'undefined' && + window.matchMedia('(prefers-color-scheme: dark)').matches; + return prefersDark ? 'PurpleThemeDark' : 'PurpleTheme'; +} + +const themeMode = checkThemeMode(); +const uiTheme = resolveUiTheme(themeMode); + +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; diff --git a/dashboard/src/i18n/locales/en-US/core/header.json b/dashboard/src/i18n/locales/en-US/core/header.json index d83f06152..082bdd7e1 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": { diff --git a/dashboard/src/i18n/locales/en-US/features/auth.json b/dashboard/src/i18n/locales/en-US/features/auth.json index 526c080be..f85cc81e9 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" } -} +} diff --git a/dashboard/src/i18n/locales/ru-RU/core/header.json b/dashboard/src/i18n/locales/ru-RU/core/header.json index 3124cd971..a851897df 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": { diff --git a/dashboard/src/i18n/locales/ru-RU/features/auth.json b/dashboard/src/i18n/locales/ru-RU/features/auth.json index 97f2a71f7..9c5f9f638 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": "Тема" } } diff --git a/dashboard/src/i18n/locales/zh-CN/core/header.json b/dashboard/src/i18n/locales/zh-CN/core/header.json index 8897ad0af..611213b78 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": { diff --git a/dashboard/src/i18n/locales/zh-CN/features/auth.json b/dashboard/src/i18n/locales/zh-CN/features/auth.json index ad886f105..163143713 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": "主题" } } diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue index c41b566e7..c34f2c426 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 () => { - - + -