轻松聘
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.
 
 
 

220 lines
7.4 KiB

import { useEffect, useState } from 'react';
import { Ionicons } from '@expo/vector-icons';
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { useRouter } from 'expo-router';
import { LinearGradient } from 'expo-linear-gradient';
import { SafeAreaView } from 'react-native-safe-area-context';
import { colors } from '../../../src/theme';
import { useOnboarding, useWechatLogin } from '../../../src/hooks';
import { useRole } from '../../../src/context/RoleContext';
import { Illustration } from '../ui/Illustration';
import { AgreementCheckbox } from '../ui/AgreementCheckbox';
import { AgreementModal } from '../ui/AgreementModal';
import { SliderCaptcha } from '../ui/SliderCaptcha';
import { DeletedAccountModal } from '../ui/DeletedAccountModal';
/** P1 登录主页:对齐参考样式 —— 淡渐变顶部 + 居中 Logo + 微信绿实心/灰底一键登录 + 协议弹窗拦截 */
export function P1Login() {
const { data, setData, navigate } = useOnboarding();
const router = useRouter();
const { setRole } = useRole();
const agreed = data.agreed;
/** 未勾选协议时被拦截的动作,弹窗「同意」后继续执行 */
const [pending, setPending] = useState<(() => void) | null>(null);
const [captchaVisible, setCaptchaVisible] = useState(false);
/** 微信登录状态机:负责三分支判定与流转 */
const {
state: wechatState,
start: startWechatLogin,
cancelLogin,
reset: resetWechat,
loading: wechatLoading,
} = useWechatLogin();
/** 协议门卫:已勾选直接执行;未勾选弹协议弹窗 */
const gate = (action: () => void) => {
if (agreed) {
action();
} else {
setPending(() => action);
}
};
const accept = () => {
setData({ agreed: true });
const act = pending;
setPending(null);
act?.();
};
/** 微信登录:先滑块拼图验证,通过后进入微信授权与三分支判定 */
const wechatLogin = () => setCaptchaVisible(true);
/*
* 状态机的副作用集中在这里,页面只负责触发与渲染:
* - needBindPhone:第一次登录,把临时凭证存入流程数据后跳 P2(手机号 + 验证码)
* - success:第二次及以后登录,凭后端返回的身份直接落地,无身份则回注册流程选择
* - alreadyLoggedIn:本地 token 未过期,免授权直接落地
* 跳转后立即 reset,避免返回本页时重复触发。
*/
useEffect(() => {
if (wechatState.kind === 'needBindPhone') {
setData({
wechatTicket: {
token: wechatState.ticket.token,
openid: wechatState.ticket.openid,
},
});
navigate('P2');
resetWechat();
return;
}
if (wechatState.kind === 'success') {
if (wechatState.role) {
setRole(wechatState.role);
router.replace(
wechatState.role === 'enterprise' ? '/(enterprise)/job-management' : '/(tabs)/jobs',
);
} else {
router.replace('/register');
}
resetWechat();
return;
}
if (wechatState.kind === 'alreadyLoggedIn') {
router.replace('/(tabs)/jobs');
resetWechat();
}
}, [wechatState, resetWechat, router, setRole, setData, navigate]);
return (
<SafeAreaView style={styles.screen} edges={['top', 'bottom']}>
<StatusBar style="dark" />
<LinearGradient colors={[colors.primaryLight, colors.pageBg]} style={styles.topGlow} />
<ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false} bounces={false}>
<View style={styles.brandArea}>
<Illustration name="logo" size={96} radius={24} />
</View>
<View style={styles.flexSpacer} />
{/* 登录方式 */}
<Pressable
style={styles.wechatBtn}
onPress={() => gate(wechatLogin)}
disabled={wechatLoading}
>
<Ionicons name="logo-wechat" size={20} color={colors.textInverse} />
<Text style={styles.wechatText}>
{wechatLoading ? '登录中…' : '微信登录'}
</Text>
</Pressable>
{wechatState.kind === 'error' ? (
<Text style={styles.loginError}>{wechatState.message}</Text>
) : null}
<Pressable style={styles.quickBtn} onPress={() => gate(() => navigate('P2Q'))}>
<Text style={styles.quickText}>一键登录</Text>
</Pressable>
<Pressable onPress={() => gate(() => navigate('P2'))} hitSlop={8}>
<Text style={styles.otherLink}>其他方式登录</Text>
</Pressable>
{/* 协议勾选 */}
<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.flexSpacer} />
<View style={styles.footer}>
<Text style={styles.footItem}>服务热线</Text>
<Text style={styles.footItem}>举报监督电话</Text>
<Text style={styles.footItem}>资质证照</Text>
</View>
</ScrollView>
<SliderCaptcha
visible={captchaVisible}
onSuccess={() => {
setCaptchaVisible(false);
void startWechatLogin();
}}
onClose={() => setCaptchaVisible(false)}
/>
<DeletedAccountModal visible={wechatState.kind === 'deleted'} onClose={cancelLogin} />
<AgreementModal
visible={pending !== null}
onAccept={accept}
onDecline={() => setPending(null)}
onOpenAgreement={() => {
setPending(null);
navigate('B2');
}}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, backgroundColor: colors.pageBg },
topGlow: { position: 'absolute', top: 0, left: 0, right: 0, height: 200 },
content: { flexGrow: 1, paddingHorizontal: 24, paddingBottom: 12 },
brandArea: { alignItems: 'center', marginTop: '22%' },
flexSpacer: { flex: 1, minHeight: 28 },
wechatBtn: {
height: 50,
borderRadius: 12,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
backgroundColor: colors.success,
},
wechatText: { fontSize: 16, fontWeight: '700', color: colors.textInverse },
quickBtn: {
height: 50,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
marginTop: 14,
backgroundColor: colors.borderLight,
},
quickText: { fontSize: 16, fontWeight: '700', color: colors.textPrimary },
otherLink: {
textAlign: 'center',
marginTop: 20,
fontSize: 14,
color: colors.textSecondary,
},
loginError: { marginTop: 10, textAlign: 'center', fontSize: 12, color: colors.danger },
agree: { marginTop: 22 },
agreeText: { fontSize: 12, color: colors.textSecondary, lineHeight: 18 },
link: { color: colors.primary, fontWeight: '600' },
footer: {
flexDirection: 'row',
justifyContent: 'center',
gap: 18,
paddingTop: 24,
paddingBottom: 8,
},
footItem: { fontSize: 12, color: colors.textHint },
});