Skip to content

Commit 376c458

Browse files
shelly-pppengxiuli
andauthored
feat: 增加主题设置功能 (#235)
* feat(ui): 增加主题设置功能 * feat(ui): footer文字颜色跟随项目主题 --------- Co-authored-by: pengpeng <[email protected]>
1 parent 7068d04 commit 376c458

File tree

6 files changed

+122
-28
lines changed

6 files changed

+122
-28
lines changed

web/src/assets/style.css

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@
1818
-webkit-font-smoothing: antialiased;
1919
-moz-osx-font-smoothing: grayscale;
2020

21+
/* 主题颜色变量 - 白天模式 */
22+
--bg-color: #f5f7fa;
23+
--bg-color-gradient: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
24+
--card-bg-color: rgba(255, 255, 255, 0.95);
25+
--card-border-color: rgba(255, 255, 255, 0.2);
26+
--text-color-primary: #1e293b;
27+
--text-color-secondary: #475569;
28+
--text-color-tertiary: #94a3b8;
29+
--border-color: #e2e8f0;
30+
--scrollbar-track-bg: rgba(0, 0, 0, 0.05);
31+
--selection-bg-color: rgba(102, 126, 234, 0.2);
32+
--focus-outline-color: rgba(102, 126, 234, 0.5);
33+
--loading-overlay-bg: rgba(255, 255, 255, 0.8);
34+
--glass-effect-bg: rgba(255, 255, 255, 0.25);
35+
--glass-effect-border: rgba(255, 255, 255, 0.18);
36+
2137
/* 现代化颜色方案 */
2238
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2339
--secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
@@ -39,6 +55,24 @@
3955
--border-radius-xl: 24px;
4056
}
4157

58+
html.dark {
59+
/* 主题颜色变量 - 暗夜模式 */
60+
--bg-color: #1a202c;
61+
--bg-color-gradient: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
62+
--card-bg-color: rgba(45, 55, 72, 0.85);
63+
--card-border-color: rgba(255, 255, 255, 0.1);
64+
--text-color-primary: #e2e8f0;
65+
--text-color-secondary: #a0aec0;
66+
--text-color-tertiary: #718096;
67+
--border-color: #4a5568;
68+
--scrollbar-track-bg: rgba(255, 255, 255, 0.1);
69+
--selection-bg-color: rgba(102, 126, 234, 0.3);
70+
--focus-outline-color: rgba(102, 126, 234, 0.6);
71+
--loading-overlay-bg: rgba(26, 32, 44, 0.8);
72+
--glass-effect-bg: rgba(45, 55, 72, 0.25);
73+
--glass-effect-border: rgba(255, 255, 255, 0.1);
74+
}
75+
4276
* {
4377
margin: 0;
4478
padding: 0;
@@ -48,7 +82,8 @@
4882
html,
4983
body {
5084
height: 100%;
51-
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
85+
color: var(--text-color-primary);
86+
background: var(--bg-color-gradient);
5287
background-attachment: fixed;
5388
}
5489

@@ -148,7 +183,7 @@ body {
148183
}
149184

150185
::-webkit-scrollbar-track {
151-
background: rgba(0, 0, 0, 0.05);
186+
background: var(--scrollbar-track-bg);
152187
border-radius: 1px;
153188
}
154189

@@ -163,11 +198,11 @@ body {
163198

164199
/* 通用卡片样式 */
165200
.modern-card {
166-
background: rgba(255, 255, 255, 0.95);
201+
background: var(--card-bg-color);
167202
backdrop-filter: blur(10px);
168203
border-radius: var(--border-radius-lg);
169204
box-shadow: var(--shadow-lg);
170-
border: 1px solid rgba(255, 255, 255, 0.2);
205+
border: 1px solid var(--card-border-color);
171206
transition: all 0.2s ease;
172207
}
173208

@@ -196,18 +231,18 @@ body {
196231
}
197232

198233
.modern-input:focus {
199-
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
234+
box-shadow: 0 0 0 3px var(--selection-bg-color);
200235
}
201236

202237
/* 选择文本样式 */
203238
::selection {
204-
background: rgba(102, 126, 234, 0.2);
205-
color: #1e293b;
239+
background: var(--selection-bg-color);
240+
color: var(--text-color-primary);
206241
}
207242

208243
::-moz-selection {
209-
background: rgba(102, 126, 234, 0.2);
210-
color: #1e293b;
244+
background: var(--selection-bg-color);
245+
color: var(--text-color-primary);
211246
}
212247

213248
/* Focus样式增强 */
@@ -216,7 +251,7 @@ body {
216251
}
217252

218253
*:focus-visible {
219-
outline: 2px solid rgba(102, 126, 234, 0.5);
254+
outline: 2px solid var(--focus-outline-color);
220255
outline-offset: 2px;
221256
}
222257

@@ -227,7 +262,7 @@ body {
227262
left: 0;
228263
right: 0;
229264
bottom: 0;
230-
background: rgba(255, 255, 255, 0.8);
265+
background: var(--loading-overlay-bg);
231266
backdrop-filter: blur(4px);
232267
display: flex;
233268
align-items: center;
@@ -259,9 +294,9 @@ body {
259294

260295
/* 玻璃态效果 */
261296
.glass-effect {
262-
background: rgba(255, 255, 255, 0.25);
297+
background: var(--glass-effect-bg);
263298
backdrop-filter: blur(16px);
264-
border: 1px solid rgba(255, 255, 255, 0.18);
299+
border: 1px solid var(--glass-effect-border);
265300
}
266301

267302
/* 悬停效果增强 */

web/src/components/AppFooter.vue

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,9 @@ onMounted(() => {
229229

230230
<style scoped>
231231
.app-footer {
232-
background: rgba(255, 255, 255, 0.95);
232+
background: var(--bg-color);
233233
backdrop-filter: blur(20px);
234-
border-top: 1px solid rgba(0, 0, 0, 0.08);
234+
border-top: 1px solid var(--border-color);
235235
padding: 12px 24px;
236236
font-size: 14px;
237237
min-height: 52px;
@@ -251,7 +251,7 @@ onMounted(() => {
251251
}
252252
253253
.project-info {
254-
color: #666;
254+
color: var(--text-color-secondary);
255255
font-weight: 500;
256256
}
257257
@@ -283,7 +283,7 @@ onMounted(() => {
283283
.version-text {
284284
font-weight: 500;
285285
font-size: 13px;
286-
color: #666;
286+
color: var(--text-color-secondary);
287287
white-space: nowrap;
288288
}
289289
@@ -311,7 +311,7 @@ onMounted(() => {
311311
display: flex;
312312
align-items: center;
313313
gap: 4px;
314-
color: #666;
314+
color: var(--text-color-secondary);
315315
text-decoration: none;
316316
padding: 4px 6px;
317317
border-radius: 4px;
@@ -338,13 +338,8 @@ onMounted(() => {
338338
gap: 8px;
339339
}
340340
341-
.copyright-text {
342-
color: #888;
343-
font-size: 12px;
344-
}
345-
346-
.license-text {
347-
color: #888;
341+
.copyright-text, .license-text {
342+
color: var(--text-color-tertiary);
348343
font-size: 12px;
349344
}
350345

web/src/components/GlobalProviders.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
<script setup lang="ts">
22
import { appState } from "@/utils/app-state";
3+
import { currentTheme } from "@/utils/theme";
34
import {
45
NConfigProvider,
56
NDialogProvider,
67
NLoadingBarProvider,
78
NMessageProvider,
9+
darkTheme,
810
useLoadingBar,
911
useMessage,
1012
type GlobalThemeOverrides,
1113
} from "naive-ui";
12-
import { defineComponent, watch } from "vue";
14+
import { computed, defineComponent, watch } from "vue";
1315
1416
// 自定义主题配置
1517
const themeOverrides: GlobalThemeOverrides = {
@@ -39,6 +41,8 @@ const themeOverrides: GlobalThemeOverrides = {
3941
},
4042
};
4143
44+
const naiveTheme = computed(() => (currentTheme.value === "dark" ? darkTheme : null));
45+
4246
function useGlobalMessage() {
4347
window.$message = useMessage();
4448
}
@@ -69,7 +73,7 @@ const Message = defineComponent({
6973
</script>
7074

7175
<template>
72-
<n-config-provider :theme-overrides="themeOverrides">
76+
<n-config-provider :theme="naiveTheme" :theme-overrides="themeOverrides">
7377
<n-loading-bar-provider>
7478
<n-message-provider placement="top-right">
7579
<n-dialog-provider>

web/src/components/Layout.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import AppFooter from "@/components/AppFooter.vue";
33
import GlobalTaskProgressBar from "@/components/GlobalTaskProgressBar.vue";
44
import Logout from "@/components/Logout.vue";
55
import NavBar from "@/components/NavBar.vue";
6+
import ThemeToggleButton from "@/components/ThemeToggleButton.vue";
67
import { useMediaQuery } from "@vueuse/core";
78
import { ref, watch } from "vue";
89
@@ -36,6 +37,7 @@ const toggleMenu = () => {
3637
</nav>
3738

3839
<div class="header-actions">
40+
<theme-toggle-button />
3941
<logout v-if="!isMobile" />
4042
<n-button v-else text @click="toggleMenu">
4143
<svg viewBox="0 0 24 24" width="24" height="24">
@@ -80,7 +82,7 @@ const toggleMenu = () => {
8082
}
8183
8284
.layout-header {
83-
background: rgba(255, 255, 255, 0.95);
85+
background: var(--bg-color);
8486
backdrop-filter: blur(20px);
8587
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
8688
box-shadow: var(--shadow-sm);
@@ -134,6 +136,7 @@ const toggleMenu = () => {
134136
flex-shrink: 0;
135137
display: flex;
136138
align-items: center;
139+
gap: 8px;
137140
}
138141
139142
.mobile-actions {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script setup lang="ts">
2+
import { currentTheme, toggleTheme } from "@/utils/theme";
3+
import { NButton } from "naive-ui";
4+
</script>
5+
6+
<template>
7+
<n-button @click="toggleTheme" class="theme-toggle-btn" quaternary>
8+
<template #icon>
9+
<span v-if="currentTheme === 'light'">🌙</span>
10+
<span v-else>☀️</span>
11+
</template>
12+
</n-button>
13+
</template>
14+
15+
<style scoped>
16+
.theme-toggle-btn {
17+
font-size: 1.2rem;
18+
padding: 0 8px;
19+
background-color: transparent;
20+
border: none;
21+
cursor: pointer;
22+
color: var(--text-color);
23+
transition: color 0.2s ease-in-out;
24+
}
25+
</style>

web/src/utils/theme.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { ref, watchEffect } from "vue";
2+
3+
type Theme = "light" | "dark";
4+
5+
const THEME_KEY = "app_theme";
6+
7+
// 优先从 localStorage 读取主题,否则根据系统偏好设置
8+
const initialTheme = (): Theme => {
9+
const storedTheme = localStorage.getItem(THEME_KEY) as Theme | null;
10+
if (storedTheme) {
11+
return storedTheme;
12+
}
13+
return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
14+
? "dark"
15+
: "light";
16+
};
17+
18+
export const currentTheme = ref<Theme>(initialTheme());
19+
20+
export const toggleTheme = () => {
21+
currentTheme.value = currentTheme.value === "light" ? "dark" : "light";
22+
};
23+
24+
// 监听主题变化,并更新 localStorage 和 <html> class
25+
watchEffect(() => {
26+
localStorage.setItem(THEME_KEY, currentTheme.value);
27+
if (currentTheme.value === "dark") {
28+
document.documentElement.classList.add("dark");
29+
} else {
30+
document.documentElement.classList.remove("dark");
31+
}
32+
});

0 commit comments

Comments
 (0)