'use client'; import { createContext, useContext, useState, useEffect, useCallback, useMemo, ReactNode } from 'react'; type Theme = 'light' | 'dark'; interface ThemeContextType { theme: Theme; toggleTheme: () => void; setTheme: (theme: Theme) => void; isSystemTheme: boolean; } const ThemeContext = createContext(undefined); // 纯函数,放在组件外避免闭包问题 function getSystemTheme(): Theme { if (typeof window === 'undefined') return 'light'; return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } function isNightTime(): boolean { if (typeof window === 'undefined') return false; const hour = new Date().getHours(); return hour >= 18 || hour < 6; } function getAutoTheme(): Theme { if (getSystemTheme() === 'dark') return 'dark'; if (isNightTime()) return 'dark'; return 'light'; } export function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setThemeState] = useState('light'); const [isSystemTheme, setIsSystemTheme] = useState(true); const [mounted, setMounted] = useState(false); // 设置主题(仅当前会话,不持久化) const setTheme = useCallback((newTheme: Theme) => { setThemeState(newTheme); setIsSystemTheme(false); }, []); // 切换主题 const toggleTheme = useCallback(() => { setThemeState(prev => prev === 'light' ? 'dark' : 'light'); setIsSystemTheme(false); }, []); // 初始化:始终跟随系统主题 + 时间 useEffect(() => { setThemeState(getAutoTheme()); setIsSystemTheme(true); setMounted(true); }, []); // 监听系统主题变化 useEffect(() => { if (!isSystemTheme) return; const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handleChange = () => { setThemeState(getAutoTheme()); }; mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); }, [isSystemTheme]); // 每分钟检查时间变化 useEffect(() => { if (!isSystemTheme) return; const interval = setInterval(() => { setThemeState(getAutoTheme()); }, 60000); return () => clearInterval(interval); }, [isSystemTheme]); // 在 DOM 上设置主题属性 useEffect(() => { if (mounted) { document.documentElement.setAttribute('data-theme', theme); } }, [theme, mounted]); const value = useMemo( () => ({ theme, toggleTheme, setTheme, isSystemTheme }), [theme, toggleTheme, setTheme, isSystemTheme] ); return ( {children} ); } export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; }