import { useState } from 'react'; import { Ionicons } from '@expo/vector-icons'; import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; import { colors } from '../../../src/theme'; import { useOnboarding, useWechatBindPhone } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { CountdownButton } from '../ui/CountdownButton'; import { AgreementCheckbox } from '../ui/AgreementCheckbox'; import { PrimaryButton } from '../ui/PrimaryButton'; import { AgreementModal } from '../ui/AgreementModal'; import { SliderCaptcha } from '../ui/SliderCaptcha'; const PHONE_RE = /^1\d{10}$/; /** P2 登录/注册:阶段1 手机号(对齐参考样式)→ 阶段2 验证码 + 60s 倒计时 */ export function P2PhoneCode() { const { data, setData, navigate, back } = useOnboarding(); const [code, setCode] = useState(''); const [codeSent, setCodeSent] = useState(false); /** 未勾选协议时被拦截的动作 */ const [pending, setPending] = useState<(() => void) | null>(null); const [captchaVisible, setCaptchaVisible] = useState(false); /** 微信第一次登录的绑定注册能力 */ const { bind, loading: binding, error: bindError } = useWechatBindPhone(); const agreed = data.agreed; const phone = data.phone; const phoneValid = PHONE_RE.test(phone); /** 有值说明本页是从「微信第一次登录」进来的,提交时需完成微信绑定 */ const wechatTicket = data.wechatTicket; const gate = (action: () => void) => { if (agreed) { action(); } else { setPending(() => action); } }; const accept = () => { setData({ agreed: true }); const act = pending; setPending(null); act?.(); }; /** 阶段1 → 阶段2:发送验证码 */ const sendCode = () => { if (!phoneValid) return; setCodeSent(true); }; /* * 提交验证码: * - 微信第一次登录(data.wechatTicket 有值):调绑定注册接口把 openid 与手机号关联, * 成功即写入登录态,再进 P3 选择身份;失败则留在当前页并提示。 * - 其他链路:沿用原逻辑,仅记录手机号后进 P3。 */ const handleLogin = async () => { if (code.length < 4) return; setData({ phone, code }); if (wechatTicket) { try { await bind({ token: wechatTicket.token, openid: wechatTicket.openid, phone, code, }); } catch { return; } } navigate('P3'); }; /** 掩码手机号:138****1234 */ const maskedPhone = phone.length === 11 ? `${phone.slice(0, 3)}****${phone.slice(7)}` : phone; return ( {!codeSent ? ( /* ---------- 阶段1:手机号 ---------- */ 登录/注册 首次验证通过即注册有职账号 +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(sendCode)} /> setData({ agreed: !agreed })} text={ 已阅读并同意 navigate('B2')}>《有职用户协议》 和 navigate('B2')}>《隐私政策》 ,允许有职统一管理本人账号信息 } /> {/* 分隔线 + 其他登录方式 */} 或通过以下方式登录 gate(() => setCaptchaVisible(true))}> gate(sendCode)}> gate(sendCode)}> {'数字\n身份'} ) : ( /* ---------- 阶段2:验证码 ---------- */ 输入验证码 验证码已发送至 {maskedPhone} setCode(t.replace(/[^\d]/g, '').slice(0, 6))} placeholder="请输入验证码" placeholderTextColor={colors.textHint} keyboardType="number-pad" maxLength={6} /> setCodeSent(true)} /> = 4 && !binding} onPress={() => gate(handleLogin)} /> {bindError ? {bindError.message} : null} setData({ agreed: !agreed })} text={ 已阅读并同意 navigate('B2')}>《有职用户协议》 和 navigate('B2')}>《隐私政策》 ,允许有职统一管理本人账号信息 } /> )} { setCaptchaVisible(false); navigate('P2W'); }} onClose={() => setCaptchaVisible(false)} /> setPending(null)} onOpenAgreement={() => { setPending(null); navigate('B2'); }} /> ); } const styles = StyleSheet.create({ h1: { fontSize: 26, fontWeight: '800', color: colors.textPrimary, marginTop: 12 }, subtitle: { fontSize: 14, color: colors.textSecondary, marginTop: 10 }, phoneRow: { 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 }, phoneInput: { flex: 1, fontSize: 17, color: colors.textPrimary, padding: 0 }, underline: { height: 1, backgroundColor: colors.borderLight }, smsHelp: { fontSize: 13, color: colors.textHint, marginTop: 16 }, btnArea: { marginTop: 28 }, agree: { marginTop: 18 }, bindError: { marginTop: 10, textAlign: 'center', fontSize: 12, color: colors.danger }, agreeText: { fontSize: 12, color: colors.textSecondary, lineHeight: 18 }, link: { color: colors.primary, fontWeight: '600' }, orRow: { flexDirection: 'row', alignItems: 'center', marginTop: 40, gap: 12 }, orLine: { flex: 1, height: 1, backgroundColor: colors.borderLight }, orText: { fontSize: 12, color: colors.textHint }, socialRow: { flexDirection: 'row', justifyContent: 'center', gap: 28, marginTop: 26 }, socialCircle: { width: 56, height: 56, borderRadius: 28, alignItems: 'center', justifyContent: 'center', }, socialIdText: { fontSize: 12, fontWeight: '700', color: colors.textInverse, textAlign: 'center', lineHeight: 15 }, });