auth.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { createContext, useContext, PropsWithChildren, useState } from 'react'
  2. import Taro from '@tarojs/taro'
  3. import { authClient } from '../api'
  4. import { InferResponseType, InferRequestType } from 'hono'
  5. import { QueryClient, QueryClientProvider, useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
  6. // 用户类型定义
  7. export type User = InferResponseType<typeof authClient.me.$get, 200>
  8. type LoginRequest = InferRequestType<typeof authClient.login.$post>['json']
  9. type RegisterRequest = InferRequestType<typeof authClient.register.$post>['json']
  10. interface AuthContextType {
  11. user: User | null
  12. login: (data: LoginRequest) => Promise<User>
  13. logout: () => Promise<void>
  14. register: (data: RegisterRequest) => Promise<User>
  15. isLoading: boolean
  16. isLoggedIn: boolean
  17. }
  18. const AuthContext = createContext<AuthContextType | undefined>(undefined)
  19. const queryClient = new QueryClient()
  20. export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
  21. const queryClient = useQueryClient()
  22. const { data: user, isLoading } = useQuery<User | null, Error>({
  23. queryKey: ['currentUser'],
  24. queryFn: async () => {
  25. const token = Taro.getStorageSync('token')
  26. if (!token) {
  27. return null
  28. }
  29. try {
  30. const response = await authClient.me.$get({})
  31. if (response.status !== 200) {
  32. throw new Error('获取用户信息失败')
  33. }
  34. const user = await response.json()
  35. Taro.setStorageSync('userInfo', JSON.stringify(user))
  36. return user
  37. } catch (error) {
  38. Taro.removeStorageSync('token')
  39. Taro.removeStorageSync('userInfo')
  40. return null
  41. }
  42. },
  43. staleTime: Infinity, // 用户信息不常变动,设为无限期
  44. refetchOnWindowFocus: false, // 失去焦点不重新获取
  45. refetchOnReconnect: false, // 网络重连不重新获取
  46. })
  47. const loginMutation = useMutation<User, Error, LoginRequest>({
  48. mutationFn: async (data) => {
  49. const response = await authClient.login.$post({ json: data })
  50. if (response.status !== 200) {
  51. throw new Error('登录失败')
  52. }
  53. const { token, user } = await response.json()
  54. Taro.setStorageSync('token', token)
  55. Taro.setStorageSync('userInfo', JSON.stringify(user))
  56. return user
  57. },
  58. onSuccess: (newUser) => {
  59. queryClient.setQueryData(['currentUser'], newUser)
  60. },
  61. onError: (error) => {
  62. Taro.showToast({
  63. title: error.message || '登录失败,请检查用户名和密码',
  64. icon: 'none',
  65. duration: 2000,
  66. })
  67. },
  68. })
  69. const registerMutation = useMutation<User, Error, RegisterRequest>({
  70. mutationFn: async (data) => {
  71. const response = await authClient.register.$post({ json: data })
  72. if (response.status !== 201) {
  73. throw new Error('注册失败')
  74. }
  75. const { token, user } = await response.json()
  76. Taro.setStorageSync('token', token)
  77. Taro.setStorageSync('userInfo', JSON.stringify(user))
  78. return user
  79. },
  80. onSuccess: (newUser) => {
  81. queryClient.setQueryData(['currentUser'], newUser)
  82. },
  83. onError: (error) => {
  84. Taro.showToast({
  85. title: error.message || '注册失败,请重试',
  86. icon: 'none',
  87. duration: 2000,
  88. })
  89. },
  90. })
  91. const logoutMutation = useMutation<void, Error>({
  92. mutationFn: async () => {
  93. try {
  94. const response = await authClient.logout.$post({})
  95. if (response.status !== 200) {
  96. throw new Error('登出失败')
  97. }
  98. } catch (error) {
  99. console.error('Logout error:', error)
  100. } finally {
  101. Taro.removeStorageSync('token')
  102. Taro.removeStorageSync('userInfo')
  103. }
  104. },
  105. onSuccess: () => {
  106. queryClient.setQueryData(['currentUser'], null)
  107. Taro.redirectTo({ url: '/pages/login/index' })
  108. },
  109. onError: (error) => {
  110. Taro.showToast({
  111. title: error.message || '登出失败',
  112. icon: 'none',
  113. duration: 2000,
  114. })
  115. },
  116. })
  117. const value = {
  118. user: user || null,
  119. login: loginMutation.mutateAsync,
  120. logout: logoutMutation.mutateAsync,
  121. register: registerMutation.mutateAsync,
  122. isLoading: isLoading || loginMutation.isPending || registerMutation.isPending || logoutMutation.isPending,
  123. isLoggedIn: !!user,
  124. }
  125. return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
  126. }
  127. export const useAuth = () => {
  128. const context = useContext(AuthContext)
  129. if (context === undefined) {
  130. throw new Error('useAuth must be used within an AuthProvider')
  131. }
  132. return context
  133. }
  134. export { queryClient }