Browse Source

feat: 新增登录鉴权层与引导/登录逻辑

- AuthContext:会话级 onboardingSeen/isLoggedIn/token,completeOnboarding/login/logout
- api.loginByPhone + LoginResult(mock,校验验证码>=4 位)
- useLogin hook 封装 loading/error,统一从 hooks 导出
master
doublex001 2 months ago
parent
commit
9c44c06afb
  1. 24
      src/api/api.ts
  2. 2
      src/api/index.ts
  3. 73
      src/context/AuthContext.tsx
  4. 1
      src/hooks/index.ts
  5. 37
      src/hooks/useLogin.ts

24
src/api/api.ts

@ -4862,3 +4862,27 @@ export async function sendChatMessage(
}; };
return mockRequest([userMessage, hrReply]); 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<LoginResult> {
if (code.trim().length < 4) {
return Promise.reject(new Error('验证码不正确'));
}
return mockRequest<LoginResult>({ token: `mock_token_${phone}`, phone });
}

2
src/api/index.ts

@ -1,6 +1,7 @@
/** /**
* 接口统一导出 * 接口统一导出
*/ */
export type { LoginResult } from './api';
export { export {
fetchAboutInfo, fetchAboutInfo,
fetchAbilityAssessment, fetchAbilityAssessment,
@ -70,6 +71,7 @@ export {
fetchDeleteAllData, fetchDeleteAllData,
submitDeleteData, submitDeleteData,
fetchLegalDocument, fetchLegalDocument,
loginByPhone,
fetchPersonalDataExport, fetchPersonalDataExport,
requestPersonalDataExport, requestPersonalDataExport,
fetchRankData, fetchRankData,

73
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<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;
}

1
src/hooks/index.ts

@ -52,3 +52,4 @@ export { useSalaryCalculator } from './useSalaryCalculator';
export { useSkillCloud } from './useSkillCloud'; export { useSkillCloud } from './useSkillCloud';
export { useTalentRecommendation } from './useTalentRecommendation'; export { useTalentRecommendation } from './useTalentRecommendation';
export { useWeightSettings } from './useWeightSettings'; export { useWeightSettings } from './useWeightSettings';
export { useLogin } from './useLogin';

37
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<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…
Cancel
Save