Files
frontend/src/screens/auth/RegisterScreen.tsx
lafay a005fb0a15 feat: add QR code login and enhance chat experience
- Add QR code login flow with scanner and confirmation screens
- Implement swipe-to-reply in chat with SwipeableMessageBubble
- Replace FlatList with FlashList for chat performance optimization
- Add Schedule stack navigator with CourseDetail screen support
- Configure deep linking for QR code login (carrotbbs://qrcode/login/:sessionId)
- Update branding from 胡萝卜 to 萝卜社区
- Display dynamic app version in settings
2026-03-20 19:28:42 +08:00

1078 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 注册页 RegisterScreen响应式适配 - 分栏布局)
* 胡萝卜BBS - 用户注册
*
* 布局规则:
* - 屏幕宽度 < 768px单栏布局橙色渐变背景
* - 屏幕宽度 >= 768px分栏布局左侧橙色右侧中性灰
*/
import React, { useState, useEffect, useRef } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
TextInput,
KeyboardAvoidingView,
Platform,
ScrollView,
ActivityIndicator,
Animated,
StatusBar,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { colors, spacing, borderRadius, shadows, fontSizes } from '../../theme';
import { authService, resolveAuthApiError } from '../../services/authService';
import { useAuthStore } from '../../stores';
import { RootStackParamList } from '../../navigation/types';
import { useResponsive, useResponsiveValue } from '../../hooks';
import { showPrompt } from '../../services/promptService';
type RegisterNavigationProp = NativeStackNavigationProp<RootStackParamList, 'Auth'>;
// 分栏布局断点
const SPLIT_BREAKPOINT = 768;
export const RegisterScreen: React.FC = () => {
const navigation = useNavigation<RegisterNavigationProp>();
const register = useAuthStore((state) => state.register);
// 响应式布局
const { isLandscape, width } = useResponsive();
// 是否使用分栏布局
const isSplitLayout = width >= SPLIT_BREAKPOINT;
const [username, setUsername] = useState('');
const [nickname, setNickname] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [verificationCode, setVerificationCode] = useState('');
const [loading, setLoading] = useState(false);
const [sendingCode, setSendingCode] = useState(false);
const [countdown, setCountdown] = useState(0);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [agreedToTerms, setAgreedToTerms] = useState(true);
// 动画值
const fadeAnim = useRef(new Animated.Value(0)).current;
const slideAnim = useRef(new Animated.Value(50)).current;
const scaleAnim = useRef(new Animated.Value(0.95)).current;
const inputFadeAnim = useRef(new Animated.Value(0)).current;
// 响应式值 - 大屏模式单独放大图标尺寸,更显大气
const iconSize = useResponsiveValue({
xs: 48, sm: 52, md: 56,
lg: isSplitLayout ? 120 : 64, // 大屏模式图标进一步放大
xl: isSplitLayout ? 140 : 72
});
const iconContainerSize = useResponsiveValue({
xs: 80, sm: 88, md: 96,
lg: isSplitLayout ? 180 : 108, // 大屏模式图标容器进一步放大
xl: isSplitLayout ? 200 : 120
});
const titleSize = useResponsiveValue({
xs: 24, sm: 26, md: 28,
lg: isSplitLayout ? 60 : 30, // 大屏模式标题文字大幅放大
xl: isSplitLayout ? 72 : 32
});
const formMaxWidth = isSplitLayout ? 480 : undefined;
// 启动入场动画
useEffect(() => {
Animated.sequence([
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
]),
Animated.timing(slideAnim, {
toValue: 0,
duration: 400,
useNativeDriver: true,
}),
Animated.timing(inputFadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}),
]).start();
}, []);
useEffect(() => {
if (countdown <= 0) {
return;
}
const timer = setInterval(() => {
setCountdown((prev) => (prev <= 1 ? 0 : prev - 1));
}, 1000);
return () => clearInterval(timer);
}, [countdown]);
// 表单验证
const validateForm = (): boolean => {
if (!username.trim()) {
showPrompt({ title: '提示', message: '请输入用户名', type: 'warning' });
return false;
}
if (username.length < 3 || username.length > 20) {
showPrompt({ title: '提示', message: '用户名长度需在3-20个字符之间', type: 'warning' });
return false;
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
showPrompt({ title: '提示', message: '用户名只能包含字母、数字和下划线', type: 'warning' });
return false;
}
if (!nickname.trim()) {
showPrompt({ title: '提示', message: '请输入昵称', type: 'warning' });
return false;
}
if (nickname.length < 2 || nickname.length > 20) {
showPrompt({ title: '提示', message: '昵称长度需在2-20个字符之间', type: 'warning' });
return false;
}
if (!email.trim()) {
showPrompt({ title: '提示', message: '请输入邮箱', type: 'warning' });
return false;
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
showPrompt({ title: '提示', message: '请输入正确的邮箱地址', type: 'warning' });
return false;
}
if (!verificationCode.trim()) {
showPrompt({ title: '提示', message: '请输入邮箱验证码', type: 'warning' });
return false;
}
// 手机号验证(如果填写了)
if (phone && !/^1[3-9]\d{9}$/.test(phone)) {
showPrompt({ title: '提示', message: '请输入正确的手机号', type: 'warning' });
return false;
}
if (!password) {
showPrompt({ title: '提示', message: '请输入密码', type: 'warning' });
return false;
}
if (password.length < 6) {
showPrompt({ title: '提示', message: '密码长度不能少于6位', type: 'warning' });
return false;
}
if (password !== confirmPassword) {
showPrompt({ title: '提示', message: '两次输入的密码不一致', type: 'warning' });
return false;
}
if (!agreedToTerms) {
showPrompt({ title: '提示', message: '请同意用户协议和隐私政策', type: 'warning' });
return false;
}
return true;
};
const handleSendCode = async () => {
if (!email.trim()) {
showPrompt({ title: '提示', message: '请先输入邮箱', type: 'warning' });
return;
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
showPrompt({ title: '提示', message: '请输入正确的邮箱地址', type: 'warning' });
return;
}
if (countdown > 0) {
return;
}
setSendingCode(true);
try {
const ok = await authService.sendRegisterCode(email.trim());
if (ok) {
setCountdown(60);
showPrompt({ title: '发送成功', message: '验证码已发送,请查收邮箱', type: 'success' });
}
} catch (error: any) {
showPrompt({ title: '发送失败', message: resolveAuthApiError(error, '验证码发送失败,请稍后重试'), type: 'error' });
} finally {
setSendingCode(false);
}
};
// 处理注册
const handleRegister = async () => {
if (!validateForm()) return;
setLoading(true);
try {
const success = await register({
username,
password,
nickname,
email: email.trim(),
verification_code: verificationCode.trim(),
phone: phone || undefined,
});
if (success) {
// 注册成功,导航会自动切换到主页
} else {
const msg = useAuthStore.getState().error || '注册失败,请稍后重试';
showPrompt({ title: '注册失败', message: msg, type: 'error' });
}
} catch (error: any) {
showPrompt({ title: '注册失败', message: resolveAuthApiError(error, '请稍后重试'), type: 'error' });
} finally {
setLoading(false);
}
};
// 跳转到登录页
const handleGoToLogin = () => {
navigation.navigate('Login' as any);
};
// 渲染左侧面板Logo和品牌信息- 优化大屏布局
const renderLeftPanel = () => (
<Animated.View
style={[
styles.leftPanel,
isSplitLayout && styles.splitLeftPanel, // 大屏模式专用布局
{
opacity: fadeAnim,
transform: [{ scale: scaleAnim }],
},
]}
>
{/* 用户图标 - 靠上显示 */}
<View style={[
styles.iconContainer,
styles.iconContainerTop,
isSplitLayout && styles.splitIconContainer, // 大屏模式图标容器样式
{ width: iconContainerSize, height: iconContainerSize, borderRadius: iconContainerSize / 2 }
]}>
<MaterialCommunityIcons
name="account-plus"
size={iconSize}
color="#FFF"
/>
</View>
{/* 品牌名称 - 优化大屏排版 */}
<Text style={[styles.title, isSplitLayout && styles.splitTitle]}></Text>
<Text style={[styles.subtitle, isSplitLayout && styles.splitSubtitle]}></Text>
{/* 装饰元素 - 大屏模式改为水平排列 + 大幅放大 */}
<View style={[styles.leftPanelDecorBottom, isSplitLayout && styles.splitLeftPanelDecorBottom]}>
{isSplitLayout ? (
// 大屏模式:水平排列 + 大幅放大图标
<View style={styles.splitDecorRow}>
<MaterialCommunityIcons name="account-plus-outline" size={80} color="rgba(255,255,255,0.4)" style={styles.splitDecorIcon} />
<MaterialCommunityIcons name="email-check-outline" size={90} color="rgba(255,255,255,0.45)" style={styles.splitDecorIcon} />
<MaterialCommunityIcons name="shield-check-outline" size={85} color="rgba(255,255,255,0.35)" style={styles.splitDecorIcon} />
</View>
) : (
// 小屏模式:保持原有横向排列
<View style={styles.decorRow}>
<MaterialCommunityIcons name="account-plus-outline" size={48} color="rgba(255,255,255,0.35)" />
<MaterialCommunityIcons name="email-check-outline" size={44} color="rgba(255,255,255,0.3)" />
<MaterialCommunityIcons name="shield-check-outline" size={40} color="rgba(255,255,255,0.25)" />
</View>
)}
</View>
</Animated.View>
);
// 渲染表单内容
const renderFormContent = () => (
<View style={isSplitLayout ? styles.splitFormWrapper : styles.singleFormWrapper}>
<Animated.View
style={[
styles.formCard,
// 核心修改:大屏模式使用专用的阴影样式,小屏用原有阴影
isSplitLayout ? styles.splitFormCardShadow : styles.normalFormCardShadow,
formMaxWidth && { maxWidth: formMaxWidth, width: '100%' },
{
opacity: inputFadeAnim,
transform: [{ translateY: slideAnim }],
},
]}
>
<Text style={styles.formTitle}></Text>
{/* 用户名输入框 */}
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="account-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isSplitLayout && styles.inputWide,
]}
placeholder="用户名3-20个字符"
placeholderTextColor={colors.text.hint}
value={username}
onChangeText={setUsername}
autoCapitalize="none"
autoCorrect={false}
maxLength={20}
/>
{username.length > 0 && (
<TouchableOpacity
onPress={() => setUsername('')}
style={styles.clearButton}
>
<MaterialCommunityIcons
name="close-circle"
size={20}
color={colors.text.hint}
/>
</TouchableOpacity>
)}
</View>
</View>
{/* 昵称输入框 */}
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="emoticon-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isSplitLayout && styles.inputWide,
]}
placeholder="昵称2-20个字符"
placeholderTextColor={colors.text.hint}
value={nickname}
onChangeText={setNickname}
maxLength={20}
/>
{nickname.length > 0 && (
<TouchableOpacity
onPress={() => setNickname('')}
style={styles.clearButton}
>
<MaterialCommunityIcons
name="close-circle"
size={20}
color={colors.text.hint}
/>
</TouchableOpacity>
)}
</View>
</View>
{/* 邮箱输入框 */}
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="email-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isSplitLayout && styles.inputWide,
]}
placeholder="邮箱(必填)"
placeholderTextColor={colors.text.hint}
value={email}
onChangeText={setEmail}
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
/>
{email.length > 0 && (
<TouchableOpacity
onPress={() => setEmail('')}
style={styles.clearButton}
>
<MaterialCommunityIcons
name="close-circle"
size={20}
color={colors.text.hint}
/>
</TouchableOpacity>
)}
</View>
</View>
{/* 邮箱验证码 */}
<View style={styles.inputContainer}>
<View style={styles.codeRow}>
<View style={[styles.inputWrapper, styles.codeInputWrapper, isSplitLayout && styles.inputWrapperWide]}>
<MaterialCommunityIcons
name="shield-key-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[styles.input, isSplitLayout && styles.inputWide]}
placeholder="邮箱验证码"
placeholderTextColor={colors.text.hint}
value={verificationCode}
onChangeText={setVerificationCode}
keyboardType="number-pad"
maxLength={6}
/>
</View>
<TouchableOpacity
style={[
styles.sendCodeButton,
(sendingCode || countdown > 0) && styles.sendCodeButtonDisabled,
]}
onPress={handleSendCode}
disabled={sendingCode || countdown > 0}
activeOpacity={0.8}
>
<Text style={styles.sendCodeButtonText}>
{sendingCode ? '发送中...' : countdown > 0 ? `${countdown}s` : '发送验证码'}
</Text>
</TouchableOpacity>
</View>
</View>
{/* 手机号输入框 */}
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="phone-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isSplitLayout && styles.inputWide,
]}
placeholder="手机号(选填)"
placeholderTextColor={colors.text.hint}
value={phone}
onChangeText={setPhone}
keyboardType="phone-pad"
maxLength={11}
/>
{phone.length > 0 && (
<TouchableOpacity
onPress={() => setPhone('')}
style={styles.clearButton}
>
<MaterialCommunityIcons
name="close-circle"
size={20}
color={colors.text.hint}
/>
</TouchableOpacity>
)}
</View>
</View>
{/* 密码输入框 */}
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="lock-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isSplitLayout && styles.inputWide,
]}
placeholder="密码至少6位"
placeholderTextColor={colors.text.hint}
value={password}
onChangeText={setPassword}
secureTextEntry={!showPassword}
autoCapitalize="none"
/>
<TouchableOpacity
onPress={() => setShowPassword(!showPassword)}
style={styles.clearButton}
>
<MaterialCommunityIcons
name={showPassword ? 'eye-off-outline' : 'eye-outline'}
size={20}
color={colors.text.hint}
/>
</TouchableOpacity>
</View>
</View>
{/* 确认密码输入框 */}
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="lock-check-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isSplitLayout && styles.inputWide,
]}
placeholder="确认密码"
placeholderTextColor={colors.text.hint}
value={confirmPassword}
onChangeText={setConfirmPassword}
secureTextEntry={!showConfirmPassword}
autoCapitalize="none"
returnKeyType="done"
onSubmitEditing={handleRegister}
/>
<TouchableOpacity
onPress={() => setShowConfirmPassword(!showConfirmPassword)}
style={styles.clearButton}
>
<MaterialCommunityIcons
name={showConfirmPassword ? 'eye-off-outline' : 'eye-outline'}
size={20}
color={colors.text.hint}
/>
</TouchableOpacity>
</View>
</View>
{/* 服务条款 */}
<TouchableOpacity
style={styles.termsContainer}
onPress={() => setAgreedToTerms(!agreedToTerms)}
activeOpacity={0.8}
>
<View style={[styles.checkbox, agreedToTerms && styles.checkboxChecked]}>
<MaterialCommunityIcons
name={agreedToTerms ? "checkbox-marked" : "checkbox-blank-outline"}
size={24}
color={agreedToTerms ? colors.primary.main : colors.text.hint}
/>
</View>
<Text style={styles.termsText}>
<Text style={styles.termsLink}></Text>
<Text style={styles.termsLink}></Text>
</Text>
</TouchableOpacity>
{/* 注册按钮 */}
<TouchableOpacity
style={[styles.registerButton, loading && styles.registerButtonDisabled]}
onPress={handleRegister}
disabled={loading}
activeOpacity={0.8}
>
<LinearGradient
colors={[colors.primary.main, colors.primary.light]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={[
styles.registerButtonGradient,
isSplitLayout && styles.registerButtonGradientWide,
]}
>
{loading ? (
<ActivityIndicator size="small" color="#FFF" />
) : (
<Text style={styles.registerButtonText}> </Text>
)}
</LinearGradient>
</TouchableOpacity>
</Animated.View>
{/* 底部登录提示 */}
<Animated.View
style={[
styles.footerSection,
isSplitLayout && styles.splitFooterSection,
{ opacity: inputFadeAnim },
]}
>
<Text style={[styles.footerText, isSplitLayout && styles.splitFooterText]}></Text>
<TouchableOpacity onPress={handleGoToLogin}>
<Text style={[styles.loginLink, isSplitLayout && styles.splitLoginLink]}></Text>
</TouchableOpacity>
</Animated.View>
</View>
);
// 渲染分栏布局
const renderSplitLayout = () => (
<View style={styles.splitContainer}>
{/* 左侧橙色面板 */}
<LinearGradient
colors={[colors.primary.main, colors.primary.light, '#FFB088']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.leftPanelContainer}
>
{/* 装饰性背景元素 */}
<View style={styles.decorCircle1} />
<View style={styles.decorCircle2} />
<StatusBar barStyle="light-content" />
<SafeAreaView style={styles.leftPanelSafeArea}>
{renderLeftPanel()}
</SafeAreaView>
</LinearGradient>
{/* 右侧中性灰面板 */}
<View style={styles.rightPanelContainer}>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.rightPanelSafeArea}>
{/* 核心修复用View包裹确保垂直+水平居中 */}
<View style={styles.splitRightContentWrapper}>
{renderFormContent()}
</View>
</SafeAreaView>
</View>
</View>
);
// 渲染单栏布局
const renderSingleLayout = () => (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="light-content" />
<LinearGradient
colors={[colors.primary.main, colors.primary.light, '#FFB088']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.gradient}
>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.keyboardView}
>
<ScrollView
contentContainerStyle={[
styles.scrollContent,
isLandscape && styles.scrollContentLandscape,
]}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{/* 装饰性背景元素 */}
<View style={styles.decorCircle1} />
<View style={styles.decorCircle2} />
{/* 头部 */}
<Animated.View
style={[
styles.headerSection,
isLandscape && styles.headerSectionLandscape,
{
opacity: fadeAnim,
transform: [{ scale: scaleAnim }],
},
]}
>
<View style={[
styles.iconContainer,
{ width: iconContainerSize, height: iconContainerSize, borderRadius: iconContainerSize / 2 }
]}>
<MaterialCommunityIcons
name="account-plus"
size={iconSize}
color="#FFF"
/>
</View>
<Text style={[styles.title, { fontSize: titleSize }]}></Text>
<Text style={styles.subtitle}></Text>
</Animated.View>
{/* 表单 */}
{renderFormContent()}
</ScrollView>
</KeyboardAvoidingView>
</LinearGradient>
</SafeAreaView>
);
return isSplitLayout ? renderSplitLayout() : renderSingleLayout();
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
gradient: {
flex: 1,
},
keyboardView: {
flex: 1,
},
scrollContent: {
flexGrow: 1,
paddingHorizontal: spacing.xl,
paddingVertical: spacing.xl,
},
scrollContentLandscape: {
paddingVertical: spacing.md,
},
// 装饰性背景元素
decorCircle1: {
position: 'absolute',
top: -80,
right: -80,
width: 250,
height: 250,
borderRadius: 125,
backgroundColor: 'rgba(255,255,255,0.1)',
},
decorCircle2: {
position: 'absolute',
bottom: 150,
left: -120,
width: 200,
height: 200,
borderRadius: 100,
backgroundColor: 'rgba(255,255,255,0.08)',
},
// 头部区域
headerSection: {
alignItems: 'center',
marginTop: spacing.xl,
marginBottom: spacing.xl,
},
headerSectionLandscape: {
marginTop: spacing.md,
marginBottom: spacing.md,
},
iconContainer: {
backgroundColor: 'rgba(255,255,255,0.25)',
alignItems: 'center',
justifyContent: 'center',
marginBottom: spacing.md,
borderWidth: 2,
borderColor: 'rgba(255,255,255,0.4)',
...shadows.md,
},
title: {
fontWeight: '800',
color: '#FFFFFF',
marginBottom: spacing.xs,
textShadowColor: 'rgba(0,0,0,0.1)',
textShadowOffset: { width: 0, height: 2 },
textShadowRadius: 4,
},
subtitle: {
fontSize: fontSizes.md,
color: 'rgba(255,255,255,0.9)',
},
// 表单卡片
formCard: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius['2xl'],
padding: spacing.xl,
marginBottom: spacing.lg,
...shadows.md,
},
inputContainer: {
marginBottom: spacing.md,
},
codeRow: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
},
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.background.default,
borderRadius: borderRadius.lg,
borderWidth: 1.5,
borderColor: colors.divider,
paddingHorizontal: spacing.md,
height: 52,
},
inputWrapperWide: {
height: 56,
},
codeInputWrapper: {
flex: 1,
},
inputIcon: {
marginRight: spacing.sm,
},
input: {
flex: 1,
fontSize: fontSizes.md,
color: colors.text.primary,
},
inputWide: {
fontSize: fontSizes.lg,
},
clearButton: {
padding: spacing.xs,
},
sendCodeButton: {
height: 52,
minWidth: 110,
paddingHorizontal: spacing.md,
borderRadius: borderRadius.lg,
backgroundColor: colors.primary.main,
alignItems: 'center',
justifyContent: 'center',
},
sendCodeButtonDisabled: {
opacity: 0.6,
},
sendCodeButtonText: {
color: '#fff',
fontSize: fontSizes.sm,
fontWeight: '600',
},
// 服务条款
termsContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: spacing.lg,
marginTop: spacing.sm,
},
checkbox: {
marginRight: spacing.sm,
},
checkboxChecked: {
// 选中状态的额外样式
},
termsText: {
fontSize: fontSizes.sm,
color: colors.text.secondary,
flex: 1,
lineHeight: 20,
},
termsLink: {
color: colors.primary.main,
fontWeight: '500',
},
// 注册按钮
registerButton: {
borderRadius: borderRadius.lg,
overflow: 'hidden',
...shadows.md,
},
registerButtonGradient: {
height: 50,
alignItems: 'center',
justifyContent: 'center',
},
registerButtonGradientWide: {
height: 54,
},
registerButtonDisabled: {
opacity: 0.7,
},
registerButtonText: {
fontSize: fontSizes.lg,
fontWeight: '700',
color: '#FFFFFF',
},
// 底部区域
footerSection: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginTop: 'auto',
paddingTop: spacing.md,
paddingBottom: spacing.md,
},
splitFooterSection: {
paddingTop: spacing.xl,
},
footerText: {
fontSize: fontSizes.md,
color: 'rgba(255,255,255,0.9)',
},
splitFooterText: {
color: colors.text.primary,
},
loginLink: {
fontSize: fontSizes.md,
color: '#FFFFFF',
fontWeight: '700',
marginLeft: spacing.xs,
textDecorationLine: 'underline',
},
splitLoginLink: {
color: colors.primary.main,
},
// ========== 分栏布局样式 ==========
splitContainer: {
flex: 1,
flexDirection: 'row',
},
leftPanelContainer: {
flex: 1, // 50%
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
},
leftPanelSafeArea: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: spacing['4xl'], // 增加内边距,不贴边
},
leftPanel: {
alignItems: 'center',
paddingHorizontal: spacing['2xl'],
},
// 大屏模式左侧面板布局
splitLeftPanel: {
width: '100%',
justifyContent: 'center',
paddingVertical: 120,
},
// 大屏模式图标容器样式 - 增加阴影和内边距,更立体
splitIconContainer: {
borderWidth: 3,
borderColor: 'rgba(255,255,255,0.5)',
...shadows.lg,
},
iconContainerTop: {
marginTop: spacing['3xl'],
},
// 大屏模式标题样式 - 大幅放大,增加间距和文字效果
splitTitle: {
fontSize: 72, // 大幅放大主标题
marginBottom: 48,
textShadowOffset: { width: 0, height: 4 },
textShadowRadius: 8,
letterSpacing: 4, // 增加字间距,更显大气
fontWeight: '900',
},
// 大屏模式副标题样式 - 大幅放大
splitSubtitle: {
fontSize: 24, // 放大副标题
marginBottom: 120, // 增加底部间距
lineHeight: 36,
paddingHorizontal: spacing['4xl'],
letterSpacing: 1.5,
fontWeight: '500',
},
leftPanelDecorBottom: {
marginTop: spacing['2xl'],
},
// 大屏模式装饰元素布局
splitLeftPanelDecorBottom: {
marginTop: 120,
width: '100%',
alignItems: 'center',
},
// 大屏模式装饰元素水平排列
splitDecorRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
width: '100%',
gap: spacing['2xl'], // 增加图标间距
},
// 大屏模式装饰图标样式
splitDecorIcon: {
transform: [{ scale: 1.1 }],
opacity: 0.9,
},
decorRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
width: '80%',
},
rightPanelContainer: {
flex: 1, // 50%
backgroundColor: colors.background.paper,
},
rightPanelSafeArea: {
flex: 1,
// 移除左右内边距,交给子容器控制
},
// 分栏布局右侧内容包裹器(核心修复)
splitRightContentWrapper: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
// 新增:左右内边距,保证表单和边缘有间距
paddingHorizontal: spacing['2xl'],
},
// 表单内容包裹器(区分大屏/小屏)
splitFormWrapper: {
// 关键修改:设置最大宽度 + 自适应宽度,实现水平居中
maxWidth: 480,
width: '100%',
},
singleFormWrapper: {
flexGrow: 1,
width: '100%',
},
// 小屏模式:原有默认阴影
normalFormCardShadow: {
...shadows.md,
},
// 大屏模式:专用全向阴影
splitFormCardShadow: {
// iOS 全向柔和阴影
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.12,
shadowRadius: 12,
// Android 高程 + 轻微边框(模拟顶部阴影)
elevation: 8,
// 新增:极细微的顶部边框,强化区分度
borderTopWidth: 1,
borderTopColor: 'rgba(0,0,0,0.05)',
},
formTitle: {
fontSize: fontSizes['2xl'],
fontWeight: '700',
color: colors.text.primary,
marginBottom: spacing.xl,
textAlign: 'center',
},
});
export default RegisterScreen;