From a12e27f9ab1600f133922a5c839f4d542bb0d2ff Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Tue, 3 Feb 2026 14:41:48 +0800
Subject: [PATCH] feat: implement theme customization with primary and
secondary color options
---
.../i18n/locales/en-US/features/settings.json | 12 ++-
.../i18n/locales/zh-CN/features/settings.json | 12 ++-
dashboard/src/main.ts | 28 +++++-
dashboard/src/theme/LightTheme.ts | 4 +-
dashboard/src/views/Settings.vue | 91 ++++++++++++++++++-
5 files changed, 140 insertions(+), 7 deletions(-)
diff --git a/dashboard/src/i18n/locales/en-US/features/settings.json b/dashboard/src/i18n/locales/en-US/features/settings.json
index 9c4c19266..ba035b5ab 100644
--- a/dashboard/src/i18n/locales/en-US/features/settings.json
+++ b/dashboard/src/i18n/locales/en-US/features/settings.json
@@ -16,6 +16,16 @@
"custom": "Custom"
}
},
+ "theme": {
+ "title": "Theme",
+ "subtitle": "Customize theme primary and secondary colors. Changes apply immediately and are stored locally in your browser.",
+ "customize": {
+ "title": "Theme Colors",
+ "primary": "Primary Color",
+ "secondary": "Secondary Color",
+ "reset": "Reset to Default"
+ }
+ },
"system": {
"title": "System",
"restart": {
@@ -119,4 +129,4 @@
"ftpHint": "For large backup files, you can also upload directly to the data/backups directory via FTP/SFTP"
}
}
-}
\ No newline at end of file
+}
diff --git a/dashboard/src/i18n/locales/zh-CN/features/settings.json b/dashboard/src/i18n/locales/zh-CN/features/settings.json
index 6d4a194b9..27636088a 100644
--- a/dashboard/src/i18n/locales/zh-CN/features/settings.json
+++ b/dashboard/src/i18n/locales/zh-CN/features/settings.json
@@ -16,6 +16,16 @@
"custom": "自定义"
}
},
+ "theme": {
+ "title": "主题",
+ "subtitle": "自定义主题主色与辅助色。修改后立即生效,并保存在浏览器本地。",
+ "customize": {
+ "title": "主题颜色",
+ "primary": "主色",
+ "secondary": "辅助色",
+ "reset": "恢复默认"
+ }
+ },
"system": {
"title": "系统",
"restart": {
@@ -119,4 +129,4 @@
"ftpHint": "对于较大的备份文件,也可以通过 FTP/SFTP 等方式直接上传到 data/backups 目录"
}
}
-}
\ No newline at end of file
+}
diff --git a/dashboard/src/main.ts b/dashboard/src/main.ts
index 305c7644b..451f1616b 100644
--- a/dashboard/src/main.ts
+++ b/dashboard/src/main.ts
@@ -30,6 +30,19 @@ setupI18n().then(() => {
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;
+ });
+ }
});
}).catch(error => {
console.error('❌ 新i18n系统初始化失败:', error);
@@ -49,6 +62,19 @@ setupI18n().then(() => {
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;
+ });
+ }
});
});
@@ -79,4 +105,4 @@ loader.config({
paths: {
vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.54.0/min/vs',
},
-})
\ No newline at end of file
+})
diff --git a/dashboard/src/theme/LightTheme.ts b/dashboard/src/theme/LightTheme.ts
index 84240cb13..e908e43d0 100644
--- a/dashboard/src/theme/LightTheme.ts
+++ b/dashboard/src/theme/LightTheme.ts
@@ -8,8 +8,8 @@ const PurpleTheme: ThemeTypes = {
'carousel-control-size': 10
},
colors: {
- primary: '#1e88e5',
- secondary: '#5e35b1',
+ primary: '#3c96ca',
+ secondary: '#2288b7',
info: '#03c9d7',
success: '#00c853',
accent: '#FFAB91',
diff --git a/dashboard/src/views/Settings.vue b/dashboard/src/views/Settings.vue
index 1c56119ab..f7c401e4c 100644
--- a/dashboard/src/views/Settings.vue
+++ b/dashboard/src/views/Settings.vue
@@ -15,6 +15,41 @@
+ {{ tm('theme.title') }}
+
+
+
+
+
+
+
+
+
+
+
+ mdi-restore
+ {{ tm('theme.customize.reset') }}
+
+
+
+
+
{{ tm('system.title') }}
@@ -42,7 +77,7 @@
\ No newline at end of file
+
+const resetThemeColors = () => {
+ primaryColor.value = PurpleTheme.colors.primary;
+ secondaryColor.value = PurpleTheme.colors.secondary;
+ localStorage.removeItem('themePrimary');
+ localStorage.removeItem('themeSecondary');
+ applyThemeColors(primaryColor.value, secondaryColor.value);
+};
+