AuthProvider.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import React, { useState, createContext, useContext } from 'react';
  2. import {
  3. useQuery,
  4. useQueryClient,
  5. } from '@tanstack/react-query';
  6. import axios from 'axios';
  7. import 'dayjs/locale/zh-cn';
  8. import type {
  9. AuthContextType
  10. } from '@/share/types';
  11. import { authClient } from '@/client/api';
  12. import type { InferResponseType } from 'hono/client';
  13. type User = InferResponseType<typeof authClient.me.$get, 200>;
  14. // 创建认证上下文
  15. const AuthContext = createContext<AuthContextType<User> | null>(null);
  16. // 认证提供器组件
  17. export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  18. const [user, setUser] = useState<User | null>(null);
  19. const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
  20. const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
  21. const queryClient = useQueryClient();
  22. // 声明handleLogout函数
  23. const handleLogout = async () => {
  24. try {
  25. // 如果已登录,调用登出API
  26. if (token) {
  27. await authClient.logout.$post();
  28. }
  29. } catch (error) {
  30. console.error('登出请求失败:', error);
  31. } finally {
  32. // 清除本地状态
  33. setToken(null);
  34. setUser(null);
  35. setIsAuthenticated(false);
  36. localStorage.removeItem('token');
  37. // 清除Authorization头
  38. delete axios.defaults.headers.common['Authorization'];
  39. console.log('登出时已删除全局Authorization头');
  40. // 清除所有查询缓存
  41. queryClient.clear();
  42. }
  43. };
  44. // 使用useQuery检查登录状态
  45. const { isLoading } = useQuery({
  46. queryKey: ['auth', 'status', token],
  47. queryFn: async () => {
  48. if (!token) {
  49. setIsAuthenticated(false);
  50. setUser(null);
  51. return null;
  52. }
  53. try {
  54. // 设置全局默认请求头
  55. axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
  56. // 使用API验证当前用户
  57. const res = await authClient.me.$get();
  58. if (res.status !== 200) {
  59. const result = await res.json();
  60. throw new Error(result.message)
  61. }
  62. const currentUser = await res.json();
  63. setUser(currentUser);
  64. setIsAuthenticated(true);
  65. return { isValid: true, user: currentUser };
  66. } catch (error) {
  67. return { isValid: false };
  68. }
  69. },
  70. enabled: !!token,
  71. refetchOnWindowFocus: false,
  72. retry: false
  73. });
  74. const handleLogin = async (username: string, password: string): Promise<void> => {
  75. try {
  76. // 使用AuthAPI登录
  77. const response = await authClient.login.$post({
  78. json: {
  79. username,
  80. password
  81. }
  82. },
  83. import.meta.env.DEV ? {
  84. headers: {
  85. 'X-Tenant-Id': '2'
  86. }
  87. }: undefined )
  88. if (response.status !== 200) {
  89. const result = await response.json()
  90. throw new Error(result.message);
  91. }
  92. const result = await response.json()
  93. // 保存token和用户信息
  94. const { token: newToken, user: newUser } = result;
  95. // 设置全局默认请求头
  96. axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
  97. // 保存状态
  98. setToken(newToken);
  99. setUser(newUser);
  100. setIsAuthenticated(true);
  101. localStorage.setItem('token', newToken);
  102. } catch (error) {
  103. console.error('登录失败:', error);
  104. throw error;
  105. }
  106. };
  107. return (
  108. <AuthContext.Provider
  109. value={{
  110. user,
  111. token,
  112. login: handleLogin,
  113. logout: handleLogout,
  114. isAuthenticated,
  115. isLoading
  116. }}
  117. >
  118. {children}
  119. </AuthContext.Provider>
  120. );
  121. };
  122. // 使用上下文的钩子
  123. export const useAuth = () => {
  124. const context = useContext(AuthContext);
  125. if (!context) {
  126. throw new Error('useAuth必须在AuthProvider内部使用');
  127. }
  128. return context;
  129. };