2026-03-09 21:29:03 +08:00
|
|
|
|
/**
|
2026-04-01 00:50:38 +08:00
|
|
|
|
* 注册页 RegisterScreen(简洁表单样式)
|
2026-03-09 21:29:03 +08:00
|
|
|
|
* 胡萝卜BBS - 用户注册
|
2026-04-01 00:50:38 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 设计风格:
|
|
|
|
|
|
* - 纯白背景,扁平化设计
|
|
|
|
|
|
* - 增大间距,避免页面太空
|
|
|
|
|
|
* - 输入框带灰色背景填充
|
|
|
|
|
|
* - 橙色圆角按钮
|
2026-03-09 21:29:03 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
2026-03-25 05:16:54 +08:00
|
|
|
|
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
import {
|
|
|
|
|
|
View,
|
|
|
|
|
|
Text,
|
|
|
|
|
|
StyleSheet,
|
|
|
|
|
|
TouchableOpacity,
|
|
|
|
|
|
TextInput,
|
|
|
|
|
|
KeyboardAvoidingView,
|
|
|
|
|
|
Platform,
|
|
|
|
|
|
ScrollView,
|
|
|
|
|
|
ActivityIndicator,
|
|
|
|
|
|
Animated,
|
2026-03-16 17:47:10 +08:00
|
|
|
|
StatusBar,
|
2026-03-09 21:29:03 +08:00
|
|
|
|
} from 'react-native';
|
|
|
|
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
2026-03-24 14:21:31 +08:00
|
|
|
|
import { useRouter } from 'expo-router';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
2026-04-01 00:50:38 +08:00
|
|
|
|
import { useAppColors, type AppColors } from '../../theme';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
import { authService, resolveAuthApiError } from '../../services/authService';
|
|
|
|
|
|
import { useAuthStore } from '../../stores';
|
2026-03-24 14:21:31 +08:00
|
|
|
|
import * as hrefs from '../../navigation/hrefs';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
import { showPrompt } from '../../services/promptService';
|
|
|
|
|
|
|
2026-04-01 00:50:38 +08:00
|
|
|
|
// 胡萝卜橙主题色
|
|
|
|
|
|
const THEME_COLORS = {
|
|
|
|
|
|
primary: '#FF6B35',
|
|
|
|
|
|
primaryLight: '#FF8C5A',
|
|
|
|
|
|
primaryDark: '#E55A2B',
|
|
|
|
|
|
};
|
2026-03-16 17:47:10 +08:00
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
export const RegisterScreen: React.FC = () => {
|
2026-03-25 05:16:54 +08:00
|
|
|
|
const colors = useAppColors();
|
|
|
|
|
|
const styles = useMemo(() => createRegisterStyles(colors), [colors]);
|
2026-03-24 14:21:31 +08:00
|
|
|
|
const router = useRouter();
|
2026-03-09 21:29:03 +08:00
|
|
|
|
const register = useAuthStore((state) => state.register);
|
2026-03-24 14:21:31 +08:00
|
|
|
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-03-24 14:21:31 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (isAuthenticated) {
|
|
|
|
|
|
router.replace(hrefs.hrefHome());
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [isAuthenticated, router]);
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
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;
|
2026-04-01 00:50:38 +08:00
|
|
|
|
const slideAnim = useRef(new Animated.Value(20)).current;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
// 启动入场动画
|
|
|
|
|
|
useEffect(() => {
|
2026-04-01 00:50:38 +08:00
|
|
|
|
Animated.parallel([
|
|
|
|
|
|
Animated.timing(fadeAnim, {
|
|
|
|
|
|
toValue: 1,
|
2026-03-09 21:29:03 +08:00
|
|
|
|
duration: 400,
|
|
|
|
|
|
useNativeDriver: true,
|
|
|
|
|
|
}),
|
2026-04-01 00:50:38 +08:00
|
|
|
|
Animated.timing(slideAnim, {
|
|
|
|
|
|
toValue: 0,
|
|
|
|
|
|
duration: 400,
|
2026-03-09 21:29:03 +08:00
|
|
|
|
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 = () => {
|
2026-03-24 14:21:31 +08:00
|
|
|
|
router.push(hrefs.hrefAuthLogin());
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-01 00:50:38 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<SafeAreaView style={styles.container}>
|
|
|
|
|
|
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
|
|
|
|
|
|
<KeyboardAvoidingView
|
|
|
|
|
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
|
|
|
|
style={styles.keyboardView}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
>
|
2026-04-01 00:50:38 +08:00
|
|
|
|
<ScrollView
|
|
|
|
|
|
contentContainerStyle={styles.scrollContent}
|
|
|
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
|
|
|
keyboardShouldPersistTaps="handled"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Animated.View
|
|
|
|
|
|
style={[
|
|
|
|
|
|
styles.content,
|
|
|
|
|
|
{
|
|
|
|
|
|
opacity: fadeAnim,
|
|
|
|
|
|
transform: [{ translateY: slideAnim }],
|
|
|
|
|
|
},
|
|
|
|
|
|
]}
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* 标题区域 - 增大间距 */}
|
|
|
|
|
|
<View style={styles.titleSection}>
|
|
|
|
|
|
<Text style={styles.title}>创建新账号</Text>
|
|
|
|
|
|
<View style={styles.underline} />
|
|
|
|
|
|
<Text style={styles.subtitle}>填写以下信息完成注册</Text>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 表单区域 */}
|
|
|
|
|
|
<View style={styles.formSection}>
|
|
|
|
|
|
{/* 用户名输入框 */}
|
|
|
|
|
|
<View style={styles.inputContainer}>
|
|
|
|
|
|
<Text style={styles.inputLabel}>用户名</Text>
|
|
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="3-20个字符,字母数字下划线"
|
|
|
|
|
|
placeholderTextColor={colors.text.hint}
|
|
|
|
|
|
value={username}
|
|
|
|
|
|
onChangeText={setUsername}
|
|
|
|
|
|
autoCapitalize="none"
|
|
|
|
|
|
autoCorrect={false}
|
|
|
|
|
|
maxLength={20}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{username.length > 0 && /^[a-zA-Z0-9_]{3,20}$/.test(username) && (
|
|
|
|
|
|
<MaterialCommunityIcons name="check-circle" size={20} color={THEME_COLORS.primary} style={styles.checkIcon} />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 昵称输入框 */}
|
|
|
|
|
|
<View style={styles.inputContainer}>
|
|
|
|
|
|
<Text style={styles.inputLabel}>昵称</Text>
|
|
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="2-20个字符"
|
|
|
|
|
|
placeholderTextColor={colors.text.hint}
|
|
|
|
|
|
value={nickname}
|
|
|
|
|
|
onChangeText={setNickname}
|
|
|
|
|
|
maxLength={20}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{nickname.length > 0 && nickname.length >= 2 && (
|
|
|
|
|
|
<MaterialCommunityIcons name="check-circle" size={20} color={THEME_COLORS.primary} style={styles.checkIcon} />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 邮箱输入框 */}
|
|
|
|
|
|
<View style={styles.inputContainer}>
|
|
|
|
|
|
<Text style={styles.inputLabel}>邮箱</Text>
|
|
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="请输入邮箱地址"
|
|
|
|
|
|
placeholderTextColor={colors.text.hint}
|
|
|
|
|
|
value={email}
|
|
|
|
|
|
onChangeText={setEmail}
|
|
|
|
|
|
autoCapitalize="none"
|
|
|
|
|
|
autoCorrect={false}
|
|
|
|
|
|
keyboardType="email-address"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{email.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) && (
|
|
|
|
|
|
<MaterialCommunityIcons name="check-circle" size={20} color={THEME_COLORS.primary} style={styles.checkIcon} />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 邮箱验证码 */}
|
|
|
|
|
|
<View style={styles.inputContainer}>
|
|
|
|
|
|
<Text style={styles.inputLabel}>验证码</Text>
|
|
|
|
|
|
<View style={styles.codeRow}>
|
|
|
|
|
|
<View style={[styles.inputWrapper, styles.codeInputWrapper]}>
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="6位验证码"
|
|
|
|
|
|
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}>
|
|
|
|
|
|
<Text style={styles.inputLabel}>手机号(选填)</Text>
|
|
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="请输入手机号"
|
|
|
|
|
|
placeholderTextColor={colors.text.hint}
|
|
|
|
|
|
value={phone}
|
|
|
|
|
|
onChangeText={setPhone}
|
|
|
|
|
|
keyboardType="phone-pad"
|
|
|
|
|
|
maxLength={11}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 密码输入框 */}
|
|
|
|
|
|
<View style={styles.inputContainer}>
|
|
|
|
|
|
<Text style={styles.inputLabel}>密码</Text>
|
|
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="至少6位"
|
|
|
|
|
|
placeholderTextColor={colors.text.hint}
|
|
|
|
|
|
value={password}
|
|
|
|
|
|
onChangeText={setPassword}
|
|
|
|
|
|
secureTextEntry={!showPassword}
|
|
|
|
|
|
autoCapitalize="none"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
onPress={() => setShowPassword(!showPassword)}
|
|
|
|
|
|
style={styles.eyeButton}
|
|
|
|
|
|
>
|
|
|
|
|
|
<MaterialCommunityIcons
|
|
|
|
|
|
name={showPassword ? 'eye-off-outline' : 'eye-outline'}
|
|
|
|
|
|
size={20}
|
|
|
|
|
|
color={colors.text.hint}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-04-01 00:50:38 +08:00
|
|
|
|
{/* 确认密码输入框 */}
|
|
|
|
|
|
<View style={styles.inputContainer}>
|
|
|
|
|
|
<Text style={styles.inputLabel}>确认密码</Text>
|
|
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="再次输入密码"
|
|
|
|
|
|
placeholderTextColor={colors.text.hint}
|
|
|
|
|
|
value={confirmPassword}
|
|
|
|
|
|
onChangeText={setConfirmPassword}
|
|
|
|
|
|
secureTextEntry={!showConfirmPassword}
|
|
|
|
|
|
autoCapitalize="none"
|
|
|
|
|
|
returnKeyType="done"
|
|
|
|
|
|
onSubmitEditing={handleRegister}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
onPress={() => setShowConfirmPassword(!showConfirmPassword)}
|
|
|
|
|
|
style={styles.eyeButton}
|
|
|
|
|
|
>
|
|
|
|
|
|
<MaterialCommunityIcons
|
|
|
|
|
|
name={showConfirmPassword ? 'eye-off-outline' : 'eye-outline'}
|
|
|
|
|
|
size={20}
|
|
|
|
|
|
color={colors.text.hint}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 服务条款 */}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
<TouchableOpacity
|
2026-04-01 00:50:38 +08:00
|
|
|
|
style={styles.termsContainer}
|
|
|
|
|
|
onPress={() => setAgreedToTerms(!agreedToTerms)}
|
|
|
|
|
|
activeOpacity={0.8}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
>
|
|
|
|
|
|
<MaterialCommunityIcons
|
2026-04-01 00:50:38 +08:00
|
|
|
|
name={agreedToTerms ? "checkbox-marked" : "checkbox-blank-outline"}
|
|
|
|
|
|
size={22}
|
|
|
|
|
|
color={agreedToTerms ? THEME_COLORS.primary : colors.text.hint}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
/>
|
2026-04-01 00:50:38 +08:00
|
|
|
|
<Text style={styles.termsText}>
|
|
|
|
|
|
我已阅读并同意
|
|
|
|
|
|
<Text style={styles.termsLink}>《用户协议》</Text>
|
|
|
|
|
|
和
|
|
|
|
|
|
<Text style={styles.termsLink}>《隐私政策》</Text>
|
|
|
|
|
|
</Text>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
|
2026-04-01 00:50:38 +08:00
|
|
|
|
{/* 注册按钮 */}
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
style={[styles.registerButton, loading && styles.registerButtonDisabled]}
|
|
|
|
|
|
onPress={handleRegister}
|
|
|
|
|
|
disabled={loading}
|
|
|
|
|
|
activeOpacity={0.9}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
>
|
2026-04-01 00:50:38 +08:00
|
|
|
|
{loading ? (
|
|
|
|
|
|
<ActivityIndicator size="small" color="#FFF" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Text style={styles.registerButtonText}>注 册</Text>
|
|
|
|
|
|
)}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
2026-04-01 00:50:38 +08:00
|
|
|
|
{/* 底部登录提示 */}
|
|
|
|
|
|
<View style={styles.footer}>
|
|
|
|
|
|
<Text style={styles.footerText}>已有账号?</Text>
|
|
|
|
|
|
<TouchableOpacity onPress={handleGoToLogin}>
|
|
|
|
|
|
<Text style={styles.loginLink}>立即登录</Text>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</TouchableOpacity>
|
2026-04-01 00:50:38 +08:00
|
|
|
|
</View>
|
|
|
|
|
|
</Animated.View>
|
|
|
|
|
|
</ScrollView>
|
|
|
|
|
|
</KeyboardAvoidingView>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</SafeAreaView>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-25 05:16:54 +08:00
|
|
|
|
function createRegisterStyles(colors: AppColors) {
|
|
|
|
|
|
return StyleSheet.create({
|
2026-04-01 00:50:38 +08:00
|
|
|
|
container: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
|
|
},
|
|
|
|
|
|
keyboardView: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
},
|
|
|
|
|
|
scrollContent: {
|
|
|
|
|
|
flexGrow: 1,
|
|
|
|
|
|
paddingHorizontal: 28,
|
|
|
|
|
|
paddingTop: 50,
|
|
|
|
|
|
paddingBottom: 50,
|
|
|
|
|
|
},
|
|
|
|
|
|
content: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
maxWidth: 400,
|
|
|
|
|
|
width: '100%',
|
|
|
|
|
|
alignSelf: 'center',
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 标题区域 - 增大间距
|
|
|
|
|
|
titleSection: {
|
|
|
|
|
|
marginBottom: 40,
|
|
|
|
|
|
marginTop: 10,
|
|
|
|
|
|
},
|
|
|
|
|
|
title: {
|
|
|
|
|
|
fontSize: 32,
|
|
|
|
|
|
fontWeight: '700',
|
|
|
|
|
|
color: colors.text.primary,
|
|
|
|
|
|
lineHeight: 40,
|
|
|
|
|
|
},
|
|
|
|
|
|
underline: {
|
|
|
|
|
|
width: 40,
|
|
|
|
|
|
height: 4,
|
|
|
|
|
|
backgroundColor: THEME_COLORS.primary,
|
|
|
|
|
|
borderRadius: 2,
|
|
|
|
|
|
marginTop: 12,
|
|
|
|
|
|
marginBottom: 16,
|
|
|
|
|
|
},
|
|
|
|
|
|
subtitle: {
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
color: colors.text.secondary,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 表单区域
|
|
|
|
|
|
formSection: {
|
|
|
|
|
|
marginBottom: 24,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 输入框
|
|
|
|
|
|
inputContainer: {
|
|
|
|
|
|
marginBottom: 20,
|
|
|
|
|
|
},
|
|
|
|
|
|
inputLabel: {
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
fontWeight: '500',
|
|
|
|
|
|
color: colors.text.secondary,
|
|
|
|
|
|
marginBottom: 10,
|
|
|
|
|
|
},
|
|
|
|
|
|
inputWrapper: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
backgroundColor: '#F5F5F7',
|
|
|
|
|
|
borderRadius: 14,
|
|
|
|
|
|
paddingHorizontal: 18,
|
|
|
|
|
|
height: 56,
|
|
|
|
|
|
},
|
|
|
|
|
|
input: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
color: colors.text.primary,
|
|
|
|
|
|
height: 56,
|
|
|
|
|
|
},
|
|
|
|
|
|
checkIcon: {
|
|
|
|
|
|
marginLeft: 8,
|
|
|
|
|
|
},
|
|
|
|
|
|
eyeButton: {
|
|
|
|
|
|
padding: 4,
|
|
|
|
|
|
marginLeft: 4,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 验证码行
|
|
|
|
|
|
codeRow: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
gap: 12,
|
|
|
|
|
|
},
|
|
|
|
|
|
codeInputWrapper: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
},
|
|
|
|
|
|
sendCodeButton: {
|
|
|
|
|
|
height: 56,
|
|
|
|
|
|
paddingHorizontal: 16,
|
|
|
|
|
|
backgroundColor: 'rgba(255, 107, 53, 0.1)',
|
|
|
|
|
|
borderRadius: 14,
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
minWidth: 100,
|
|
|
|
|
|
},
|
|
|
|
|
|
sendCodeButtonDisabled: {
|
|
|
|
|
|
opacity: 0.5,
|
|
|
|
|
|
},
|
|
|
|
|
|
sendCodeButtonText: {
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
fontWeight: '600',
|
|
|
|
|
|
color: THEME_COLORS.primary,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 服务条款
|
|
|
|
|
|
termsContainer: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
marginBottom: 28,
|
|
|
|
|
|
marginTop: 4,
|
|
|
|
|
|
},
|
|
|
|
|
|
termsText: {
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
color: colors.text.secondary,
|
|
|
|
|
|
marginLeft: 10,
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
lineHeight: 22,
|
|
|
|
|
|
},
|
|
|
|
|
|
termsLink: {
|
|
|
|
|
|
color: THEME_COLORS.primary,
|
|
|
|
|
|
fontWeight: '500',
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 注册按钮
|
|
|
|
|
|
registerButton: {
|
|
|
|
|
|
height: 56,
|
|
|
|
|
|
borderRadius: 14,
|
|
|
|
|
|
backgroundColor: THEME_COLORS.primary,
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
},
|
|
|
|
|
|
registerButtonDisabled: {
|
|
|
|
|
|
opacity: 0.7,
|
|
|
|
|
|
},
|
|
|
|
|
|
registerButtonText: {
|
|
|
|
|
|
fontSize: 17,
|
|
|
|
|
|
fontWeight: '600',
|
|
|
|
|
|
color: '#FFFFFF',
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 底部
|
|
|
|
|
|
footer: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
marginTop: 32,
|
|
|
|
|
|
},
|
|
|
|
|
|
footerText: {
|
|
|
|
|
|
fontSize: 15,
|
|
|
|
|
|
color: colors.text.secondary,
|
|
|
|
|
|
},
|
|
|
|
|
|
loginLink: {
|
|
|
|
|
|
fontSize: 15,
|
|
|
|
|
|
color: THEME_COLORS.primary,
|
|
|
|
|
|
fontWeight: '600',
|
|
|
|
|
|
marginLeft: 6,
|
|
|
|
|
|
},
|
2026-03-25 05:16:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
export default RegisterScreen;
|