AuthContext.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'
  2. import Taro from '@tarojs/taro'
  3. import { talentAuthClient } from '../api'
  4. import type { InferResponseType } from 'hono'
  5. export interface TalentUserInfo {
  6. userId: number
  7. username: string
  8. userType: 'talent'
  9. personId?: number
  10. phone?: string | null
  11. nickname?: string | null
  12. name?: string | null
  13. // 从 disabled_person 表获取的人才详情
  14. personInfo?: {
  15. personId: number
  16. name: string
  17. disabilityType: string
  18. idCard: string
  19. disabilityId: string
  20. phone: string
  21. province: string
  22. city: string
  23. district?: string | null
  24. detailedAddress?: string | null
  25. jobStatus: number
  26. }
  27. }
  28. export interface AuthContextType {
  29. isLoggedIn: boolean
  30. user: TalentUserInfo | null
  31. token: string | null
  32. login: (identifier: string, password: string) => Promise<void>
  33. logout: () => Promise<void>
  34. refreshUser: () => Promise<void>
  35. loading: boolean
  36. }
  37. const AuthContext = createContext<AuthContextType | undefined>(undefined)
  38. const TOKEN_KEY = 'talent_token'
  39. const USER_KEY = 'talent_user'
  40. export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
  41. const [isLoggedIn, setIsLoggedIn] = useState(false)
  42. const [user, setUser] = useState<TalentUserInfo | null>(null)
  43. const [token, setToken] = useState<string | null>(null)
  44. const [loading, setLoading] = useState(true)
  45. // 初始化:从本地存储恢复登录状态
  46. useEffect(() => {
  47. const initAuth = async () => {
  48. try {
  49. const storedToken = Taro.getStorageSync(TOKEN_KEY)
  50. const storedUser = Taro.getStorageSync(USER_KEY)
  51. if (storedToken && storedUser) {
  52. setToken(storedToken)
  53. setUser(storedUser)
  54. setIsLoggedIn(true)
  55. }
  56. } catch (error) {
  57. console.debug('恢复登录状态失败:', error)
  58. } finally {
  59. setLoading(false)
  60. }
  61. }
  62. initAuth()
  63. }, [])
  64. // 类型定义:登录响应
  65. type LoginResponse = InferResponseType<typeof talentAuthClient.login.$post, 200>
  66. const login = async (identifier: string, password: string): Promise<void> => {
  67. setLoading(true)
  68. try {
  69. // 调用登录API
  70. const response = await talentAuthClient.login.$post({
  71. body: {
  72. identifier: identifier.trim(),
  73. password: password.trim()
  74. }
  75. })
  76. // 转换API响应为UserInfo格式
  77. const userInfo: TalentUserInfo = {
  78. userId: response.user.id,
  79. username: response.user.username,
  80. userType: 'talent',
  81. personId: response.user.personId || undefined,
  82. phone: response.user.phone,
  83. nickname: response.user.nickname,
  84. name: response.user.name,
  85. personInfo: response.user.personInfo ? {
  86. personId: response.user.personInfo.id,
  87. name: response.user.personInfo.name,
  88. disabilityType: response.user.personInfo.disabilityType,
  89. idCard: response.user.personInfo.idCard,
  90. disabilityId: response.user.personInfo.disabilityId,
  91. phone: response.user.personInfo.phone,
  92. province: response.user.personInfo.province,
  93. city: response.user.personInfo.city,
  94. district: response.user.personInfo.district,
  95. detailedAddress: response.user.personInfo.detailedAddress,
  96. jobStatus: response.user.personInfo.jobStatus,
  97. } : undefined
  98. }
  99. // 保存到本地存储
  100. Taro.setStorageSync(TOKEN_KEY, response.token)
  101. Taro.setStorageSync(USER_KEY, userInfo)
  102. setToken(response.token)
  103. setUser(userInfo)
  104. setIsLoggedIn(true)
  105. } catch (error: any) {
  106. console.error('登录失败:', error)
  107. // 提取错误消息
  108. const errorMessage = error?.message || '登录失败,请稍后重试'
  109. throw new Error(errorMessage)
  110. } finally {
  111. setLoading(false)
  112. }
  113. }
  114. const logout = async (): Promise<void> => {
  115. try {
  116. // TODO: 调用退出API
  117. // await talentAuthClient.logout.$post()
  118. // 清除本地存储
  119. Taro.removeStorageSync(TOKEN_KEY)
  120. Taro.removeStorageSync(USER_KEY)
  121. setToken(null)
  122. setUser(null)
  123. setIsLoggedIn(false)
  124. // 跳转到登录页
  125. Taro.reLaunch({ url: '/pages/login/index' })
  126. } catch (error) {
  127. console.error('退出失败:', error)
  128. throw error
  129. }
  130. }
  131. const refreshUser = async (): Promise<void> => {
  132. if (!token) return
  133. try {
  134. // 调用获取用户信息API
  135. const response = await talentAuthClient.me.$get()
  136. // 转换API响应为UserInfo格式
  137. const userInfo: TalentUserInfo = {
  138. userId: response.id,
  139. username: response.username,
  140. userType: 'talent',
  141. personId: response.personId || undefined,
  142. phone: response.phone,
  143. nickname: response.nickname,
  144. name: response.name,
  145. personInfo: response.personInfo ? {
  146. personId: response.personInfo.id,
  147. name: response.personInfo.name,
  148. disabilityType: response.personInfo.disabilityType,
  149. idCard: response.personInfo.idCard,
  150. disabilityId: response.personInfo.disabilityId,
  151. phone: response.personInfo.phone,
  152. province: response.personInfo.province,
  153. city: response.personInfo.city,
  154. district: response.personInfo.district,
  155. detailedAddress: response.personInfo.detailedAddress,
  156. jobStatus: response.personInfo.jobStatus,
  157. } : undefined
  158. }
  159. // 更新本地存储
  160. Taro.setStorageSync(USER_KEY, userInfo)
  161. setUser(userInfo)
  162. } catch (error) {
  163. console.error('刷新用户信息失败:', error)
  164. throw error
  165. }
  166. }
  167. return (
  168. <AuthContext.Provider
  169. value={{
  170. isLoggedIn,
  171. user,
  172. token,
  173. login,
  174. logout,
  175. refreshUser,
  176. loading,
  177. }}
  178. >
  179. {children}
  180. </AuthContext.Provider>
  181. )
  182. }
  183. export const useAuth = (): AuthContextType => {
  184. const context = useContext(AuthContext)
  185. if (context === undefined) {
  186. throw new Error('useAuth must be used within an AuthProvider')
  187. }
  188. return context
  189. }
  190. export default AuthContext