Files
d8d-admin-mobile-starter-pu…/client/mobile/hooks.tsx

271 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
};
};