import { Redirect, useRouter } from 'expo-router'; import { useCallback, useEffect, useState } from 'react'; import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native'; import { useAuth } from '../src/context/AuthContext'; import { colors, spacing } from '../src/theme'; /** * 应用总入口(根路由 /)。 * 按「引导 → 登录 → 与服务端核对身份 → 对应端」漏斗放行。 * * **角色不以本地缓存为准**:本地存的 role 只代表上次是什么。冷启动有 Token 时先调 * `/auth/me` 核对,核对成功才跳业务页;核对失败**不**退回本地角色硬跳 —— * 那会把已被后台改成企业(或已禁用)的账号继续放进求职端,页面按错误身份渲染并一路报错。 * 失败时停在本页给出重试入口,把决定权交回用户。 */ export default function Index() { const { onboardingSeen, isTokenValid, isHydrated: authHydrated, role, isRoleVerified, verifySession, } = useAuth(); const [verifying, setVerifying] = useState(false); const [verifyError, setVerifyError] = useState(null); /** 重试计数:仅用于重新触发核对 effect */ const [attempt, setAttempt] = useState(0); const router = useRouter(); /** 需要核对的条件:本地存储已就绪 + 看过引导 + 有有效 Token + 尚未核对过 */ const needVerify = authHydrated && onboardingSeen && isTokenValid && !isRoleVerified; useEffect(() => { if (!needVerify) return; let active = true; setVerifying(true); setVerifyError(null); verifySession() .catch((e: unknown) => { if (active) setVerifyError(e instanceof Error ? e : new Error('身份核对失败')); }) .finally(() => { if (active) setVerifying(false); }); return () => { active = false; }; }, [needVerify, verifySession, attempt]); const retry = useCallback(() => setAttempt((n) => n + 1), []); // 本地存储读取完成前不做任何跳转决策,否则已登录用户会闪现引导页/注册页 if (!authHydrated) { return ; } if (!onboardingSeen) { return ; } if (!isTokenValid) { return ; } // 有 Token 但角色未与服务器核对:核对中或失败都不能放行 if (!isRoleVerified) { if (verifying) { return ( 正在确认登录状态… ); } return ( 无法确认登录状态 {verifyError?.message ?? '请检查网络后重试'} 重试 router.replace('/register')}> 重新登录 ); } // 核对通过但服务器说角色未定(UNASSIGNED)→ 去选身份 if (!role) { return ; } // 两个路由组均无 index,必须用显式落地路由,否则落地页空白 return ( ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.white, }, center: { alignItems: 'center', justifyContent: 'center', paddingHorizontal: spacing.framework, }, hint: { marginTop: spacing.card, fontSize: 13, color: colors.textSecondary, textAlign: 'center', }, errorTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary, }, retryBtn: { marginTop: spacing.framework, paddingHorizontal: 28, height: 40, borderRadius: spacing.card, alignItems: 'center', justifyContent: 'center', backgroundColor: colors.primaryBtn, }, retryText: { fontSize: 14, fontWeight: '600', color: colors.white, }, linkBtn: { marginTop: spacing.card, }, linkText: { fontSize: 13, fontWeight: '600', color: colors.primaryText, }, });