Files
d8d-admin-mobile-starter-pu…/client/mobile/hooks.tsx
yourname 1df9f7fea2 创建了api目录并拆分为10个功能模块文件
建立了index.ts统一导出
精简了原api.ts文件
2025-05-13 09:00:58 +00:00

246 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 { useQuery, useQueryClient } from '@tanstack/react-query';
import { getLocalStorageWithExpiry, setLocalStorageWithExpiry } from './utils.ts';
import type { User, AuthContextType, ThemeContextType, ThemeSettings } from '../share/types.ts';
import { ThemeMode, FontSize, CompactMode } from '../share/types.ts';
import { AuthAPI, ThemeAPI } from './api/index.ts';
// 默认主题设置
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 | null>(null);
// 创建主题上下文
const ThemeContext = createContext<ThemeContextType | null>(null);
// 认证提供者组件
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [token, setToken] = useState<string | null>(getLocalStorageWithExpiry('mobile_token'));
const [isAuthenticated, setIsAuthenticated] = useState(false);
const queryClient = useQueryClient();
// 使用useQuery检查登录状态
const { isLoading: isAuthChecking } = useQuery({
queryKey: ['auth', 'status', token],
queryFn: async () => {
if (!token) {
setUser(null);
setIsAuthenticated(false);
return null;
}
try {
// 设置请求头
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// 获取当前用户信息
const currentUser = await AuthAPI.getCurrentUser();
setUser(currentUser);
setIsAuthenticated(true);
setLocalStorageWithExpiry('mobile_user', currentUser, 24);
return { isValid: true, user: currentUser };
} catch (error) {
// 如果API调用失败自动登出
logout();
return { isValid: false };
}
},
enabled: !!token,
refetchOnWindowFocus: false,
retry: false,
});
// 登录函数
const login = async (username: string, password: string, latitude?: number, longitude?: number) => {
try {
const response = await AuthAPI.login(username, password, latitude, longitude);
const { token, user } = response;
// 保存到状态和本地存储
setToken(token);
setUser(user);
setLocalStorageWithExpiry('mobile_token', token, 24); // 24小时过期
setLocalStorageWithExpiry('mobile_user', user, 24);
// 设置请求头
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
} catch (error) {
console.error('登录失败:', error);
throw error;
}
};
// 登出函数
const logout = async () => {
try {
// 调用登出API
await AuthAPI.logout();
} catch (error) {
console.error('登出API调用失败:', error);
} finally {
// 无论API调用成功与否都清除本地状态
setToken(null);
setUser(null);
localStorage.removeItem('mobile_token');
localStorage.removeItem('mobile_user');
// 清除请求头
delete axios.defaults.headers.common['Authorization'];
// 清除所有查询缓存
queryClient.clear();
}
};
return (
<AuthContext.Provider
value={{
user,
token,
login,
logout,
isAuthenticated,
isLoading: isAuthChecking
}}
>
{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 ThemeAPI.updateThemeSettings(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 ThemeAPI.resetThemeSettings();
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>
);
};
// 使用上下文的钩子
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth必须在AuthProvider内部使用');
}
return context;
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme必须在ThemeProvider内部使用');
}
return context;
};