| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- import React, { createContext, useContext, useState, useEffect } from 'react';
- import axios from 'axios';
- import { getLocalStorageWithExpiry, setLocalStorageWithExpiry } from './utils.ts';
- import type { User, AuthContextType, ThemeContextType, ThemeSettings } from '../share/types.ts';
- import { ThemeMode, FontSize, CompactMode } from '../share/types.ts';
- // 创建axios实例
- const api = axios.create({
- baseURL: window.CONFIG?.API_BASE_URL || '/api',
- timeout: 10000,
- headers: {
- 'Content-Type': 'application/json',
- }
- });
- // 请求拦截器添加token
- api.interceptors.request.use(
- (config) => {
- const token = getLocalStorageWithExpiry('token');
- if (token) {
- config.headers['Authorization'] = `Bearer ${token}`;
- }
- return config;
- },
- (error) => {
- return Promise.reject(error);
- }
- );
- // 响应拦截器处理错误
- api.interceptors.response.use(
- (response) => {
- return response;
- },
- (error) => {
- if (error.response && error.response.status === 401) {
- // 清除本地存储并刷新页面
- localStorage.removeItem('token');
- localStorage.removeItem('user');
- window.location.href = '/mobile/login';
- }
- return Promise.reject(error);
- }
- );
- // 默认主题设置
- const defaultThemeSettings: ThemeSettings = {
- user_id: 0,
- theme_mode: ThemeMode.LIGHT,
- primary_color: '#3B82F6', // 蓝色
- background_color: '#F9FAFB',
- text_color: '#111827',
- border_radius: 8,
- font_size: FontSize.MEDIUM,
- is_compact: CompactMode.NORMAL
- };
- // 创建认证上下文
- const AuthContext = createContext<AuthContextType>({
- user: null,
- token: null,
- login: async () => {},
- logout: async () => {},
- isAuthenticated: false,
- isLoading: true
- });
- // 创建主题上下文
- const ThemeContext = createContext<ThemeContextType>({
- isDark: false,
- currentTheme: defaultThemeSettings,
- updateTheme: () => {},
- saveTheme: async () => defaultThemeSettings,
- resetTheme: async () => defaultThemeSettings,
- toggleTheme: () => {}
- });
- // 认证提供者组件
- export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
- const [user, setUser] = useState<User | null>(null);
- const [token, setToken] = useState<string | null>(null);
- const [isLoading, setIsLoading] = useState(true);
- // 初始化时从本地存储获取用户信息和令牌
- useEffect(() => {
- const storedToken = getLocalStorageWithExpiry('token');
- const storedUser = getLocalStorageWithExpiry('user');
-
- if (storedToken && storedUser) {
- setToken(storedToken);
- setUser(storedUser);
- }
-
- setIsLoading(false);
- }, []);
- // 登录函数
- const login = async (username: string, password: string) => {
- try {
- const response = await api.post('/auth/login', { username, password });
- const { token, user } = response.data;
-
- // 保存到状态和本地存储
- setToken(token);
- setUser(user);
- setLocalStorageWithExpiry('token', token, 24); // 24小时过期
- setLocalStorageWithExpiry('user', user, 24);
-
- return user;
- } catch (error) {
- console.error('登录失败:', error);
- throw error;
- }
- };
- // 登出函数
- const logout = async () => {
- try {
- // 调用登出API
- await api.post('/auth/logout');
- } catch (error) {
- console.error('登出API调用失败:', error);
- } finally {
- // 无论API调用成功与否,都清除本地状态
- setToken(null);
- setUser(null);
- localStorage.removeItem('token');
- localStorage.removeItem('user');
- }
- };
- return (
- <AuthContext.Provider
- value={{
- user,
- token,
- login,
- logout,
- isAuthenticated: !!token,
- isLoading
- }}
- >
- {children}
- </AuthContext.Provider>
- );
- };
- // 主题提供者组件
- export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
- const [currentTheme, setCurrentTheme] = useState<ThemeSettings>(() => {
- const storedTheme = localStorage.getItem('theme');
- return storedTheme ? JSON.parse(storedTheme) : defaultThemeSettings;
- });
-
- const isDark = currentTheme.theme_mode === ThemeMode.DARK;
- // 更新主题(实时预览)
- const updateTheme = (theme: Partial<ThemeSettings>) => {
- setCurrentTheme(prev => {
- const updatedTheme = { ...prev, ...theme };
- localStorage.setItem('theme', JSON.stringify(updatedTheme));
- return updatedTheme;
- });
- };
- // 保存主题到后端
- const saveTheme = async (theme: Partial<ThemeSettings>): Promise<ThemeSettings> => {
- try {
- const updatedTheme = { ...currentTheme, ...theme };
- const { data } = await api.post('/theme/save', updatedTheme);
-
- setCurrentTheme(data);
- localStorage.setItem('theme', JSON.stringify(data));
-
- return data;
- } catch (error) {
- console.error('保存主题失败:', error);
- throw error;
- }
- };
- // 重置主题
- const resetTheme = async (): Promise<ThemeSettings> => {
- try {
- const { data } = await api.post('/theme/reset');
-
- setCurrentTheme(data);
- localStorage.setItem('theme', JSON.stringify(data));
-
- return data;
- } catch (error) {
- console.error('重置主题失败:', error);
-
- // 如果API失败,至少重置到默认主题
- setCurrentTheme(defaultThemeSettings);
- localStorage.setItem('theme', JSON.stringify(defaultThemeSettings));
-
- return defaultThemeSettings;
- }
- };
- // 切换主题模式(亮色/暗色)
- const toggleTheme = () => {
- const newMode = isDark ? ThemeMode.LIGHT : ThemeMode.DARK;
- const updatedTheme = {
- ...currentTheme,
- theme_mode: newMode,
- // 暗色和亮色模式下自动调整背景色和文字颜色
- background_color: newMode === ThemeMode.DARK ? '#121212' : '#F9FAFB',
- text_color: newMode === ThemeMode.DARK ? '#E5E7EB' : '#111827'
- };
-
- setCurrentTheme(updatedTheme);
- localStorage.setItem('theme', JSON.stringify(updatedTheme));
- };
- // 主题变化时应用CSS变量
- useEffect(() => {
- document.documentElement.style.setProperty('--primary-color', currentTheme.primary_color);
- document.documentElement.style.setProperty('--background-color', currentTheme.background_color || '#F9FAFB');
- document.documentElement.style.setProperty('--text-color', currentTheme.text_color || '#111827');
- document.documentElement.style.setProperty('--border-radius', `${currentTheme.border_radius || 8}px`);
-
- // 设置字体大小
- let rootFontSize = '16px'; // 默认中等字体
- if (currentTheme.font_size === FontSize.SMALL) {
- rootFontSize = '14px';
- } else if (currentTheme.font_size === FontSize.LARGE) {
- rootFontSize = '18px';
- }
- document.documentElement.style.setProperty('--font-size', rootFontSize);
-
- // 设置暗色模式类
- if (isDark) {
- document.documentElement.classList.add('dark');
- } else {
- document.documentElement.classList.remove('dark');
- }
- }, [currentTheme, isDark]);
- return (
- <ThemeContext.Provider
- value={{
- isDark,
- currentTheme,
- updateTheme,
- saveTheme,
- resetTheme,
- toggleTheme
- }}
- >
- {children}
- </ThemeContext.Provider>
- );
- };
- // 主题hook
- export const useTheme = () => useContext(ThemeContext);
- // 认证hook
- export const useAuth = () => useContext(AuthContext);
- // API hook
- export const useApi = () => {
- const { token } = useAuth();
-
- return {
- api,
- isAuthenticated: !!token
- };
- };
|