Files
d8d-vite-starter/src/client/admin/hooks/AuthProvider.tsx
D8D Developer 12c6ed592f u
2025-06-27 03:02:46 +00:00

140 lines
3.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 {
useQuery,
useQueryClient,
} from '@tanstack/react-query';
import axios from 'axios';
import 'dayjs/locale/zh-cn';
import type {
AuthContextType
} from '@/share/types';
import { authClient } from '@/client/api';
import type { InferResponseType, InferRequestType } from 'hono/client';
type User = InferResponseType<typeof authClient.me.$get, 200>;
// 创建认证上下文
const AuthContext = createContext<AuthContextType<User> | 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 authClient.logout.$post();
}
} 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 res = await authClient.me.$get();
if (res.status !== 200) {
const result = await res.json();
throw new Error(result.message)
}
const currentUser = await res.json();
setUser(currentUser);
setIsAuthenticated(true);
return { isValid: true, user: currentUser };
} catch (error) {
return { isValid: false };
}
},
enabled: !!token,
refetchOnWindowFocus: false,
retry: false
});
const handleLogin = async (username: string, password: string, latitude?: number, longitude?: number): Promise<void> => {
try {
// 使用AuthAPI登录
const response = await authClient.login.$post({
json: {
username,
password
}
})
if (response.status !== 200) {
const result = await response.json()
throw new Error(result.message);
}
const result = await response.json()
// 保存token和用户信息
const { token: newToken, user: newUser } = result;
// 设置全局默认请求头
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 useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth必须在AuthProvider内部使用');
}
return context;
};