140 lines
3.8 KiB
TypeScript
140 lines
3.8 KiB
TypeScript
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;
|
||
}; |