|
@@ -0,0 +1,147 @@
|
|
|
|
|
+import { createContext, useContext, PropsWithChildren, useState } from 'react'
|
|
|
|
|
+import Taro from '@tarojs/taro'
|
|
|
|
|
+import { authClient } from '../api'
|
|
|
|
|
+import { InferResponseType, InferRequestType } from 'hono'
|
|
|
|
|
+import { QueryClient, QueryClientProvider, useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
|
|
|
+
|
|
|
|
|
+// 用户类型定义
|
|
|
|
|
+export type User = InferResponseType<typeof authClient.me.$get, 200>
|
|
|
|
|
+type LoginRequest = InferRequestType<typeof authClient.login.$post>['json']
|
|
|
|
|
+type RegisterRequest = InferRequestType<typeof authClient.register.$post>['json']
|
|
|
|
|
+
|
|
|
|
|
+interface AuthContextType {
|
|
|
|
|
+ user: User | null
|
|
|
|
|
+ login: (data: LoginRequest) => Promise<User>
|
|
|
|
|
+ logout: () => Promise<void>
|
|
|
|
|
+ register: (data: RegisterRequest) => Promise<User>
|
|
|
|
|
+ isLoading: boolean
|
|
|
|
|
+ isLoggedIn: boolean
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
|
|
|
|
+
|
|
|
|
|
+const queryClient = new QueryClient()
|
|
|
|
|
+
|
|
|
|
|
+export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
|
|
+ const queryClient = useQueryClient()
|
|
|
|
|
+
|
|
|
|
|
+ const { data: user, isLoading } = useQuery<User | null, Error>({
|
|
|
|
|
+ queryKey: ['currentUser'],
|
|
|
|
|
+ queryFn: async () => {
|
|
|
|
|
+ const token = Taro.getStorageSync('token')
|
|
|
|
|
+ if (!token) {
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await authClient.me.$get({})
|
|
|
|
|
+ if (response.status !== 200) {
|
|
|
|
|
+ throw new Error('获取用户信息失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ const user = await response.json()
|
|
|
|
|
+ Taro.setStorageSync('userInfo', JSON.stringify(user))
|
|
|
|
|
+ return user
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Taro.removeStorageSync('token')
|
|
|
|
|
+ Taro.removeStorageSync('userInfo')
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ staleTime: Infinity, // 用户信息不常变动,设为无限期
|
|
|
|
|
+ refetchOnWindowFocus: false, // 失去焦点不重新获取
|
|
|
|
|
+ refetchOnReconnect: false, // 网络重连不重新获取
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const loginMutation = useMutation<User, Error, LoginRequest>({
|
|
|
|
|
+ mutationFn: async (data) => {
|
|
|
|
|
+ const response = await authClient.login.$post({ json: data })
|
|
|
|
|
+ if (response.status !== 200) {
|
|
|
|
|
+ throw new Error('登录失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ const { token, user } = await response.json()
|
|
|
|
|
+ Taro.setStorageSync('token', token)
|
|
|
|
|
+ Taro.setStorageSync('userInfo', JSON.stringify(user))
|
|
|
|
|
+ return user
|
|
|
|
|
+ },
|
|
|
|
|
+ onSuccess: (newUser) => {
|
|
|
|
|
+ queryClient.setQueryData(['currentUser'], newUser)
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error) => {
|
|
|
|
|
+ Taro.showToast({
|
|
|
|
|
+ title: error.message || '登录失败,请检查用户名和密码',
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ duration: 2000,
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const registerMutation = useMutation<User, Error, RegisterRequest>({
|
|
|
|
|
+ mutationFn: async (data) => {
|
|
|
|
|
+ const response = await authClient.register.$post({ json: data })
|
|
|
|
|
+ if (response.status !== 201) {
|
|
|
|
|
+ throw new Error('注册失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ const { token, user } = await response.json()
|
|
|
|
|
+ Taro.setStorageSync('token', token)
|
|
|
|
|
+ Taro.setStorageSync('userInfo', JSON.stringify(user))
|
|
|
|
|
+ return user
|
|
|
|
|
+ },
|
|
|
|
|
+ onSuccess: (newUser) => {
|
|
|
|
|
+ queryClient.setQueryData(['currentUser'], newUser)
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error) => {
|
|
|
|
|
+ Taro.showToast({
|
|
|
|
|
+ title: error.message || '注册失败,请重试',
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ duration: 2000,
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const logoutMutation = useMutation<void, Error>({
|
|
|
|
|
+ mutationFn: async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await authClient.logout.$post({})
|
|
|
|
|
+ if (response.status !== 200) {
|
|
|
|
|
+ throw new Error('登出失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Logout error:', error)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ Taro.removeStorageSync('token')
|
|
|
|
|
+ Taro.removeStorageSync('userInfo')
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onSuccess: () => {
|
|
|
|
|
+ queryClient.setQueryData(['currentUser'], null)
|
|
|
|
|
+ Taro.redirectTo({ url: '/pages/login/index' })
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error) => {
|
|
|
|
|
+ Taro.showToast({
|
|
|
|
|
+ title: error.message || '登出失败',
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ duration: 2000,
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const value = {
|
|
|
|
|
+ user: user || null,
|
|
|
|
|
+ login: loginMutation.mutateAsync,
|
|
|
|
|
+ logout: logoutMutation.mutateAsync,
|
|
|
|
|
+ register: registerMutation.mutateAsync,
|
|
|
|
|
+ isLoading: isLoading || loginMutation.isPending || registerMutation.isPending || logoutMutation.isPending,
|
|
|
|
|
+ isLoggedIn: !!user,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export const useAuth = () => {
|
|
|
|
|
+ const context = useContext(AuthContext)
|
|
|
|
|
+ if (context === undefined) {
|
|
|
|
|
+ throw new Error('useAuth must be used within an AuthProvider')
|
|
|
|
|
+ }
|
|
|
|
|
+ return context
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export { queryClient }
|