|
|
@@ -0,0 +1,235 @@
|
|
|
+import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'
|
|
|
+import Taro from '@tarojs/taro'
|
|
|
+import { QueryClient, useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
|
+import { talentAuthClient } from '../api'
|
|
|
+import type { InferResponseType } from 'hono'
|
|
|
+
|
|
|
+export interface TalentUserInfo {
|
|
|
+ userId: number
|
|
|
+ username: string
|
|
|
+ userType: 'talent'
|
|
|
+ personId?: number
|
|
|
+ phone?: string | null
|
|
|
+ nickname?: string | null
|
|
|
+ name?: string | null
|
|
|
+ // 从 disabled_person 表获取的人才详情
|
|
|
+ personInfo?: {
|
|
|
+ personId: number
|
|
|
+ name: string
|
|
|
+ disabilityType: string
|
|
|
+ idCard: string
|
|
|
+ disabilityId: string
|
|
|
+ phone: string
|
|
|
+ province: string
|
|
|
+ city: string
|
|
|
+ district?: string | null
|
|
|
+ detailedAddress?: string | null
|
|
|
+ jobStatus: number
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export interface AuthContextType {
|
|
|
+ isLoggedIn: boolean
|
|
|
+ user: TalentUserInfo | null
|
|
|
+ token: string | null
|
|
|
+ login: (identifier: string, password: string) => Promise<void>
|
|
|
+ logout: () => Promise<void>
|
|
|
+ refreshUser: () => Promise<void>
|
|
|
+ loading: boolean
|
|
|
+}
|
|
|
+
|
|
|
+const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
|
|
+
|
|
|
+const TOKEN_KEY = 'talent_token'
|
|
|
+const USER_KEY = 'talent_user'
|
|
|
+
|
|
|
+// 导出queryClient以供外部使用(与mini-enterprise-auth-ui保持一致)
|
|
|
+export const queryClient = new QueryClient()
|
|
|
+
|
|
|
+export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
|
|
+ const queryClient = useQueryClient()
|
|
|
+
|
|
|
+ const [isLoggedIn, setIsLoggedIn] = useState(false)
|
|
|
+ const [token, setToken] = useState<string | null>(null)
|
|
|
+ const [loading, setLoading] = useState(true)
|
|
|
+
|
|
|
+ // 使用React Query管理用户状态
|
|
|
+ const { data: user, isLoading } = useQuery<TalentUserInfo | null, Error>({
|
|
|
+ queryKey: ['talentCurrentUser'],
|
|
|
+ queryFn: async () => {
|
|
|
+ const storedToken = Taro.getStorageSync(TOKEN_KEY)
|
|
|
+ if (!storedToken) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await talentAuthClient.me.$get()
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取用户信息失败')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换API响应为UserInfo格式
|
|
|
+ const userInfo: TalentUserInfo = {
|
|
|
+ userId: response.id,
|
|
|
+ username: response.username,
|
|
|
+ userType: 'talent',
|
|
|
+ personId: response.personId || undefined,
|
|
|
+ phone: response.phone,
|
|
|
+ nickname: response.nickname,
|
|
|
+ name: response.name,
|
|
|
+ personInfo: response.personInfo ? {
|
|
|
+ personId: response.personInfo.id,
|
|
|
+ name: response.personInfo.name,
|
|
|
+ disabilityType: response.personInfo.disabilityType,
|
|
|
+ idCard: response.personInfo.idCard,
|
|
|
+ disabilityId: response.personInfo.disabilityId,
|
|
|
+ phone: response.personInfo.phone,
|
|
|
+ province: response.personInfo.province,
|
|
|
+ city: response.personInfo.city,
|
|
|
+ district: response.personInfo.district,
|
|
|
+ detailedAddress: response.personInfo.detailedAddress,
|
|
|
+ jobStatus: response.personInfo.jobStatus,
|
|
|
+ } : undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ // 缓存到本地存储
|
|
|
+ Taro.setStorageSync(USER_KEY, userInfo)
|
|
|
+ return userInfo
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取用户信息失败:', error)
|
|
|
+ Taro.removeStorageSync(TOKEN_KEY)
|
|
|
+ Taro.removeStorageSync(USER_KEY)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ staleTime: Infinity,
|
|
|
+ refetchOnWindowFocus: false,
|
|
|
+ refetchOnReconnect: false,
|
|
|
+ })
|
|
|
+
|
|
|
+ // 同步isLoggedIn状态
|
|
|
+ useEffect(() => {
|
|
|
+ setIsLoggedIn(!!user)
|
|
|
+ setToken(Taro.getStorageSync(TOKEN_KEY) || null)
|
|
|
+ setLoading(false)
|
|
|
+ }, [user])
|
|
|
+
|
|
|
+ const loginMutation = useMutation<void, Error, { identifier: string; password: string }>({
|
|
|
+ mutationFn: async ({ identifier, password }) => {
|
|
|
+ const response = await talentAuthClient.login.$post({
|
|
|
+ body: {
|
|
|
+ identifier: identifier.trim(),
|
|
|
+ password: password.trim()
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('登录失败')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换API响应为UserInfo格式
|
|
|
+ const userInfo: TalentUserInfo = {
|
|
|
+ userId: response.user.id,
|
|
|
+ username: response.user.username,
|
|
|
+ userType: 'talent',
|
|
|
+ personId: response.user.personId || undefined,
|
|
|
+ phone: response.user.phone,
|
|
|
+ nickname: response.user.nickname,
|
|
|
+ name: response.user.name,
|
|
|
+ personInfo: response.user.personInfo ? {
|
|
|
+ personId: response.user.personInfo.id,
|
|
|
+ name: response.user.personInfo.name,
|
|
|
+ disabilityType: response.user.personInfo.disabilityType,
|
|
|
+ idCard: response.user.personInfo.idCard,
|
|
|
+ disabilityId: response.user.personInfo.disabilityId,
|
|
|
+ phone: response.user.personInfo.phone,
|
|
|
+ province: response.user.personInfo.province,
|
|
|
+ city: response.user.personInfo.city,
|
|
|
+ district: response.user.personInfo.district,
|
|
|
+ detailedAddress: response.user.personInfo.detailedAddress,
|
|
|
+ jobStatus: response.user.personInfo.jobStatus,
|
|
|
+ } : undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存到本地存储
|
|
|
+ Taro.setStorageSync(TOKEN_KEY, response.token)
|
|
|
+ Taro.setStorageSync(USER_KEY, userInfo)
|
|
|
+
|
|
|
+ setToken(response.token)
|
|
|
+ },
|
|
|
+ onSuccess: () => {
|
|
|
+ // 刷新用户信息
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['talentCurrentUser'] })
|
|
|
+ },
|
|
|
+ onError: (error) => {
|
|
|
+ Taro.showToast({
|
|
|
+ title: error.message || '登录失败,请检查身份证号和密码',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ })
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ const logoutMutation = useMutation<void, Error>({
|
|
|
+ mutationFn: async () => {
|
|
|
+ try {
|
|
|
+ // TODO: 调用退出API
|
|
|
+ // await talentAuthClient.logout.$post()
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Logout error:', error)
|
|
|
+ } finally {
|
|
|
+ Taro.removeStorageSync(TOKEN_KEY)
|
|
|
+ Taro.removeStorageSync(USER_KEY)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onSuccess: () => {
|
|
|
+ queryClient.setQueryData(['talentCurrentUser'], null)
|
|
|
+ Taro.reLaunch({ url: '/pages/login/index' })
|
|
|
+ },
|
|
|
+ onError: (error) => {
|
|
|
+ Taro.showToast({
|
|
|
+ title: error.message || '登出失败',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ })
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ const login = async (identifier: string, password: string): Promise<void> => {
|
|
|
+ await loginMutation.mutateAsync({ identifier, password })
|
|
|
+ }
|
|
|
+
|
|
|
+ const logout = async (): Promise<void> => {
|
|
|
+ await logoutMutation.mutateAsync()
|
|
|
+ }
|
|
|
+
|
|
|
+ const refreshUser = async (): Promise<void> => {
|
|
|
+ await queryClient.invalidateQueries({ queryKey: ['talentCurrentUser'] })
|
|
|
+ }
|
|
|
+
|
|
|
+ const value: AuthContextType = {
|
|
|
+ isLoggedIn: !!user,
|
|
|
+ user: user || null,
|
|
|
+ token,
|
|
|
+ login,
|
|
|
+ logout,
|
|
|
+ refreshUser,
|
|
|
+ loading: loading || isLoading,
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <AuthContext.Provider value={value}>
|
|
|
+ {children}
|
|
|
+ </AuthContext.Provider>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export const useAuth = (): AuthContextType => {
|
|
|
+ const context = useContext(AuthContext)
|
|
|
+ if (context === undefined) {
|
|
|
+ throw new Error('useAuth must be used within an AuthProvider')
|
|
|
+ }
|
|
|
+ return context
|
|
|
+}
|
|
|
+
|
|
|
+export default AuthContext
|