- vite.config.js 双入口(前台 index + 后台 admin),产物输出 public/dist
- 前台 App 布局壳 + 11 条路由占位;api client/theme/utils 共享层
- index.html 保留 ${site_name} 等占位符与 style.css 引用(UI 100% 还原基础)
- dev 代理 3101,构建产物 gitignored
45 lines
1.3 KiB
React
45 lines
1.3 KiB
React
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||
|
||
// 与 v1 一致:深色模式由 document.documentElement 上的 data-theme="dark" 控制
|
||
// (public/css/style.css 使用 [data-theme="dark"] 选择器,非 .dark 类)
|
||
const THEME_KEY = 'theme';
|
||
const ThemeContext = createContext(null);
|
||
|
||
function applyTheme(theme) {
|
||
if (theme === 'dark') {
|
||
document.documentElement.setAttribute('data-theme', 'dark');
|
||
} else {
|
||
document.documentElement.removeAttribute('data-theme');
|
||
}
|
||
}
|
||
|
||
export function ThemeProvider({ children }) {
|
||
const [theme, setTheme] = useState(() => {
|
||
const saved = localStorage.getItem(THEME_KEY);
|
||
if (saved === 'dark' || saved === 'light') return saved;
|
||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||
});
|
||
|
||
useEffect(() => {
|
||
applyTheme(theme);
|
||
localStorage.setItem(THEME_KEY, theme);
|
||
}, [theme]);
|
||
|
||
const value = useMemo(
|
||
() => ({
|
||
theme,
|
||
toggleTheme: () => setTheme((t) => (t === 'dark' ? 'light' : 'dark')),
|
||
setTheme,
|
||
}),
|
||
[theme]
|
||
);
|
||
|
||
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
||
}
|
||
|
||
export function useTheme() {
|
||
const ctx = useContext(ThemeContext);
|
||
if (!ctx) throw new Error('useTheme 必须在 ThemeProvider 内使用');
|
||
return ctx;
|
||
}
|