轻松聘
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

212 lines
7.9 KiB

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 } 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';
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 agreed = data.agreed;
const phone = data.phone;
const phoneValid = PHONE_RE.test(phone);
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);
};
const handleLogin = () => {
if (code.length < 4) return;
setData({ phone, code });
navigate('P3');
};
/** 掩码手机号:138****1234 */
const maskedPhone = phone.length === 11 ? `${phone.slice(0, 3)}****${phone.slice(7)}` : phone;
return (
<ScreenShell showBack onBack={back}>
{!codeSent ? (
/* ---------- 阶段1:手机号 ---------- */
<View>
<Text style={styles.h1}>登录/注册</Text>
<Text style={styles.subtitle}>首次验证通过即注册有职账号</Text>
<View style={styles.phoneRow}>
<Text style={styles.prefix}>+86</Text>
<Ionicons name="caret-down" size={14} color={colors.textSecondary} />
<View style={styles.divider} />
<TextInput
style={styles.phoneInput}
value={phone}
onChangeText={(t) => setData({ phone: t.replace(/[^\d]/g, '').slice(0, 11) })}
placeholder="请输入手机号"
placeholderTextColor={colors.textHint}
keyboardType="phone-pad"
maxLength={11}
/>
{phone.length > 0 ? (
<Pressable onPress={() => setData({ phone: '' })} hitSlop={8}>
<Ionicons name="close-circle" size={18} color={colors.textDisabled} />
</Pressable>
) : null}
</View>
<View style={styles.underline} />
<Text style={styles.smsHelp}>接收不到短信</Text>
<View style={styles.btnArea}>
<PrimaryButton label="下一步" active={phoneValid} onPress={() => gate(sendCode)} />
</View>
<View style={styles.agree}>
<AgreementCheckbox
checked={agreed}
onToggle={() => setData({ agreed: !agreed })}
text={
<Text style={styles.agreeText}>
已阅读并同意
<Text style={styles.link} onPress={() => navigate('B2')}>《有职用户协议》</Text>
和
<Text style={styles.link} onPress={() => navigate('B2')}>《隐私政策》</Text>
,允许有职统一管理本人账号信息
</Text>
}
/>
</View>
{/* 分隔线 + 其他登录方式 */}
<View style={styles.orRow}>
<View style={styles.orLine} />
<Text style={styles.orText}>或通过以下方式登录</Text>
<View style={styles.orLine} />
</View>
<View style={styles.socialRow}>
<Pressable style={[styles.socialCircle, { backgroundColor: colors.success }]} onPress={() => gate(() => navigate('B1'))}>
<Ionicons name="logo-wechat" size={26} color={colors.textInverse} />
</Pressable>
<Pressable style={[styles.socialCircle, { backgroundColor: colors.primary }]} onPress={() => gate(sendCode)}>
<Ionicons name="chatbubble-ellipses" size={24} color={colors.textInverse} />
</Pressable>
<Pressable style={[styles.socialCircle, { backgroundColor: colors.dangerBtn }]} onPress={() => gate(sendCode)}>
<Text style={styles.socialIdText}>{'数字\n身份'}</Text>
</Pressable>
</View>
</View>
) : (
/* ---------- 阶段2:验证码 ---------- */
<View>
<Text style={styles.h1}>输入验证码</Text>
<Text style={styles.subtitle}>验证码已发送至 {maskedPhone}</Text>
<View style={styles.phoneRow}>
<TextInput
style={styles.phoneInput}
value={code}
onChangeText={(t) => setCode(t.replace(/[^\d]/g, '').slice(0, 6))}
placeholder="请输入验证码"
placeholderTextColor={colors.textHint}
keyboardType="number-pad"
maxLength={6}
/>
<CountdownButton disabled={!phoneValid} onSend={() => setCodeSent(true)} />
</View>
<View style={styles.underline} />
<View style={styles.btnArea}>
<PrimaryButton label="登录" active={code.length >= 4} onPress={() => gate(handleLogin)} />
</View>
<View style={styles.agree}>
<AgreementCheckbox
checked={agreed}
onToggle={() => setData({ agreed: !agreed })}
text={
<Text style={styles.agreeText}>
已阅读并同意
<Text style={styles.link} onPress={() => navigate('B2')}>《有职用户协议》</Text>
和
<Text style={styles.link} onPress={() => navigate('B2')}>《隐私政策》</Text>
,允许有职统一管理本人账号信息
</Text>
}
/>
</View>
</View>
)}
<AgreementModal
visible={pending !== null}
onAccept={accept}
onDecline={() => setPending(null)}
onOpenAgreement={() => {
setPending(null);
navigate('B2');
}}
/>
</ScreenShell>
);
}
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 },
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 },
});