Explorar o código

refactor(auth): 重构rencai-auth-ui认证架构,统一使用hooks目录

参照mini-enterprise-auth-ui模式重构认证架构:
- 将AuthContext从utils/迁移到hooks/useAuth.tsx
- 添加queryClient导出,使用React Query管理认证状态
- 移除utils目录,统一使用hooks导出路径
- 更新所有导入路径: utils → hooks

修改文件:
- mini-talent/src/app.tsx: 从hooks导入AuthProvider和queryClient
- mini-talent/src/pages/index/index.tsx: 更新导入路径
- mini-talent/src/pages/login/index.tsx: 更新导入路径
- mini-ui-packages/rencai-auth-ui/package.json: 添加hooks导出,移除utils导出
- mini-ui-packages/rencai-dashboard-ui/src/pages/Dashboard/Dashboard.tsx: 更新导入路径

新增文件:
- mini-ui-packages/rencai-auth-ui/src/hooks/useAuth.tsx
- mini-ui-packages/rencai-auth-ui/src/hooks/index.ts

删除文件:
- mini-ui-packages/rencai-auth-ui/src/utils/AuthContext.tsx
- mini-ui-packages/rencai-auth-ui/src/utils/index.ts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
yourname hai 3 semanas
pai
achega
d795646823

+ 1 - 2
mini-talent/src/app.tsx

@@ -3,8 +3,7 @@ import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
 import { PropsWithChildren } from 'react'
 import { useLaunch } from '@tarojs/taro'
 import { QueryClientProvider } from '@tanstack/react-query'
-import { AuthProvider } from '@d8d/rencai-auth-ui/utils'
-import { queryClient } from '@d8d/mini-shared-ui-components/utils/queryClient'
+import { AuthProvider, queryClient } from '@d8d/rencai-auth-ui/hooks'
 
 import './app.css'
 

+ 1 - 1
mini-talent/src/pages/index/index.tsx

@@ -2,7 +2,7 @@ import React, { useEffect } from 'react'
 import { View } from '@tarojs/components'
 import Taro from '@tarojs/taro'
 import DashboardPage from '@d8d/rencai-dashboard-ui/pages/Dashboard/Dashboard'
-import { AuthProvider, useAuth } from '@d8d/rencai-auth-ui/utils'
+import { AuthProvider, useAuth } from '@d8d/rencai-auth-ui/hooks'
 
 // 内部组件:使用认证状态
 function IndexPageContent() {

+ 1 - 1
mini-talent/src/pages/login/index.tsx

@@ -1,6 +1,6 @@
 // 从 @d8d/rencai-auth-ui 包导入LoginPage组件和AuthContext
 import LoginPage from '@d8d/rencai-auth-ui/pages/LoginPage/LoginPage'
-import { AuthProvider } from '@d8d/rencai-auth-ui/utils'
+import { AuthProvider } from '@d8d/rencai-auth-ui/hooks'
 
 // 登录页面 - 用AuthProvider包装
 export default function Login() {

+ 4 - 4
mini-ui-packages/rencai-auth-ui/package.json

@@ -21,10 +21,10 @@
       "import": "./dist/src/pages/LoginPage/LoginPage.js",
       "require": "./dist/src/pages/LoginPage/LoginPage.js"
     },
-    "./utils": {
-      "types": "./dist/src/utils/index.d.ts",
-      "import": "./dist/src/utils/index.js",
-      "require": "./dist/src/utils/index.js"
+    "./hooks": {
+      "types": "./dist/src/hooks/index.d.ts",
+      "import": "./dist/src/hooks/index.js",
+      "require": "./dist/src/hooks/index.js"
     }
   },
   "scripts": {

+ 1 - 0
mini-ui-packages/rencai-auth-ui/src/hooks/index.ts

@@ -0,0 +1 @@
+export { AuthProvider, useAuth, queryClient, type TalentUserInfo, type AuthContextType } from './useAuth'

+ 235 - 0
mini-ui-packages/rencai-auth-ui/src/hooks/useAuth.tsx

@@ -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

+ 1 - 1
mini-ui-packages/rencai-auth-ui/src/pages/LoginPage/LoginPage.tsx

@@ -1,6 +1,6 @@
 import React, { useState } from 'react'
 import { View, Text, Input, Button } from '@tarojs/components'
-import { useAuth } from '../../utils/AuthContext'
+import { useAuth } from '../../hooks'
 import Taro from '@tarojs/taro'
 
 /**

+ 0 - 214
mini-ui-packages/rencai-auth-ui/src/utils/AuthContext.tsx

@@ -1,214 +0,0 @@
-import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'
-import Taro from '@tarojs/taro'
-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'
-
-export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
-  const [isLoggedIn, setIsLoggedIn] = useState(false)
-  const [user, setUser] = useState<TalentUserInfo | null>(null)
-  const [token, setToken] = useState<string | null>(null)
-  const [loading, setLoading] = useState(true)
-
-  // 初始化:从本地存储恢复登录状态
-  useEffect(() => {
-    const initAuth = async () => {
-      try {
-        const storedToken = Taro.getStorageSync(TOKEN_KEY)
-        const storedUser = Taro.getStorageSync(USER_KEY)
-
-        if (storedToken && storedUser) {
-          setToken(storedToken)
-          setUser(storedUser)
-          setIsLoggedIn(true)
-        }
-      } catch (error) {
-        console.debug('恢复登录状态失败:', error)
-      } finally {
-        setLoading(false)
-      }
-    }
-
-    initAuth()
-  }, [])
-
-  // 类型定义:登录响应
-  type LoginResponse = InferResponseType<typeof talentAuthClient.login.$post, 200>
-
-  const login = async (identifier: string, password: string): Promise<void> => {
-    setLoading(true)
-    try {
-      // 调用登录API
-      const response = await talentAuthClient.login.$post({
-        body: {
-          identifier: identifier.trim(),
-          password: password.trim()
-        }
-      })
-
-      // 转换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)
-      setUser(userInfo)
-      setIsLoggedIn(true)
-    } catch (error: any) {
-      console.error('登录失败:', error)
-      // 提取错误消息
-      const errorMessage = error?.message || '登录失败,请稍后重试'
-      throw new Error(errorMessage)
-    } finally {
-      setLoading(false)
-    }
-  }
-
-  const logout = async (): Promise<void> => {
-    try {
-      // TODO: 调用退出API
-      // await talentAuthClient.logout.$post()
-
-      // 清除本地存储
-      Taro.removeStorageSync(TOKEN_KEY)
-      Taro.removeStorageSync(USER_KEY)
-
-      setToken(null)
-      setUser(null)
-      setIsLoggedIn(false)
-
-      // 跳转到登录页
-      Taro.reLaunch({ url: '/pages/login/index' })
-    } catch (error) {
-      console.error('退出失败:', error)
-      throw error
-    }
-  }
-
-  const refreshUser = async (): Promise<void> => {
-    if (!token) return
-
-    try {
-      // 调用获取用户信息API
-      const response = await talentAuthClient.me.$get()
-
-      // 转换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)
-      setUser(userInfo)
-    } catch (error) {
-      console.error('刷新用户信息失败:', error)
-      throw error
-    }
-  }
-
-  return (
-    <AuthContext.Provider
-      value={{
-        isLoggedIn,
-        user,
-        token,
-        login,
-        logout,
-        refreshUser,
-        loading,
-      }}
-    >
-      {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

+ 0 - 2
mini-ui-packages/rencai-auth-ui/src/utils/index.ts

@@ -1,2 +0,0 @@
-export { AuthProvider, useAuth } from './AuthContext';
-export type { AuthContextType, TalentUserInfo } from './AuthContext';

+ 1 - 1
mini-ui-packages/rencai-dashboard-ui/src/pages/Dashboard/Dashboard.tsx

@@ -2,7 +2,7 @@ import React, { useEffect } from 'react'
 import { View, Text, ScrollView } from '@tarojs/components'
 import Taro from '@tarojs/taro'
 import { TabBarLayout } from '@d8d/rencai-shared-ui/components'
-import { useAuth } from '@d8d/rencai-auth-ui/utils'
+import { useAuth } from '@d8d/rencai-auth-ui/hooks'
 import { talentDashboardClient } from '../../api'
 import type { InferResponseType } from 'hono'