Browse Source
- AuthContext:会话级 onboardingSeen/isLoggedIn/token,completeOnboarding/login/logout - api.loginByPhone + LoginResult(mock,校验验证码>=4 位) - useLogin hook 封装 loading/error,统一从 hooks 导出master
5 changed files with 137 additions and 0 deletions
@ -0,0 +1,73 @@ |
|||||
|
import { createContext, useContext, useState, type ReactNode } from 'react'; |
||||
|
|
||||
|
/* ===================== 鉴权(引导/登录)上下文 ===================== |
||||
|
* 管理「引导页是否看过」「是否已登录」「登录令牌」。 |
||||
|
* 与 RoleContext 配合形成完整漏斗: |
||||
|
* 首次启动 → 引导页 → 登录页 → 选择身份(identity-select) → 对应端。 |
||||
|
* |
||||
|
* 持久化:当前为会话级(内存)实现,App 重启后回到初始态, |
||||
|
* 需重新引导/登录。若已安装 @react-native-async-storage/async-storage, |
||||
|
* 可将底部 memoryAuth 替换为真实读写,即可跨重启记住引导/登录态。 |
||||
|
*/ |
||||
|
|
||||
|
export interface AuthContextValue { |
||||
|
/** 引导页是否已看完(首次启动展示引导页) */ |
||||
|
onboardingSeen: boolean; |
||||
|
/** 是否已登录 */ |
||||
|
isLoggedIn: boolean; |
||||
|
/** 登录令牌(mock 态为占位串) */ |
||||
|
token: string | null; |
||||
|
/** 标记引导页已看完 */ |
||||
|
completeOnboarding: () => void; |
||||
|
/** 登录成功,写入 token */ |
||||
|
login: (token: string) => void; |
||||
|
/** 退出登录,清空令牌 */ |
||||
|
logout: () => void; |
||||
|
} |
||||
|
|
||||
|
const AuthContext = createContext<AuthContextValue | undefined>(undefined); |
||||
|
|
||||
|
interface MemoryAuth { |
||||
|
onboardingSeen: boolean; |
||||
|
isLoggedIn: boolean; |
||||
|
token: string | null; |
||||
|
} |
||||
|
|
||||
|
/** 会话级内存状态(重启即重置) */ |
||||
|
let memoryAuth: MemoryAuth = { onboardingSeen: false, isLoggedIn: false, token: null }; |
||||
|
|
||||
|
export function AuthProvider({ children }: { children: ReactNode }) { |
||||
|
const [state, setState] = useState<MemoryAuth>(memoryAuth); |
||||
|
|
||||
|
const completeOnboarding = () => { |
||||
|
const next = { ...state, onboardingSeen: true }; |
||||
|
memoryAuth = next; |
||||
|
setState(next); |
||||
|
}; |
||||
|
|
||||
|
const login = (token: string) => { |
||||
|
const next = { ...state, isLoggedIn: true, token }; |
||||
|
memoryAuth = next; |
||||
|
setState(next); |
||||
|
}; |
||||
|
|
||||
|
const logout = () => { |
||||
|
const next = { ...state, isLoggedIn: false, token: null }; |
||||
|
memoryAuth = next; |
||||
|
setState(next); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<AuthContext.Provider value={{ ...state, completeOnboarding, login, logout }}> |
||||
|
{children} |
||||
|
</AuthContext.Provider> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function useAuth(): AuthContextValue { |
||||
|
const ctx = useContext(AuthContext); |
||||
|
if (!ctx) { |
||||
|
throw new Error('useAuth 必须在 AuthProvider 内部使用'); |
||||
|
} |
||||
|
return ctx; |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
import { useCallback, useState } from 'react'; |
||||
|
import { loginByPhone, type LoginResult } from '../api'; |
||||
|
|
||||
|
export interface UseLoginReturn { |
||||
|
/** 手机号 + 验证码登录,成功返回 LoginResult */ |
||||
|
login: (phone: string, code: string) => Promise<LoginResult>; |
||||
|
/** 登录进行中 */ |
||||
|
loading: boolean; |
||||
|
/** 登录失败的错误(用于页面提示) */ |
||||
|
error: Error | null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 登录(一次性变更,非取数)。 |
||||
|
* 包装 api.loginByPhone,向上暴露 loading / error 供页面控制按钮与提示。 |
||||
|
*/ |
||||
|
export function useLogin(): UseLoginReturn { |
||||
|
const [loading, setLoading] = useState(false); |
||||
|
const [error, setError] = useState<Error | null>(null); |
||||
|
|
||||
|
const login = useCallback(async (phone: string, code: string) => { |
||||
|
setLoading(true); |
||||
|
setError(null); |
||||
|
try { |
||||
|
const result = await loginByPhone(phone, code); |
||||
|
return result; |
||||
|
} catch (e) { |
||||
|
const err = e instanceof Error ? e : new Error('登录失败,请稍后重试'); |
||||
|
setError(err); |
||||
|
throw err; |
||||
|
} finally { |
||||
|
setLoading(false); |
||||
|
} |
||||
|
}, []); |
||||
|
|
||||
|
return { login, loading, error }; |
||||
|
} |
||||
Loading…
Reference in new issue