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
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 },
|
|
});
|
|
|