import { useState } from 'react'; import { Ionicons } from '@expo/vector-icons'; import { Alert, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; import { colors, spacing } from '../../../src/theme'; import { useAgreementGate, useLoginLanding, useOnboarding, usePasswordLogin } from '../../../src/hooks'; import { isValidPhone } from '../../../src/utils'; import { useAuth } from '../../../src/context/AuthContext'; import { PasswordField } from '../../../src/components/PasswordField'; import { ScreenShell } from '../ui/ScreenShell'; import { AgreementCheckbox } from '../ui/AgreementCheckbox'; import { PrimaryButton } from '../ui/PrimaryButton'; import { AgreementModal } from '../ui/AgreementModal'; /** * P2P 手机号 + 密码登录。 * * 与 P2(验证码)、P2R(设置密码)互相可达:本页底部可切回验证码登录,忘记密码进 P2R。 * 登录成功后按是否已选过身份决定落地页,与 P1 微信分支的原逻辑一致。 */ export function P2PasswordLogin() { const { data, setData, navigate, back } = useOnboarding(); const { gate, modalProps } = useAgreementGate(); const { login, loading, error } = usePasswordLogin(); /** 会话写入 + 落地判定(双 Token + 服务器角色) */ const { land, go } = useLoginLanding(); const { verifySession } = useAuth(); const [password, setPassword] = useState(''); const agreed = data.agreed; const phone = data.phone; const phoneValid = isValidPhone(phone); const canSubmit = phoneValid && password.length > 0; const handleLogin = async () => { if (!canSubmit || loading) return; let result; try { result = await login(phone, password); } catch { // 失败原因由 usePasswordLogin 记录,页面下方展示 return; } // 登录响应里就带角色:直接按它落地(消费一次,不额外打接口) const target = land(result); if (target !== 'identity') { go(target); return; } /* * 登录响应要求或暗示还需选身份时,再用 /auth/me 复核一次: * 早先这里在 /auth/me 失败后「退回本地已选身份」并继续跳转 —— 本地角色只是上次的 * 缓存,服务器此刻可能已经把它改了(例如选了企业、又被后台调整)。拿过期缓存把人 * 放进业务页面,比停下来更糟。现在失败即提示重试,不进业务页。 */ try { const verified = await verifySession(); if (verified) { go(verified === 'enterprise' ? 'enterprise' : 'candidate'); return; } } catch (e) { Alert.alert('无法确认账号身份', (e as Error).message || '请检查网络后重试'); return; } navigate('P3'); }; return ( 密码登录 使用手机号与登录密码进入轻松聘 {/* 手机号 */} +86 setData({ phone: t.replace(/[^\d]/g, '').slice(0, 11) })} placeholder="请输入手机号" placeholderTextColor={colors.textHint} keyboardType="phone-pad" maxLength={11} /> {phone.length > 0 ? ( setData({ phone: '' })} hitSlop={8}> ) : null} {/* 密码 */} gate(() => navigate('P2R'))} hitSlop={8}> 忘记密码? gate(handleLogin)} /> {error ? {error.message} : null} setData({ agreed: !agreed })} text={ 已阅读并同意 navigate('B2')}>《轻松聘用户协议》 和 navigate('B2')}>《隐私政策》 ,允许轻松聘统一管理本人账号信息 } /> gate(() => navigate('P2'))} hitSlop={8}> 验证码登录 ); } const styles = StyleSheet.create({ h1: { fontSize: 26, fontWeight: '800', color: colors.textPrimary, marginTop: spacing.block }, subtitle: { fontSize: 14, color: colors.textSecondary, marginTop: spacing.sm }, fieldRow: { flexDirection: 'row', alignItems: 'center', height: 56, marginTop: 28, gap: 6 }, prefix: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, divider: { width: 1, height: 20, backgroundColor: colors.border, marginHorizontal: 10 }, input: { flex: 1, fontSize: 17, color: colors.textPrimary, padding: 0 }, underline: { height: 1, backgroundColor: colors.borderLight }, passwordArea: { marginTop: spacing.section }, forgotLink: { marginTop: spacing.block, alignSelf: 'flex-end', fontSize: 13, color: colors.primaryText, fontWeight: '600' }, btnArea: { marginTop: 26 }, formError: { marginTop: spacing.sm, textAlign: 'center', fontSize: 12, color: colors.danger }, agree: { marginTop: spacing.block }, agreeText: { fontSize: 12, color: colors.textSecondary, lineHeight: 18 }, link: { color: colors.primary, fontWeight: '600' }, switchLink: { marginTop: 26, textAlign: 'center', fontSize: 14, color: colors.textSecondary }, });