/** * 主题系统(需求②): * - 三态:light / dark / system(跟随 prefers-color-scheme) * - 默认跟随系统;localStorage 记忆(键 fcb_theme_mode) * - 实际生效主题写入 ,CSS 变量按此切换 * - Naive UI darkTheme 由 App.vue 读取 useTheme().resolved 响应式传递 */ import { ref, watch } from 'vue' export type ThemeMode = 'light' | 'dark' | 'system' export type ResolvedTheme = 'light' | 'dark' export const THEME_MODES: ThemeMode[] = ['light', 'dark', 'system'] const STORAGE_KEY = 'fcb_theme_mode' function readStoredMode(): ThemeMode | null { try { const v = localStorage.getItem(STORAGE_KEY) return v === 'light' || v === 'dark' || v === 'system' ? v : null } catch { return null } } function persist(mode: ThemeMode): void { try { localStorage.setItem(STORAGE_KEY, mode) } catch { /* ignore */ } } function systemPrefersDark(): boolean { return typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches } /** 当前模式(未初始化时按 localStorage → 默认 system) */ const mode = ref(readStoredMode() ?? 'system') /** 实际生效主题(响应式,供 Naive UI 与 使用) */ const resolved = ref(mode.value === 'system' ? (systemPrefersDark() ? 'dark' : 'light') : mode.value) let started = false /** 初始化系统主题监听(App.vue 挂载时调用一次) */ function start(): void { if (started || typeof matchMedia !== 'function') return started = true const mq = matchMedia('(prefers-color-scheme: dark)') mq.addEventListener?.('change', () => { if (mode.value === 'system') resolved.value = mq.matches ? 'dark' : 'light' }) } function apply(): void { start() resolved.value = mode.value === 'system' ? (systemPrefersDark() ? 'dark' : 'light') : mode.value document.documentElement.dataset.theme = resolved.value } watch(mode, apply, { immediate: true }) /** 切换模式并记忆(需求②:导航栏三态切换 + localStorage) */ export function setThemeMode(next: ThemeMode): void { mode.value = next persist(next) } export function useTheme() { return { mode, resolved, setMode: setThemeMode, } }