2
0

hooks.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import React, { createContext, useContext, useState, useEffect } from 'react';
  2. import axios from 'axios';
  3. import { getLocalStorageWithExpiry, setLocalStorageWithExpiry } from './utils.ts';
  4. import type { User, AuthContextType, ThemeContextType, ThemeSettings } from '../share/types.ts';
  5. import { ThemeMode, FontSize, CompactMode } from '../share/types.ts';
  6. import { AuthAPI, ThemeAPI } from './api.ts';
  7. // 创建axios实例
  8. const api = axios.create({
  9. baseURL: window.CONFIG?.API_BASE_URL || '/api',
  10. timeout: 10000,
  11. headers: {
  12. 'Content-Type': 'application/json',
  13. }
  14. });
  15. // 请求拦截器添加token
  16. api.interceptors.request.use(
  17. (config) => {
  18. const token = getLocalStorageWithExpiry('token');
  19. if (token) {
  20. config.headers['Authorization'] = `Bearer ${token}`;
  21. }
  22. return config;
  23. },
  24. (error) => {
  25. return Promise.reject(error);
  26. }
  27. );
  28. // 响应拦截器处理错误
  29. api.interceptors.response.use(
  30. (response) => {
  31. return response;
  32. },
  33. (error) => {
  34. if (error.response && error.response.status === 401) {
  35. // 清除本地存储并刷新页面
  36. localStorage.removeItem('token');
  37. localStorage.removeItem('user');
  38. window.location.href = '/mobile/login';
  39. }
  40. return Promise.reject(error);
  41. }
  42. );
  43. // 默认主题设置
  44. const defaultThemeSettings: ThemeSettings = {
  45. user_id: 0,
  46. theme_mode: ThemeMode.LIGHT,
  47. primary_color: '#3B82F6', // 蓝色
  48. background_color: '#F9FAFB',
  49. text_color: '#111827',
  50. border_radius: 8,
  51. font_size: FontSize.MEDIUM,
  52. is_compact: CompactMode.NORMAL
  53. };
  54. // 创建认证上下文
  55. const AuthContext = createContext<AuthContextType>({
  56. user: null,
  57. token: null,
  58. login: async () => {},
  59. logout: async () => {},
  60. isAuthenticated: false,
  61. isLoading: true
  62. });
  63. // 创建主题上下文
  64. const ThemeContext = createContext<ThemeContextType>({
  65. isDark: false,
  66. currentTheme: defaultThemeSettings,
  67. updateTheme: () => {},
  68. saveTheme: async () => defaultThemeSettings,
  69. resetTheme: async () => defaultThemeSettings,
  70. toggleTheme: () => {}
  71. });
  72. // 认证提供者组件
  73. export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  74. const [user, setUser] = useState<User | null>(null);
  75. const [token, setToken] = useState<string | null>(null);
  76. const [isLoading, setIsLoading] = useState(true);
  77. // 初始化时从本地存储获取用户信息和令牌
  78. useEffect(() => {
  79. const storedToken = getLocalStorageWithExpiry('token');
  80. const storedUser = getLocalStorageWithExpiry('user');
  81. if (storedToken && storedUser) {
  82. setToken(storedToken);
  83. setUser(storedUser);
  84. }
  85. setIsLoading(false);
  86. }, []);
  87. // 登录函数
  88. const login = async (username: string, password: string) => {
  89. try {
  90. const response = await AuthAPI.login(username, password);
  91. const { token, user } = response;
  92. // 保存到状态和本地存储
  93. setToken(token);
  94. setUser(user);
  95. setLocalStorageWithExpiry('token', token, 24); // 24小时过期
  96. setLocalStorageWithExpiry('user', user, 24);
  97. return user;
  98. } catch (error) {
  99. console.error('登录失败:', error);
  100. throw error;
  101. }
  102. };
  103. // 登出函数
  104. const logout = async () => {
  105. try {
  106. // 调用登出API
  107. await AuthAPI.logout();
  108. } catch (error) {
  109. console.error('登出API调用失败:', error);
  110. } finally {
  111. // 无论API调用成功与否,都清除本地状态
  112. setToken(null);
  113. setUser(null);
  114. localStorage.removeItem('token');
  115. localStorage.removeItem('user');
  116. }
  117. };
  118. return (
  119. <AuthContext.Provider
  120. value={{
  121. user,
  122. token,
  123. login,
  124. logout,
  125. isAuthenticated: !!token,
  126. isLoading
  127. }}
  128. >
  129. {children}
  130. </AuthContext.Provider>
  131. );
  132. };
  133. // 主题提供者组件
  134. export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  135. const [currentTheme, setCurrentTheme] = useState<ThemeSettings>(() => {
  136. const storedTheme = localStorage.getItem('theme');
  137. return storedTheme ? JSON.parse(storedTheme) : defaultThemeSettings;
  138. });
  139. const isDark = currentTheme.theme_mode === ThemeMode.DARK;
  140. // 更新主题(实时预览)
  141. const updateTheme = (theme: Partial<ThemeSettings>) => {
  142. setCurrentTheme(prev => {
  143. const updatedTheme = { ...prev, ...theme };
  144. localStorage.setItem('theme', JSON.stringify(updatedTheme));
  145. return updatedTheme;
  146. });
  147. };
  148. // 保存主题到后端
  149. const saveTheme = async (theme: Partial<ThemeSettings>): Promise<ThemeSettings> => {
  150. try {
  151. const updatedTheme = { ...currentTheme, ...theme };
  152. const data = await ThemeAPI.updateThemeSettings(updatedTheme);
  153. setCurrentTheme(data);
  154. localStorage.setItem('theme', JSON.stringify(data));
  155. return data;
  156. } catch (error) {
  157. console.error('保存主题失败:', error);
  158. throw error;
  159. }
  160. };
  161. // 重置主题
  162. const resetTheme = async (): Promise<ThemeSettings> => {
  163. try {
  164. const data = await ThemeAPI.resetThemeSettings();
  165. setCurrentTheme(data);
  166. localStorage.setItem('theme', JSON.stringify(data));
  167. return data;
  168. } catch (error) {
  169. console.error('重置主题失败:', error);
  170. // 如果API失败,至少重置到默认主题
  171. setCurrentTheme(defaultThemeSettings);
  172. localStorage.setItem('theme', JSON.stringify(defaultThemeSettings));
  173. return defaultThemeSettings;
  174. }
  175. };
  176. // 切换主题模式(亮色/暗色)
  177. const toggleTheme = () => {
  178. const newMode = isDark ? ThemeMode.LIGHT : ThemeMode.DARK;
  179. const updatedTheme = {
  180. ...currentTheme,
  181. theme_mode: newMode,
  182. // 暗色和亮色模式下自动调整背景色和文字颜色
  183. background_color: newMode === ThemeMode.DARK ? '#121212' : '#F9FAFB',
  184. text_color: newMode === ThemeMode.DARK ? '#E5E7EB' : '#111827'
  185. };
  186. setCurrentTheme(updatedTheme);
  187. localStorage.setItem('theme', JSON.stringify(updatedTheme));
  188. };
  189. // 主题变化时应用CSS变量
  190. useEffect(() => {
  191. document.documentElement.style.setProperty('--primary-color', currentTheme.primary_color);
  192. document.documentElement.style.setProperty('--background-color', currentTheme.background_color || '#F9FAFB');
  193. document.documentElement.style.setProperty('--text-color', currentTheme.text_color || '#111827');
  194. document.documentElement.style.setProperty('--border-radius', `${currentTheme.border_radius || 8}px`);
  195. // 设置字体大小
  196. let rootFontSize = '16px'; // 默认中等字体
  197. if (currentTheme.font_size === FontSize.SMALL) {
  198. rootFontSize = '14px';
  199. } else if (currentTheme.font_size === FontSize.LARGE) {
  200. rootFontSize = '18px';
  201. }
  202. document.documentElement.style.setProperty('--font-size', rootFontSize);
  203. // 设置暗色模式类
  204. if (isDark) {
  205. document.documentElement.classList.add('dark');
  206. } else {
  207. document.documentElement.classList.remove('dark');
  208. }
  209. }, [currentTheme, isDark]);
  210. return (
  211. <ThemeContext.Provider
  212. value={{
  213. isDark,
  214. currentTheme,
  215. updateTheme,
  216. saveTheme,
  217. resetTheme,
  218. toggleTheme
  219. }}
  220. >
  221. {children}
  222. </ThemeContext.Provider>
  223. );
  224. };
  225. // 主题hook
  226. export const useTheme = () => useContext(ThemeContext);
  227. // 认证hook
  228. export const useAuth = () => useContext(AuthContext);
  229. // API hook
  230. export const useApi = () => {
  231. const { token } = useAuth();
  232. return {
  233. api,
  234. isAuthenticated: !!token
  235. };
  236. };