添加管理端和移动端的多个新功能模块,包括文件上传、在线地图、用户认证、系统设置等,优化代码结构,提升可维护性和用户体验。

This commit is contained in:
zyh
2025-04-10 02:25:25 +00:00
parent 01dea6efa1
commit b1a6b608c6
31 changed files with 1366 additions and 46 deletions

318
client/admin/hooks_sys.tsx Normal file
View File

@@ -0,0 +1,318 @@
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.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(() => {
// console.log('token状态变化当前token:', token);
// if (token) {
// // 从localStorage中恢复token时设置全局请求头
// axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// console.log('从状态中恢复全局Authorization头:', axios.defaults.headers.common['Authorization']);
// } else {
// // 登出时删除请求头
// delete axios.defaults.headers.common['Authorization'];
// console.log('已删除全局Authorization头');
// }
// 设置响应拦截器处理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): Promise<void> => {
try {
// 使用AuthAPI登录
const response = await AuthAPI.login(username, password);
// 保存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;
};