// 主题常量与持久化 — 主流主题 + 开发者主题 + 跟随系统 // Logan.Li import { getItem, setItem, STORAGE_KEYS } from './storage'; import i18n from '@/i18n'; /** 用户选择的主题偏好(含跟随系统) */ export type ThemePreference = | 'system' | 'ocean-deep' | 'light-professional' | 'one-dark' | 'catppuccin' | 'dracula' | 'tokyo-night' | 'nord'; /** 实际应用到 data-theme 的具体主题 */ export type ResolvedTheme = Exclude; export interface ThemeOption { id: ThemePreference; name: string; description: string; isDark: boolean; primaryColor: string; } export interface ThemeGroup { label: string; options: ThemeOption[]; } /** 主流主题 — 默认推荐 */ const MAINSTREAM_THEMES: ThemeOption[] = [ { id: 'system', name: i18n.t('common.lightDark'), description: i18n.t('跟随系统'), isDark: false, primaryColor: 'linear-gradient(135deg, #2563eb 50%, #0d1117 50%)' }, { id: 'ocean-deep', name: i18n.t('深海蓝'), description: i18n.t('深海蓝'), isDark: true, primaryColor: '#0d1117' }, { id: 'light-professional', name: i18n.t('明亮白'), description: i18n.t('企业控制台清爽浅色'), isDark: false, primaryColor: '#2563eb' }, ]; /** 开发者主题 — 高辨识度扩展 */ const DEVELOPER_THEMES: ThemeOption[] = [ { id: 'one-dark', name: 'One Dark', description: i18n.t('Atom 暖灰 IDE,紫绿语法渐变'), isDark: true, primaryColor: '#c678dd' }, { id: 'dracula', name: 'Dracula', description: i18n.t('经典紫粉暗色,极高辨识度'), isDark: true, primaryColor: '#bd93f9' }, { id: 'tokyo-night', name: 'Tokyo Night', description: i18n.t('东京霓虹深蓝紫,VS Code 热门'), isDark: true, primaryColor: '#7aa2f7' }, { id: 'catppuccin', name: 'Catppuccin', description: i18n.t('马卡龙柔和暗色,护眼'), isDark: true, primaryColor: '#89b4fa' }, ]; export const THEME_GROUPS: ThemeGroup[] = [ { label: i18n.t('主流'), options: MAINSTREAM_THEMES }, { label: i18n.t('开发者'), options: DEVELOPER_THEMES }, ]; export const THEME_OPTIONS: ThemeOption[] = [ ...MAINSTREAM_THEMES, ...DEVELOPER_THEMES, ]; export const THEME_ORDER: ThemePreference[] = THEME_OPTIONS.map((o) => o.id); export const DEFAULT_THEME: ThemePreference = 'system'; const LEGACY_THEME_MAP: Record = { dark: 'ocean-deep', light: 'light-professional', 'midnight-purple': 'one-dark', 'pine-green': 'catppuccin', 'orange-vitality': 'tokyo-night', nord: 'tokyo-night', }; export function migrateLegacyTheme(value: string | null): ThemePreference | null { if (!value) return null; return LEGACY_THEME_MAP[value] ?? null; } /** 读取系统 color-scheme 偏好 // Logan.Li */ export function getSystemColorScheme(): 'light' | 'dark' { if (typeof window === 'undefined') return 'dark'; return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } /** 将用户偏好解析为具体主题 // Logan.Li */ export function resolveThemePreference(pref: ThemePreference): ResolvedTheme { if (pref === 'system') { return getSystemColorScheme() === 'dark' ? 'ocean-deep' : 'light-professional'; } return pref; } export function isThemeDark(pref: ThemePreference): boolean { if (pref === 'system') return getSystemColorScheme() === 'dark'; const opt = THEME_OPTIONS.find((o) => o.id === pref); return opt?.isDark ?? true; } export function getInitialTheme(): ThemePreference { if (typeof window === 'undefined') return DEFAULT_THEME; const stored = getItem(STORAGE_KEYS.theme); const migrated = migrateLegacyTheme(stored); if (migrated) { setItem(STORAGE_KEYS.theme, migrated); return migrated; } if (stored && THEME_ORDER.includes(stored as ThemePreference)) { return stored as ThemePreference; } /* nord 已下架,自动迁移到 Tokyo Night // Logan.Li */ if (stored === 'nord') { setItem(STORAGE_KEYS.theme, 'tokyo-night'); return 'tokyo-night'; } return DEFAULT_THEME; } export function persistTheme(theme: ThemePreference): void { setItem(STORAGE_KEYS.theme, theme); } export function applyThemeToDocument(pref: ThemePreference): void { if (typeof document === 'undefined') return; const resolved = resolveThemePreference(pref); document.documentElement.setAttribute('data-theme', resolved); document.documentElement.setAttribute('data-theme-preference', pref); document.documentElement.style.colorScheme = isThemeDark(pref) ? 'dark' : 'light'; } export function getNextTheme(current: ThemePreference): ThemePreference { const idx = THEME_ORDER.indexOf(current); return THEME_ORDER[(idx + 1) % THEME_ORDER.length]; }