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.

1267 lines
33 KiB

import React, { useEffect, useState, useRef } from 'react';
2 months ago
import {
View,
Text,
StyleSheet,
TouchableOpacity,
StatusBar,
Platform,
BackHandler,
TextInput,
Animated,
Easing,
FlatList,
Keyboard,
Modal,
InteractionManager,
2 months ago
} from 'react-native';
import { useTranslation } from 'react-i18next';
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '../navigation/types';
import { Country, countries } from '../constants/countries';
import { useAuth } from '../contexts/AuthContext';
2 months ago
// 常见邮箱后缀列表
const EMAIL_DOMAINS = [
'gmail.com',
'yahoo.com',
'hotmail.com',
'outlook.com',
'icloud.com',
'mail.com',
'protonmail.com',
'qq.com',
'163.com',
'126.com',
];
type LoginScreenProps = {
onClose?: () => void;
isModal?: boolean;
};
export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => {
2 months ago
const { t } = useTranslation();
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const { login } = useAuth();
// 邮箱登录状态
const [showEmailLogin, setShowEmailLogin] = useState(false);
const [email, setEmail] = useState('');
const [emailPassword, setEmailPassword] = useState('');
const [emailPasswordError, setEmailPasswordError] = useState(false);
const [showSuggestions, setShowSuggestions] = useState(false);
const [suggestions, setSuggestions] = useState<string[]>([]);
// 手机号登录状态
const [showPhoneLogin, setShowPhoneLogin] = useState(false);
const [phoneNumber, setPhoneNumber] = useState('');
const [password, setPassword] = useState('');
const [passwordError, setPasswordError] = useState(false);
const [showCountryModal, setShowCountryModal] = useState(false);
const [selectedCountry, setSelectedCountry] = useState<Country>({
name: 'Republic of Congo',
code: 'CG',
flag: '🇨🇬',
userCount: 300000,
phoneCode: '+242'
});
// 动画值
const emailSlideAnim = useRef(new Animated.Value(400)).current;
const phoneSlideAnim = useRef(new Animated.Value(400)).current;
const [emailLoginMounted, setEmailLoginMounted] = useState(false);
const [phoneLoginMounted, setPhoneLoginMounted] = useState(false);
// 处理邮箱输入变化,生成邮箱建议
const handleEmailChange = (text: string) => {
setEmail(text);
// 检查是否包含@符号
if (text.includes('@')) {
const [username, domain] = text.split('@');
if (domain) {
// 如果已经输入了部分域名,过滤匹配的域名
const filteredDomains = EMAIL_DOMAINS.filter(item =>
item.toLowerCase().startsWith(domain.toLowerCase())
);
// 生成完整的邮箱建议列表
const emailSuggestions = filteredDomains.map(d => `${username}@${d}`);
setSuggestions(emailSuggestions);
setShowSuggestions(emailSuggestions.length > 0);
} else {
// 如果只输入了@,显示所有域名建议
const emailSuggestions = EMAIL_DOMAINS.map(d => `${username}@${d}`);
setSuggestions(emailSuggestions);
setShowSuggestions(true);
}
} else if (text.length > 0) {
// 没有@符号但有输入内容,显示常见邮箱后缀建议
const emailSuggestions = EMAIL_DOMAINS.map(d => `${text}@${d}`);
setSuggestions(emailSuggestions);
setShowSuggestions(true);
} else {
// 输入为空,不显示建议
setShowSuggestions(false);
}
};
// 选择一个邮箱建议
const handleSelectSuggestion = (suggestion: string) => {
setEmail(suggestion);
setShowSuggestions(false);
};
// 验证邮箱格式
const isValidEmail = (email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
// 渲染单个邮箱建议项
const renderSuggestionItem = ({ item }: { item: string }) => (
<TouchableOpacity
style={styles.suggestionItem}
onPress={() => handleSelectSuggestion(item)}
>
<Text style={styles.suggestionText}>{item}</Text>
</TouchableOpacity>
);
// 选择国家
const handleCountrySelect = (country: Country) => {
setSelectedCountry(country);
setShowCountryModal(false);
};
// 渲染国家列表项 - 添加性能优化
const renderCountryItem = React.useCallback(({ item }: { item: Country }) => (
<TouchableOpacity
style={styles.countryItem}
onPress={() => handleCountrySelect(item)}
activeOpacity={0.7}
>
<Text style={styles.countryItemFlag}>{item.flag}</Text>
<View style={styles.countryItemContent}>
<Text style={styles.countryItemName}>{item.name}</Text>
<Text style={styles.countryItemCode}>{item.phoneCode}</Text>
</View>
</TouchableOpacity>
), []);
// 添加Android返回按钮处理
useEffect(() => {
const backAction = () => {
if (showEmailLogin) {
closeEmailLogin();
return true;
}
if (showPhoneLogin) {
closePhoneLogin();
return true;
}
if (showCountryModal) {
setShowCountryModal(false);
return true;
}
handleClose();
return true;
};
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
backAction
);
2 months ago
return () => backHandler.remove();
}, [showEmailLogin, showPhoneLogin, showCountryModal]);
// 初始化动画配置
useEffect(() => {
// React Native Animated API不支持直接设置useNativeDriver
// 这些配置会在动画函数中使用
}, []);
// 打开邮箱登录面板
const openEmailLogin = () => {
emailSlideAnim.setValue(400);
setEmailLoginMounted(true);
// 等待组件挂载后启动动画
requestAnimationFrame(() => {
setShowEmailLogin(true);
Animated.spring(emailSlideAnim, {
toValue: 0,
useNativeDriver: true,
friction: 8,
tension: 40,
restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01,
}).start();
});
};
// 关闭邮箱登录面板
const closeEmailLogin = () => {
Keyboard.dismiss();
// 使用更简单的配置减少计算复杂度
Animated.timing(emailSlideAnim, {
toValue: 400,
duration: 200,
useNativeDriver: true,
easing: Easing.cubic,
}).start();
// 先隐藏建议列表,减少布局计算
setShowSuggestions(false);
// 使用InteractionManager确保动画完成后再执行耗时操作
InteractionManager.runAfterInteractions(() => {
setShowEmailLogin(false);
setEmail('');
setEmailPassword('');
setEmailPasswordError(false);
// 延迟卸载组件
setTimeout(() => {
setEmailLoginMounted(false);
}, 50);
});
};
// 打开手机号登录面板
const openPhoneLogin = () => {
phoneSlideAnim.setValue(400);
setPhoneLoginMounted(true);
// 等待组件挂载后启动动画
requestAnimationFrame(() => {
setShowPhoneLogin(true);
Animated.spring(phoneSlideAnim, {
toValue: 0,
useNativeDriver: true,
friction: 8,
tension: 40,
restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01,
}).start();
});
};
// 关闭手机号登录面板
const closePhoneLogin = () => {
Keyboard.dismiss();
// 使用更简单的配置减少计算复杂度
Animated.timing(phoneSlideAnim, {
toValue: 400,
duration: 200,
useNativeDriver: true,
easing: Easing.cubic,
}).start();
// 使用InteractionManager确保动画完成后再执行耗时操作
InteractionManager.runAfterInteractions(() => {
setShowPhoneLogin(false);
setPhoneNumber('');
// 延迟卸载组件
setTimeout(() => {
setPhoneLoginMounted(false);
}, 50);
});
};
// 修改关闭按钮处理函数
2 months ago
const handleClose = () => {
if (isModal && onClose) {
onClose();
} else {
navigation.goBack();
}
2 months ago
};
const handleGoogleLogin = () => {
// 处理Google登录
};
const handleFacebookLogin = () => {
// 处理Facebook登录
};
const handleAppleLogin = () => {
// 处理Apple登录
};
const handleInstagramLogin = () => {
// 处理Instagram登录
};
const handleEmailLogin = () => {
// 处理邮箱登录 - 显示邮箱登录面板
openEmailLogin();
};
const handleEmailContinue = async () => {
if (emailPassword === '123') {
setEmailPasswordError(false);
await login();
closeEmailLogin();
navigation.replace('MainTabs');
} else {
setEmailPasswordError(true);
}
2 months ago
};
const handlePhoneLogin = () => {
// 处理手机号登录 - 显示手机号登录面板
openPhoneLogin();
};
const handlePhoneVerificationSuccess = async () => {
await login();
closePhoneLogin();
navigation.replace('MainTabs');
};
const handlePhoneContinue = async () => {
if (phoneNumber.trim() && password) {
if (password === '123') {
await login();
closePhoneLogin();
navigation.replace('MainTabs');
} else {
setPasswordError(true);
}
}
2 months ago
};
const handleForgotPassword = () => {
// 处理忘记密码
};
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" backgroundColor="#0066FF" />
{/* 顶部蓝色背景区域 */}
<View style={styles.blueHeader}>
<Text style={styles.logo}>brainnel</Text>
<View style={styles.features}>
<View style={styles.featureItem}>
<View style={styles.featureIconContainer}>
<Text style={styles.featureIcon}>💰</Text>
</View>
<Text style={styles.featureText}>{t('wholesalePrice')}</Text>
</View>
<View style={styles.featureItem}>
<View style={styles.featureIconContainer}>
<Text style={styles.featureIcon}>🚚</Text>
</View>
<Text style={styles.featureText}>{t('fastShipping')}</Text>
</View>
</View>
</View>
{/* 登录区域 */}
<View style={styles.loginContainer}>
<TouchableOpacity
style={styles.closeButton}
onPress={handleClose}
>
<Text style={styles.closeButtonText}>×</Text>
</TouchableOpacity>
<View style={styles.titleContainer}>
{/* <Text style={styles.title}>Login For brainnel</Text> */}
<Text style={styles.subtitle}>{t('loginSubtitle')}</Text>
</View>
2 months ago
{/* 登录按钮 */}
2 months ago
<TouchableOpacity
style={styles.loginButton}
2 months ago
onPress={handleGoogleLogin}
>
<View style={styles.loginButtonIcon}>
<Text>G</Text>
</View>
<Text style={styles.loginButtonText}>{t('continueWithGoogle')}</Text>
2 months ago
</TouchableOpacity>
<TouchableOpacity
style={styles.loginButton}
2 months ago
onPress={handleFacebookLogin}
>
<View style={[styles.loginButtonIcon, styles.facebookIcon]}>
<Text style={{color: '#fff'}}>f</Text>
</View>
<Text style={styles.loginButtonText}>{t('continueWithFacebook')}</Text>
2 months ago
</TouchableOpacity>
{Platform.OS === 'ios' && (
<TouchableOpacity
style={styles.loginButton}
2 months ago
onPress={handleAppleLogin}
>
<View style={[styles.loginButtonIcon, styles.appleIconBg]}>
<Text>🍎</Text>
</View>
<Text style={styles.loginButtonText}>{t('continueWithApple')}</Text>
2 months ago
</TouchableOpacity>
)}
<TouchableOpacity
style={styles.loginButton}
onPress={handleInstagramLogin}
>
<View style={[styles.loginButtonIcon, styles.instagramIcon]}>
<Text>📷</Text>
</View>
<Text style={styles.loginButtonText}>{t('continueWithInstagram')}</Text>
</TouchableOpacity>
2 months ago
<TouchableOpacity
style={styles.loginButton}
onPress={handleEmailLogin}
>
<View style={styles.loginButtonIcon}>
<Text></Text>
</View>
<Text style={styles.loginButtonText}>{t('continueWithEmail')}</Text>
</TouchableOpacity>
2 months ago
<TouchableOpacity
style={styles.loginButton}
onPress={handlePhoneLogin}
>
<View style={styles.loginButtonIcon}>
<Text>📱</Text>
</View>
<Text style={styles.loginButtonText}>{t('continueWithPhone')}</Text>
</TouchableOpacity>
2 months ago
{/* 忘记密码 */}
<TouchableOpacity
style={styles.forgotPassword}
onPress={handleForgotPassword}
>
<Text style={styles.forgotPasswordText}>{t('forgotPassword')}</Text>
</TouchableOpacity>
{/* 服务条款 */}
<View style={styles.termsContainer}>
<Text style={styles.terms}>
{t('termsText')} <Text style={styles.link}>{t('termsOfUse')}</Text>
</Text>
<Text style={styles.terms}>
{t('and')} <Text style={styles.link}>{t('privacyPolicy')}</Text>
</Text>
</View>
2 months ago
</View>
{/* 邮箱登录面板 - 从底部滑出 */}
{emailLoginMounted && (
<Animated.View
style={[
styles.emailLoginContainer,
{
transform: [{ translateY: emailSlideAnim }],
opacity: emailSlideAnim.interpolate({
inputRange: [0, 400],
outputRange: [1, 0.7]
})
}
]}
removeClippedSubviews={true}
collapsable={false}
>
<View style={styles.emailLoginHeader}>
<TouchableOpacity
style={styles.emailLoginCloseButton}
onPress={closeEmailLogin}
activeOpacity={0.7}
>
<Text style={styles.emailLoginCloseButtonText}></Text>
</TouchableOpacity>
<Text style={styles.emailLoginTitle}>{t('logInOrSignUp')}</Text>
</View>
<View style={styles.emailLoginContent}>
<View style={styles.emailInputContainer}>
<TextInput
style={styles.emailInput}
placeholder={t('pleaseEnterEmail')}
value={email}
onChangeText={(text) => {
handleEmailChange(text);
setEmailPasswordError(false);
}}
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
autoFocus
maxLength={50}
/>
{email.length > 0 && (
<TouchableOpacity
style={styles.emailClearButton}
onPress={() => {
setEmail('');
setShowSuggestions(false);
setEmailPasswordError(false);
}}
activeOpacity={0.7}
>
<Text style={styles.emailClearButtonText}></Text>
</TouchableOpacity>
)}
</View>
{/* 邮箱后缀建议列表 */}
{showSuggestions && (
<View style={styles.suggestionsContainer}>
<FlatList
data={suggestions.slice(0, 5)} // 限制最多显示5个建议
renderItem={renderSuggestionItem}
keyExtractor={(item) => item}
style={styles.suggestionsList}
keyboardShouldPersistTaps="handled"
removeClippedSubviews={true}
initialNumToRender={5}
maxToRenderPerBatch={5}
windowSize={5}
getItemLayout={(data, index) => (
{length: 44, offset: 44 * index, index}
)}
/>
</View>
)}
{/* 密码输入框 */}
<View style={[
styles.phoneInputContainer,
emailPasswordError && styles.passwordErrorContainer
]}>
<TextInput
style={styles.passwordInput}
placeholder={t('enterPassword')}
value={emailPassword}
onChangeText={(text) => {
setEmailPassword(text);
setEmailPasswordError(false);
}}
secureTextEntry={true}
autoCapitalize="none"
/>
{emailPasswordError && (
<View style={styles.passwordErrorIcon}>
<Text style={styles.passwordErrorIconText}>!</Text>
</View>
)}
</View>
{/* 密码错误提示 */}
{emailPasswordError && (
<>
<Text style={styles.passwordErrorText}>
{t('passwordIncorrect')}
</Text>
<TouchableOpacity
style={styles.forgotPasswordLink}
onPress={handleForgotPassword}
activeOpacity={0.7}
>
<Text style={styles.forgotPasswordLinkText}>
{t('forgotPassword')}
</Text>
</TouchableOpacity>
</>
)}
<TouchableOpacity
style={[
styles.emailContinueButton,
(!isValidEmail(email) || !emailPassword) && styles.emailDisabledButton
]}
onPress={handleEmailContinue}
disabled={!isValidEmail(email) || !emailPassword}
activeOpacity={0.7}
>
<Text style={styles.emailContinueButtonText}>{t('continue')}</Text>
</TouchableOpacity>
</View>
</Animated.View>
)}
{/* 手机号登录面板 - 从底部滑出 */}
{phoneLoginMounted && (
<Animated.View
style={[
styles.phoneLoginContainer,
{
transform: [{ translateY: phoneSlideAnim }],
opacity: phoneSlideAnim.interpolate({
inputRange: [0, 400],
outputRange: [1, 0.7]
})
}
]}
removeClippedSubviews={true}
collapsable={false}
>
<View style={styles.phoneLoginHeader}>
<TouchableOpacity
style={styles.phoneLoginCloseButton}
onPress={closePhoneLogin}
activeOpacity={0.7}
>
<Text style={styles.phoneLoginCloseButtonText}></Text>
</TouchableOpacity>
<Text style={styles.phoneLoginTitle}>{t('logInOrSignUp')}</Text>
</View>
<View style={styles.phoneLoginContent}>
<View style={styles.phoneInputContainer}>
<TouchableOpacity
style={styles.countrySelector}
onPress={() => setShowCountryModal(true)}
activeOpacity={0.7}
>
<Text style={styles.countryFlag}>{selectedCountry.flag}</Text>
<Text style={styles.countryCode}>{selectedCountry.phoneCode}</Text>
<Text style={styles.downArrow}></Text>
</TouchableOpacity>
<TextInput
style={styles.phoneInput}
placeholder={t('phoneNumber')}
value={phoneNumber}
onChangeText={(text) => {
setPhoneNumber(text);
setPasswordError(false);
}}
keyboardType="phone-pad"
autoFocus
maxLength={15}
/>
{phoneNumber.length > 0 && (
<TouchableOpacity
style={styles.phoneClearButton}
onPress={() => {
setPhoneNumber('');
setPasswordError(false);
}}
activeOpacity={0.7}
>
<Text style={styles.phoneClearButtonText}></Text>
</TouchableOpacity>
)}
</View>
{/* 密码输入框 */}
<View style={[
styles.phoneInputContainer,
passwordError && styles.passwordErrorContainer
]}>
<TextInput
style={styles.passwordInput}
placeholder={t('enterPassword')}
value={password}
onChangeText={(text) => {
setPassword(text);
setPasswordError(false);
}}
secureTextEntry={true}
autoCapitalize="none"
/>
{passwordError && (
<View style={styles.passwordErrorIcon}>
<Text style={styles.passwordErrorIconText}>!</Text>
</View>
)}
</View>
{/* 密码错误提示 */}
{passwordError && (
<>
<Text style={styles.passwordErrorText}>
{t('passwordIncorrect')}
</Text>
<TouchableOpacity
style={styles.forgotPasswordLink}
onPress={handleForgotPassword}
activeOpacity={0.7}
>
<Text style={styles.forgotPasswordLinkText}>
{t('forgotPassword')}
</Text>
</TouchableOpacity>
</>
)}
<Text style={[
styles.phoneInfoText,
passwordError && { marginTop: 5 }
]}>
</Text>
<TouchableOpacity
style={[
styles.phoneContinueButton,
(!phoneNumber.trim() || !password) && styles.phoneDisabledButton
]}
onPress={handlePhoneContinue}
disabled={!phoneNumber.trim() || !password}
activeOpacity={0.7}
>
<Text style={styles.phoneContinueButtonText}>{t('continue')}</Text>
</TouchableOpacity>
</View>
</Animated.View>
)}
{/* 国家选择模态框 */}
<Modal
visible={showCountryModal}
animationType="slide"
transparent={true}
onRequestClose={() => setShowCountryModal(false)}
hardwareAccelerated={true}
statusBarTranslucent={true}
presentationStyle="overFullScreen"
>
<View style={styles.countryModalContainer}>
<View style={styles.countryModalContent}>
<View style={styles.countryModalHeader}>
<TouchableOpacity
style={styles.countryModalCloseButton}
onPress={() => setShowCountryModal(false)}
activeOpacity={0.7}
>
<Text style={styles.countryModalCloseButtonText}></Text>
</TouchableOpacity>
<Text style={styles.countryModalTitle}>{t('selectCountry')}</Text>
</View>
<FlatList
data={countries}
renderItem={renderCountryItem}
keyExtractor={(item) => item.code}
style={styles.countryList}
showsVerticalScrollIndicator={false}
removeClippedSubviews={true}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={10}
getItemLayout={(data, index) => (
{length: 69, offset: 69 * index, index}
)}
/>
</View>
</View>
</Modal>
2 months ago
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
closeButton: {
position: 'absolute',
top: 15,
left: 10,
width: 24,
height: 24,
2 months ago
justifyContent: 'center',
alignItems: 'center',
zIndex: 1,
},
closeButtonText: {
color: '#000',
fontSize: 24,
fontWeight: '300',
2 months ago
},
blueHeader: {
backgroundColor: '#0066FF',
paddingHorizontal: 20,
paddingBottom: 20,
paddingTop: Platform.OS === 'ios' ? 60 : 40,
2 months ago
borderBottomLeftRadius: 24,
borderBottomRightRadius: 24,
},
logo: {
fontSize: 28,
fontWeight: 'bold',
color: '#fff',
marginBottom: 15,
},
features: {
flexDirection: 'row',
gap: 16,
2 months ago
},
featureItem: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
featureIconContainer: {
backgroundColor: 'rgba(255, 255, 255, 0.2)',
borderRadius: 8,
width: 24,
height: 24,
justifyContent: 'center',
alignItems: 'center',
},
featureIcon: {
fontSize: 12,
},
featureText: {
fontSize: 14,
color: '#fff',
},
loginContainer: {
flex: 1,
paddingHorizontal: 20,
paddingTop: Platform.OS === 'ios' ? 40 : 20,
},
titleContainer: {
alignItems: 'center',
marginBottom: 30,
paddingTop: 20,
position: 'relative',
2 months ago
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#000',
marginBottom: 8,
textAlign: 'center',
2 months ago
},
subtitle: {
fontSize: 14,
color: '#666',
textAlign: 'center',
2 months ago
},
loginButton: {
2 months ago
flexDirection: 'row',
height: 50,
borderRadius: 25,
borderWidth: 1,
borderColor: '#E1E1E1',
2 months ago
alignItems: 'center',
marginBottom: 12,
paddingHorizontal: 16,
backgroundColor: '#fff',
2 months ago
},
loginButtonIcon: {
width: 24,
height: 24,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
marginRight: 16,
2 months ago
},
facebookIcon: {
2 months ago
backgroundColor: '#3b5998',
},
appleIconBg: {
2 months ago
backgroundColor: '#000',
},
instagramIcon: {
backgroundColor: '#E1306C',
2 months ago
},
loginButtonText: {
flex: 1,
fontSize: 16,
color: '#000',
textAlign: 'center',
marginRight: 16,
},
forgotPassword: {
alignItems: 'center',
marginVertical: 20,
},
forgotPasswordText: {
color: '#0066FF',
fontSize: 14,
},
termsContainer: {
alignItems: 'center',
marginTop: 10,
},
terms: {
fontSize: 12,
color: '#666',
textAlign: 'center',
lineHeight: 18,
},
link: {
color: '#0066FF',
},
// 邮箱登录样式
emailLoginContainer: {
position: 'absolute',
top: Platform.OS === 'ios' ? 60 : 40,
height: Platform.OS === 'ios' ? 'auto' : '100%',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#fff',
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
shadowColor: '#000',
shadowOffset: { width: 0, height: -2 },
shadowOpacity: 0.1,
shadowRadius: 5,
elevation: 5,
zIndex: 10,
},
emailLoginHeader: {
flexDirection: 'row',
alignItems: 'center',
paddingTop: 20,
paddingHorizontal: 16,
paddingBottom: 15,
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0',
2 months ago
},
emailLoginCloseButton: {
padding: 8,
width: 36,
height: 36,
justifyContent: 'center',
alignItems: 'center',
2 months ago
},
emailLoginCloseButtonText: {
fontSize: 18,
color: '#000',
},
emailLoginTitle: {
2 months ago
flex: 1,
fontSize: 18,
fontWeight: '600',
color: '#000',
2 months ago
textAlign: 'center',
marginRight: 36, // 为了让标题真正居中
},
emailLoginContent: {
padding: 20,
paddingBottom: Platform.OS === 'ios' ? 50 : 30,
flex: 1,
},
emailInputContainer: {
flexDirection: 'row',
alignItems: 'center',
borderWidth: 1,
borderColor: '#E1E1E1',
borderRadius: 25,
height: 50,
marginBottom: 20,
},
emailInput: {
flex: 1,
height: '100%',
paddingHorizontal: 16,
fontSize: 16,
paddingRight: 36,
},
emailClearButton: {
position: 'absolute',
right: 12,
top: '50%',
transform: [{ translateY: -12 }],
height: 24,
width: 24,
justifyContent: 'center',
alignItems: 'center',
},
emailClearButtonText: {
2 months ago
fontSize: 16,
color: '#999',
2 months ago
fontWeight: '500',
textAlign: 'center',
},
suggestionsContainer: {
borderWidth: 1,
borderColor: '#E1E1E1',
borderRadius: 10,
marginTop: -10,
marginBottom: 20,
maxHeight: 200,
backgroundColor: '#fff',
},
suggestionsList: {
padding: 8,
},
suggestionItem: {
paddingVertical: 12,
paddingHorizontal: 16,
borderBottomWidth: 1,
borderBottomColor: '#F0F0F0',
},
suggestionText: {
fontSize: 16,
color: '#333',
},
emailContinueButton: {
height: 50,
backgroundColor: '#0039CB',
borderRadius: 25,
justifyContent: 'center',
alignItems: 'center',
marginTop: 20,
},
emailDisabledButton: {
backgroundColor: '#CCCCCC',
2 months ago
},
emailContinueButtonText: {
2 months ago
color: '#fff',
fontSize: 16,
fontWeight: '600',
2 months ago
},
// 手机号登录样式
phoneLoginContainer: {
position: 'absolute',
top: Platform.OS === 'ios' ? 60 : 40,
height: Platform.OS === 'ios' ? 'auto' : '100%',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#fff',
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
shadowColor: '#000',
shadowOffset: { width: 0, height: -2 },
shadowOpacity: 0.1,
shadowRadius: 5,
elevation: 5,
zIndex: 10,
},
phoneLoginHeader: {
2 months ago
flexDirection: 'row',
alignItems: 'center',
paddingTop: 20,
paddingHorizontal: 16,
paddingBottom: 15,
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0',
},
phoneLoginCloseButton: {
padding: 8,
width: 36,
height: 36,
justifyContent: 'center',
alignItems: 'center',
2 months ago
},
phoneLoginCloseButtonText: {
fontSize: 18,
color: '#000',
},
phoneLoginTitle: {
2 months ago
flex: 1,
fontSize: 18,
fontWeight: '600',
color: '#000',
textAlign: 'center',
marginRight: 36, // 为了让标题真正居中
2 months ago
},
phoneLoginContent: {
padding: 20,
paddingBottom: Platform.OS === 'ios' ? 50 : 30,
flex: 1,
2 months ago
},
phoneInputContainer: {
2 months ago
flexDirection: 'row',
alignItems: 'center',
borderWidth: 1,
borderColor: '#E1E1E1',
borderRadius: 25,
height: 50,
marginBottom: 20,
position: 'relative',
2 months ago
},
countrySelector: {
flexDirection: 'row',
2 months ago
alignItems: 'center',
paddingHorizontal: 12,
borderRightWidth: 1,
borderRightColor: '#E1E1E1',
backgroundColor: '#F7F7F7',
height: '100%',
minWidth: 90,
width: 90,
justifyContent: 'center',
},
countryFlag: {
fontSize: 18,
marginRight: 4,
},
countryCode: {
fontSize: 12,
color: '#333',
marginRight: 4,
2 months ago
},
downArrow: {
fontSize: 8,
color: '#666',
},
phoneInput: {
flex: 1,
height: '100%',
paddingHorizontal: 16,
fontSize: 16,
paddingRight: 36,
},
phoneClearButton: {
position: 'absolute',
right: 12,
top: '50%',
transform: [{ translateY: -12 }],
height: 24,
width: 24,
2 months ago
justifyContent: 'center',
alignItems: 'center',
},
phoneClearButtonText: {
fontSize: 16,
color: '#999',
fontWeight: '500',
textAlign: 'center',
},
phoneInfoText: {
fontSize: 14,
color: '#666',
marginBottom: 32,
lineHeight: 20,
},
phoneContinueButton: {
height: 50,
backgroundColor: '#0039CB',
borderRadius: 25,
justifyContent: 'center',
alignItems: 'center',
},
phoneDisabledButton: {
backgroundColor: '#CCCCCC',
},
phoneContinueButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
// 国家选择模态框样式
countryModalContainer: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'flex-end',
zIndex: 999,
},
countryModalContent: {
backgroundColor: '#fff',
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
maxHeight: '80%',
zIndex: 1000,
},
countryModalHeader: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#E5E5E5',
},
countryModalCloseButton: {
padding: 4,
},
countryModalCloseButtonText: {
fontSize: 18,
color: '#999',
},
countryModalTitle: {
flex: 1,
fontSize: 18,
fontWeight: '600',
textAlign: 'center',
marginRight: 24, // Balance with close button
},
countryList: {
padding: 8,
},
countryItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#F0F0F0',
},
countryItemFlag: {
2 months ago
fontSize: 24,
marginRight: 16,
2 months ago
},
countryItemContent: {
flex: 1,
},
countryItemName: {
fontSize: 16,
2 months ago
color: '#333',
},
countryItemCode: {
fontSize: 14,
color: '#666',
marginTop: 4,
},
// 密码输入框样式
passwordInput: {
flex: 1,
height: '100%',
paddingHorizontal: 16,
fontSize: 16,
},
passwordErrorContainer: {
borderColor: '#FF3B30',
},
passwordErrorIcon: {
position: 'absolute',
right: 12,
top: '50%',
transform: [{ translateY: -12 }],
width: 24,
height: 24,
backgroundColor: '#FF3B30',
borderRadius: 12,
justifyContent: 'center',
2 months ago
alignItems: 'center',
},
passwordErrorIconText: {
color: 'white',
fontWeight: 'bold',
fontSize: 16,
},
passwordErrorText: {
color: '#FF3B30',
2 months ago
fontSize: 14,
marginTop: -12,
marginBottom: 16,
paddingHorizontal: 5,
2 months ago
},
forgotPasswordLink: {
alignItems: 'center',
marginTop: 5,
2 months ago
},
forgotPasswordLinkText: {
2 months ago
color: '#0066FF',
fontSize: 14,
2 months ago
},
});