Files
d8d-admin-mobile-starter-pu…/client/admin/hooks_sys.tsx
yourname 08ae3b85df 完整API模块结构:
client/admin/api/ ├── auth.ts (认证API) ├── users.ts (用户API) ├── files.ts (文件API) ├── theme.ts (主题API) ├── charts.ts (图表API) ├── messages.ts (消息API) ├── sys.ts (系统API) ├── know_info.ts (知识库API) ├── maps.ts (地图API) └── index.ts (统一入口)
2025-05-13 08:06:34 +00:00

307 lines
8.8 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, { useState, useEffect, createContext, useContext } from 'react';
import { ConfigProvider, theme, message
} from 'antd';
import zhCN from "antd/locale/zh_CN";
import {
useQuery,
useQueryClient,
useMutation
} from '@tanstack/react-query';
import axios from 'axios';
import dayjs from 'dayjs';
import weekday from 'dayjs/plugin/weekday';
import localeData from 'dayjs/plugin/localeData';
import 'dayjs/locale/zh-cn';
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';
// 配置 dayjs 插件
dayjs.extend(weekday);
dayjs.extend(localeData);
// 设置 dayjs 语言
dayjs.locale('zh-cn');
// 确保ConfigProvider能够正确使用中文日期
const locale = {
...zhCN,
DatePicker: {
...zhCN.DatePicker,
lang: {
...zhCN.DatePicker?.lang,
shortWeekDays: ['日', '一', '二', '三', '四', '五', '六'],
shortMonths: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
}
}
};
// 创建认证上下文
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>(localStorage.getItem('token'));
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const queryClient = useQueryClient();
// 声明handleLogout函数
const handleLogout = async () => {
try {
// 如果已登录调用登出API
if (token) {
await AuthAPI.logout();
}
} catch (error) {
console.error('登出请求失败:', error);
} finally {
// 清除本地状态
setToken(null);
setUser(null);
setIsAuthenticated(false);
localStorage.removeItem('token');
// 清除Authorization头
delete axios.defaults.headers.common['Authorization'];
console.log('登出时已删除全局Authorization头');
// 清除所有查询缓存
queryClient.clear();
}
};
// 使用useQuery检查登录状态
const { isLoading } = useQuery({
queryKey: ['auth', 'status', token],
queryFn: async () => {
if (!token) {
setIsAuthenticated(false);
setUser(null);
return null;
}
try {
// 设置全局默认请求头
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// 使用API验证当前用户
const currentUser = await AuthAPI.getCurrentUser();
setUser(currentUser);
setIsAuthenticated(true);
return { isValid: true, user: currentUser };
} catch (error) {
// 如果API调用失败自动登出
handleLogout();
return { isValid: false };
}
},
enabled: !!token,
refetchOnWindowFocus: false,
retry: false
});
// 设置请求拦截器
useEffect(() => {
// 设置响应拦截器处理401错误
const responseInterceptor = axios.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
console.log('检测到401错误执行登出操作');
handleLogout();
}
return Promise.reject(error);
}
);
// 清理拦截器
return () => {
axios.interceptors.response.eject(responseInterceptor);
};
}, [token]);
const handleLogin = async (username: string, password: string, latitude?: number, longitude?: number): Promise<void> => {
try {
// 使用AuthAPI登录
const response = await AuthAPI.login(username, password, latitude, longitude);
// 保存token和用户信息
const { token: newToken, user: newUser } = response;
// 设置全局默认请求头
axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
// 保存状态
setToken(newToken);
setUser(newUser);
setIsAuthenticated(true);
localStorage.setItem('token', newToken);
} catch (error) {
console.error('登录失败:', error);
throw error;
}
};
return (
<AuthContext.Provider
value={{
user,
token,
login: handleLogin,
logout: handleLogout,
isAuthenticated,
isLoading
}}
>
{children}
</AuthContext.Provider>
);
};
// 主题提供器组件
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [isDark, setIsDark] = useState(false);
const [currentTheme, setCurrentTheme] = useState<ThemeSettings>({
user_id: 0,
theme_mode: ThemeMode.LIGHT,
primary_color: '#1890ff',
font_size: FontSize.MEDIUM,
is_compact: CompactMode.NORMAL
});
// 获取主题设置
const { isLoading: isThemeLoading } = useQuery({
queryKey: ['theme', 'settings'],
queryFn: async () => {
try {
const settings = await ThemeAPI.getThemeSettings();
setCurrentTheme(settings);
setIsDark(settings.theme_mode === ThemeMode.DARK);
return settings;
} catch (error) {
console.error('获取主题设置失败:', error);
return null;
}
},
refetchOnWindowFocus: false,
enabled: !!localStorage.getItem('token')
});
// 预览主题设置(不保存到后端)
const previewTheme = (newTheme: Partial<ThemeSettings>) => {
const updatedTheme = { ...currentTheme, ...newTheme };
setCurrentTheme(updatedTheme);
setIsDark(updatedTheme.theme_mode === ThemeMode.DARK);
};
// 更新主题设置(保存到后端)
const updateThemeMutation = useMutation({
mutationFn: async (newTheme: Partial<ThemeSettings>) => {
return await ThemeAPI.updateThemeSettings(newTheme);
},
onSuccess: (data) => {
setCurrentTheme(data);
setIsDark(data.theme_mode === ThemeMode.DARK);
message.success('主题设置已更新');
},
onError: (error) => {
console.error('更新主题设置失败:', error);
message.error('更新主题设置失败');
}
});
// 重置主题设置
const resetThemeMutation = useMutation({
mutationFn: async () => {
return await ThemeAPI.resetThemeSettings();
},
onSuccess: (data) => {
setCurrentTheme(data);
setIsDark(data.theme_mode === ThemeMode.DARK);
message.success('主题设置已重置为默认值');
},
onError: (error) => {
console.error('重置主题设置失败:', error);
message.error('重置主题设置失败');
}
});
// 添加 toggleTheme 方法
const toggleTheme = () => {
const newTheme = {
...currentTheme,
theme_mode: isDark ? ThemeMode.LIGHT : ThemeMode.DARK
};
setIsDark(!isDark);
setCurrentTheme(newTheme);
};
return (
<ThemeContext.Provider value={{
isDark,
currentTheme,
updateTheme: previewTheme,
saveTheme: updateThemeMutation.mutateAsync,
resetTheme: resetThemeMutation.mutateAsync,
toggleTheme
}}>
<ConfigProvider
theme={{
algorithm: currentTheme.is_compact === CompactMode.COMPACT
? [isDark ? theme.darkAlgorithm : theme.defaultAlgorithm, theme.compactAlgorithm]
: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm,
token: {
colorPrimary: currentTheme.primary_color,
fontSize: currentTheme.font_size === FontSize.SMALL ? 12 :
currentTheme.font_size === FontSize.MEDIUM ? 14 : 16,
// colorBgBase: isDark ? undefined : currentTheme.background_color || '#fff',
colorBgBase: currentTheme.background_color,
borderRadius: currentTheme.border_radius ?? 6,
colorTextBase: currentTheme.text_color || (isDark ? '#fff' : '#000'),
},
components: {
Layout: {
// headerBg: isDark ? undefined : currentTheme.background_color || '#fff',
// siderBg: isDark ? undefined : currentTheme.background_color || '#fff',
headerBg: currentTheme.background_color,
siderBg: currentTheme.background_color,
}
}
}}
locale={locale as any}
>
{children}
</ConfigProvider>
</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;
};