From 9c44c06afb3358fddf7ed91f14c4848546f9ed2c Mon Sep 17 00:00:00 2001 From: doublex001 <16157008+doublex001@user.noreply.gitee.com> Date: Mon, 31 Aug 2026 17:49:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=89=B4=E6=9D=83=E5=B1=82=E4=B8=8E=E5=BC=95=E5=AF=BC/?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AuthContext:会话级 onboardingSeen/isLoggedIn/token,completeOnboarding/login/logout - api.loginByPhone + LoginResult(mock,校验验证码>=4 位) - useLogin hook 封装 loading/error,统一从 hooks 导出 --- src/api/api.ts | 24 ++++++++++++ src/api/index.ts | 2 + src/context/AuthContext.tsx | 73 +++++++++++++++++++++++++++++++++++++ src/hooks/index.ts | 1 + src/hooks/useLogin.ts | 37 +++++++++++++++++++ 5 files changed, 137 insertions(+) create mode 100644 src/context/AuthContext.tsx create mode 100644 src/hooks/useLogin.ts diff --git a/src/api/api.ts b/src/api/api.ts index 14919ab..6c7f32b 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -4862,3 +4862,27 @@ export async function sendChatMessage( }; return mockRequest([userMessage, hrReply]); } + +/* ===================== 账号:手机号 + 验证码登录 ===================== */ + +export interface LoginResult { + /** 登录令牌(mock 态为占位串,真实后端返回 JWT 等) */ + token: string; + /** 登录手机号 */ + phone: string; +} + +/** + * 手机号 + 短信验证码登录(登录即注册,二合一)。 + * @method POST + * @url /api/auth/login-by-phone + * @param phone 手机号(11 位) + * @param code 短信验证码(6 位) + * @returns 登录结果(含 token) + */ +export function loginByPhone(phone: string, code: string): Promise { + if (code.trim().length < 4) { + return Promise.reject(new Error('验证码不正确')); + } + return mockRequest({ token: `mock_token_${phone}`, phone }); +} diff --git a/src/api/index.ts b/src/api/index.ts index 87a70d5..3e745d8 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,6 +1,7 @@ /** * 接口统一导出 */ +export type { LoginResult } from './api'; export { fetchAboutInfo, fetchAbilityAssessment, @@ -70,6 +71,7 @@ export { fetchDeleteAllData, submitDeleteData, fetchLegalDocument, + loginByPhone, fetchPersonalDataExport, requestPersonalDataExport, fetchRankData, diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx new file mode 100644 index 0000000..cf883e3 --- /dev/null +++ b/src/context/AuthContext.tsx @@ -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(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); + + 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 ( + + {children} + + ); +} + +export function useAuth(): AuthContextValue { + const ctx = useContext(AuthContext); + if (!ctx) { + throw new Error('useAuth 必须在 AuthProvider 内部使用'); + } + return ctx; +} diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 335e7b7..a9770ba 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -52,3 +52,4 @@ export { useSalaryCalculator } from './useSalaryCalculator'; export { useSkillCloud } from './useSkillCloud'; export { useTalentRecommendation } from './useTalentRecommendation'; export { useWeightSettings } from './useWeightSettings'; +export { useLogin } from './useLogin'; diff --git a/src/hooks/useLogin.ts b/src/hooks/useLogin.ts new file mode 100644 index 0000000..764b556 --- /dev/null +++ b/src/hooks/useLogin.ts @@ -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; + /** 登录进行中 */ + loading: boolean; + /** 登录失败的错误(用于页面提示) */ + error: Error | null; +} + +/** + * 登录(一次性变更,非取数)。 + * 包装 api.loginByPhone,向上暴露 loading / error 供页面控制按钮与提示。 + */ +export function useLogin(): UseLoginReturn { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(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 }; +}