auth.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. updateUser: (userData: Partial<User>) => void
  16. isLoading: boolean
  17. isLoggedIn: boolean
  18. }
  19. const AuthContext = createContext<AuthContextType | undefined>(undefined)
  20. const queryClient = new QueryClient()
  21. export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
  22. const queryClient = useQueryClient()
  23. const { data: user, isLoading } = useQuery<User | null, Error>({
  24. queryKey: ['currentUser'],
  25. queryFn: async () => {
  26. const token = Taro.getStorageSync('token')
  27. if (!token) {
  28. return null
  29. }
  30. try {
  31. const response = await authClient.me.$get({})
  32. if (response.status !== 200) {
  33. throw new Error('获取用户信息失败')
  34. }
  35. const user = await response.json()
  36. Taro.setStorageSync('userInfo', JSON.stringify(user))
  37. return user
  38. } catch (error) {
  39. Taro.removeStorageSync('token')
  40. Taro.removeStorageSync('userInfo')
  41. return null
  42. }
  43. },
  44. staleTime: Infinity, // 用户信息不常变动,设为无限期
  45. refetchOnWindowFocus: false, // 失去焦点不重新获取
  46. refetchOnReconnect: false, // 网络重连不重新获取
  47. })
  48. const loginMutation = useMutation<User, Error, LoginRequest>({
  49. mutationFn: async (data) => {
  50. const response = await authClient.login.$post({ json: data })
  51. if (response.status !== 200) {
  52. throw new Error('登录失败')
  53. }
  54. const { token, user } = await response.json()
  55. Taro.setStorageSync('token', token)
  56. Taro.setStorageSync('userInfo', JSON.stringify(user))
  57. return user
  58. },
  59. onSuccess: (newUser) => {
  60. queryClient.setQueryData(['currentUser'], newUser)
  61. },
  62. onError: (error) => {
  63. Taro.showToast({
  64. title: error.message || '登录失败,请检查用户名和密码',
  65. icon: 'none',
  66. duration: 2000,
  67. })
  68. },
  69. })
  70. const registerMutation = useMutation<User, Error, RegisterRequest>({
  71. mutationFn: async (data) => {
  72. const response = await authClient.register.$post({ json: data })
  73. if (response.status !== 201) {
  74. throw new Error('注册失败')
  75. }
  76. const { token, user } = await response.json()
  77. Taro.setStorageSync('token', token)
  78. Taro.setStorageSync('userInfo', JSON.stringify(user))
  79. return user
  80. },
  81. onSuccess: (newUser) => {
  82. queryClient.setQueryData(['currentUser'], newUser)
  83. },
  84. onError: (error) => {
  85. Taro.showToast({
  86. title: error.message || '注册失败,请重试',
  87. icon: 'none',
  88. duration: 2000,
  89. })
  90. },
  91. })
  92. const logoutMutation = useMutation<void, Error>({
  93. mutationFn: async () => {
  94. try {
  95. const response = await authClient.logout.$post({})
  96. if (response.status !== 200) {
  97. throw new Error('登出失败')
  98. }
  99. } catch (error) {
  100. console.error('Logout error:', error)
  101. } finally {
  102. Taro.removeStorageSync('token')
  103. Taro.removeStorageSync('userInfo')
  104. }
  105. },
  106. onSuccess: () => {
  107. queryClient.setQueryData(['currentUser'], null)
  108. Taro.redirectTo({ url: '/pages/login/index' })
  109. },
  110. onError: (error) => {
  111. Taro.showToast({
  112. title: error.message || '登出失败',
  113. icon: 'none',
  114. duration: 2000,
  115. })
  116. },
  117. })
  118. const updateUserMutation = useMutation<User, Error, Partial<User>>({
  119. mutationFn: async (userData) => {
  120. const response = await authClient.me.$put({ json: userData })
  121. if (response.status !== 200) {
  122. throw new Error('更新用户信息失败')
  123. }
  124. const updatedUser = await response.json()
  125. Taro.setStorageSync('userInfo', JSON.stringify(updatedUser))
  126. return updatedUser
  127. },
  128. onSuccess: (updatedUser) => {
  129. queryClient.setQueryData(['currentUser'], updatedUser)
  130. Taro.showToast({
  131. title: '更新成功',
  132. icon: 'success',
  133. duration: 2000,
  134. })
  135. },
  136. onError: (error) => {
  137. Taro.showToast({
  138. title: error.message || '更新失败,请重试',
  139. icon: 'none',
  140. duration: 2000,
  141. })
  142. },
  143. })
  144. const updateUser = updateUserMutation.mutateAsync
  145. const value = {
  146. user: user || null,
  147. login: loginMutation.mutateAsync,
  148. logout: logoutMutation.mutateAsync,
  149. register: registerMutation.mutateAsync,
  150. updateUser,
  151. isLoading: isLoading || loginMutation.isPending || registerMutation.isPending || logoutMutation.isPending,
  152. isLoggedIn: !!user,
  153. }
  154. return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
  155. }
  156. export const useAuth = () => {
  157. const context = useContext(AuthContext)
  158. if (context === undefined) {
  159. throw new Error('useAuth must be used within an AuthProvider')
  160. }
  161. return context
  162. }
  163. export { queryClient }