feat: 首页更新
This commit is contained in:
363
components/Header/LoginRegisterModal.tsx
Normal file
363
components/Header/LoginRegisterModal.tsx
Normal file
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* 登录/注册弹窗组件
|
||||
* 支持登录和注册两种模式
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
TextInput,
|
||||
Modal,
|
||||
ScrollView,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import { useColorScheme, useThemeColors } from '@/theme';
|
||||
import { createThemeStyles } from '@/theme';
|
||||
import { useUserStore } from '@/stores';
|
||||
import Ionicons from '@expo/vector-icons/Ionicons';
|
||||
|
||||
/**
|
||||
* 创建主题样式
|
||||
*/
|
||||
const styles = createThemeStyles((colors) => ({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
container: {
|
||||
backgroundColor: colors.background,
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 20,
|
||||
paddingBottom: 40,
|
||||
maxHeight: '80%',
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
color: colors.text,
|
||||
},
|
||||
closeButton: {
|
||||
padding: 8,
|
||||
},
|
||||
tabContainer: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.borderSecondary,
|
||||
},
|
||||
tab: {
|
||||
flex: 1,
|
||||
paddingVertical: 12,
|
||||
alignItems: 'center',
|
||||
borderBottomWidth: 2,
|
||||
borderBottomColor: 'transparent',
|
||||
},
|
||||
activeTab: {
|
||||
borderBottomColor: colors.primary,
|
||||
},
|
||||
tabText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: colors.text + '80',
|
||||
},
|
||||
activeTabText: {
|
||||
color: colors.primary,
|
||||
},
|
||||
form: {
|
||||
gap: 12,
|
||||
},
|
||||
inputContainer: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
label: {
|
||||
fontSize: 12,
|
||||
color: colors.text + '80',
|
||||
marginBottom: 6,
|
||||
},
|
||||
input: {
|
||||
borderWidth: 1,
|
||||
borderColor: colors.borderSecondary,
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
fontSize: 14,
|
||||
color: colors.text,
|
||||
backgroundColor: colors.card,
|
||||
},
|
||||
inputFocused: {
|
||||
borderColor: colors.primary,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 12,
|
||||
color: '#ff4444',
|
||||
marginTop: 4,
|
||||
},
|
||||
submitButton: {
|
||||
backgroundColor: colors.primary,
|
||||
borderRadius: 8,
|
||||
paddingVertical: 12,
|
||||
alignItems: 'center',
|
||||
marginTop: 20,
|
||||
},
|
||||
submitButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
},
|
||||
divider: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: 16,
|
||||
},
|
||||
dividerLine: {
|
||||
flex: 1,
|
||||
height: 1,
|
||||
backgroundColor: colors.borderSecondary,
|
||||
},
|
||||
dividerText: {
|
||||
marginHorizontal: 12,
|
||||
fontSize: 12,
|
||||
color: colors.text + '80',
|
||||
},
|
||||
socialButtons: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
socialButton: {
|
||||
flex: 1,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.borderSecondary,
|
||||
borderRadius: 8,
|
||||
paddingVertical: 10,
|
||||
alignItems: 'center',
|
||||
},
|
||||
agreementText: {
|
||||
fontSize: 12,
|
||||
color: colors.text + '80',
|
||||
marginTop: 12,
|
||||
lineHeight: 18,
|
||||
},
|
||||
agreementLink: {
|
||||
color: colors.primary,
|
||||
textDecorationLine: 'underline',
|
||||
},
|
||||
}));
|
||||
|
||||
interface LoginRegisterModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录/注册弹窗组件
|
||||
*/
|
||||
export default function LoginRegisterModal({ visible, onClose }: LoginRegisterModalProps) {
|
||||
const colorScheme = useColorScheme();
|
||||
const s = styles[colorScheme];
|
||||
const themeColors = useThemeColors();
|
||||
const { login } = useUserStore();
|
||||
|
||||
const [mode, setMode] = useState<'login' | 'register'>('login');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const validateEmail = useCallback((email: string) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}, []);
|
||||
|
||||
const validateForm = useCallback(() => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
|
||||
if (!email.trim()) {
|
||||
newErrors.email = '请输入邮箱';
|
||||
} else if (!validateEmail(email)) {
|
||||
newErrors.email = '邮箱格式不正确';
|
||||
}
|
||||
|
||||
if (!password.trim()) {
|
||||
newErrors.password = '请输入密码';
|
||||
} else if (password.length < 6) {
|
||||
newErrors.password = '密码至少6个字符';
|
||||
}
|
||||
|
||||
if (mode === 'register') {
|
||||
if (!confirmPassword.trim()) {
|
||||
newErrors.confirmPassword = '请确认密码';
|
||||
} else if (password !== confirmPassword) {
|
||||
newErrors.confirmPassword = '两次输入的密码不一致';
|
||||
}
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
}, [email, password, confirmPassword, mode, validateEmail]);
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 模拟 API 调用
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// 模拟登录/注册成功
|
||||
const mockUser = {
|
||||
id: '1',
|
||||
username: email.split('@')[0],
|
||||
email,
|
||||
avatar: 'https://i.pravatar.cc/150?img=1',
|
||||
nickname: '用户',
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
login(mockUser, 'mock-token-' + Date.now());
|
||||
|
||||
Alert.alert('成功', mode === 'login' ? '登录成功!' : '注册成功!');
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
Alert.alert('失败', error.message || '操作失败,请重试');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [validateForm, email, mode, login, onClose]);
|
||||
|
||||
const handleSwitchMode = useCallback(() => {
|
||||
setMode(mode === 'login' ? 'register' : 'login');
|
||||
setErrors({});
|
||||
}, [mode]);
|
||||
|
||||
return (
|
||||
<Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
|
||||
<View style={s.overlay}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<TouchableOpacity style={{ flex: 1 }} activeOpacity={1} onPress={onClose} />
|
||||
<View style={s.container}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{/* 头部 */}
|
||||
<View style={s.header}>
|
||||
<Text style={s.title}>{mode === 'login' ? '登录' : '注册'}</Text>
|
||||
<TouchableOpacity style={s.closeButton} onPress={onClose} activeOpacity={0.7}>
|
||||
<Ionicons name="close" size={24} color={themeColors.text} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* 标签页 */}
|
||||
<View style={s.tabContainer}>
|
||||
<TouchableOpacity
|
||||
style={[s.tab, mode === 'login' && s.activeTab]}
|
||||
onPress={() => setMode('login')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={[s.tabText, mode === 'login' && s.activeTabText]}>登录</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[s.tab, mode === 'register' && s.activeTab]}
|
||||
onPress={() => setMode('register')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={[s.tabText, mode === 'register' && s.activeTabText]}>注册</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* 表单 */}
|
||||
<View style={s.form}>
|
||||
{/* 邮箱 */}
|
||||
<View style={s.inputContainer}>
|
||||
<Text style={s.label}>邮箱</Text>
|
||||
<TextInput
|
||||
style={[s.input, errors.email && { borderColor: '#ff4444' }]}
|
||||
placeholder="请输入邮箱"
|
||||
placeholderTextColor={themeColors.text + '60'}
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
keyboardType="email-address"
|
||||
editable={!loading}
|
||||
/>
|
||||
{errors.email && <Text style={s.errorText}>{errors.email}</Text>}
|
||||
</View>
|
||||
|
||||
{/* 密码 */}
|
||||
<View style={s.inputContainer}>
|
||||
<Text style={s.label}>密码</Text>
|
||||
<TextInput
|
||||
style={[s.input, errors.password && { borderColor: '#ff4444' }]}
|
||||
placeholder="请输入密码"
|
||||
placeholderTextColor={themeColors.text + '60'}
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
editable={!loading}
|
||||
/>
|
||||
{errors.password && <Text style={s.errorText}>{errors.password}</Text>}
|
||||
</View>
|
||||
|
||||
{/* 确认密码(注册模式) */}
|
||||
{mode === 'register' && (
|
||||
<View style={s.inputContainer}>
|
||||
<Text style={s.label}>确认密码</Text>
|
||||
<TextInput
|
||||
style={[s.input, errors.confirmPassword && { borderColor: '#ff4444' }]}
|
||||
placeholder="请再次输入密码"
|
||||
placeholderTextColor={themeColors.text + '60'}
|
||||
value={confirmPassword}
|
||||
onChangeText={setConfirmPassword}
|
||||
secureTextEntry
|
||||
editable={!loading}
|
||||
/>
|
||||
{errors.confirmPassword && (
|
||||
<Text style={s.errorText}>{errors.confirmPassword}</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 提交按钮 */}
|
||||
<TouchableOpacity
|
||||
style={s.submitButton}
|
||||
onPress={handleSubmit}
|
||||
disabled={loading}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={s.submitButtonText}>
|
||||
{loading ? '处理中...' : mode === 'login' ? '登录' : '注册'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* 用户协议 */}
|
||||
{mode === 'register' && (
|
||||
<Text style={s.agreementText}>
|
||||
注册即表示同意 <Text style={s.agreementLink}>用户协议</Text>和
|
||||
<Text style={s.agreementLink}>隐私政策</Text>
|
||||
</Text>
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user