diff --git a/web/src/components/Index.vue b/web/src/components/Index.vue index 6cda8a4..a8c33b7 100644 --- a/web/src/components/Index.vue +++ b/web/src/components/Index.vue @@ -25,34 +25,48 @@ const isUserMenuOpen = ref(false) const userAccount = ref('管理员') const siteConfig = ref({}) -// 主题:明暗模式 -const getInitialTheme = (): 'light' | 'dark' => { +// 主题:明暗模式与跟随系统 +type ThemePreference = 'light' | 'dark' | 'system' + +const getInitialThemePreference = (): ThemePreference => { try { - const stored = localStorage.getItem('theme') as 'light' | 'dark' | null - if (stored === 'light' || stored === 'dark') return stored - const systemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches - return systemDark ? 'dark' : 'light' + const storedPref = localStorage.getItem('themePreference') as ThemePreference | null + if (storedPref === 'light' || storedPref === 'dark' || storedPref === 'system') return storedPref + const legacy = localStorage.getItem('theme') as 'light' | 'dark' | null + if (legacy === 'light' || legacy === 'dark') return legacy + return 'system' } catch { - return 'light' + return 'system' } } -const theme = ref<'light' | 'dark'>(getInitialTheme()) +const themePreference = ref(getInitialThemePreference()) +const theme = ref<'light' | 'dark'>('light') -const applyTheme = (next: 'light' | 'dark') => { - theme.value = next - if (next === 'dark') { +const applyThemeFromPreference = () => { + const systemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches + const effective: 'light' | 'dark' = themePreference.value === 'system' + ? (systemDark ? 'dark' : 'light') + : themePreference.value + theme.value = effective + if (effective === 'dark') { document.documentElement.classList.add('dark') } else { document.documentElement.classList.remove('dark') } - try { localStorage.setItem('theme', next) } catch {} + try { localStorage.setItem('themePreference', themePreference.value) } catch {} } const toggleTheme = () => { - applyTheme(theme.value === 'dark' ? 'light' : 'dark') + themePreference.value = themePreference.value === 'light' ? 'dark' : (themePreference.value === 'dark' ? 'system' : 'light') + applyThemeFromPreference() } +const themeLabel = computed(() => { + if (themePreference.value === 'system') return '跟随系统' + return theme.value === 'dark' ? '深色' : '浅色' +}) + // 从JWT中解析用户名 const parseJwtUsername = (token: string): string => { try { @@ -165,6 +179,22 @@ const logout = () => { // 监听localStorage变化 onMounted(() => { + // 初始化主题并监听系统主题变化 + applyThemeFromPreference() + try { + const media = window.matchMedia('(prefers-color-scheme: dark)') + const handleSystemChange = () => { + if (themePreference.value === 'system') applyThemeFromPreference() + } + // 新浏览器 + if (media.addEventListener) { + media.addEventListener('change', handleSystemChange) + } else if ((media as any).addListener) { + // 兼容旧浏览器 + ;(media as any).addListener(handleSystemChange) + } + } catch {} + // 初始化用户账号信息 updateUserAccount(); @@ -350,7 +380,7 @@ const siteTitle = computed(() => {