2026-03-09 21:29:03 +08:00
|
|
|
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
|
|
|
|
import {
|
|
|
|
|
|
View,
|
|
|
|
|
|
StyleSheet,
|
|
|
|
|
|
TextInput,
|
|
|
|
|
|
TouchableOpacity,
|
|
|
|
|
|
ActivityIndicator,
|
|
|
|
|
|
ScrollView,
|
|
|
|
|
|
} from 'react-native';
|
2026-03-21 03:22:28 +08:00
|
|
|
|
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
2026-03-25 05:16:54 +08:00
|
|
|
|
import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme';
|
2026-03-29 02:34:13 +08:00
|
|
|
|
import { Text } from '../../components/common';
|
|
|
|
|
|
import { useResponsive, useResponsiveSpacing } from '../../hooks';
|
|
|
|
|
|
|
|
|
|
|
|
// 内容最大宽度
|
|
|
|
|
|
const CONTENT_MAX_WIDTH = 720;
|
2026-04-04 08:01:45 +08:00
|
|
|
|
|
|
|
|
|
|
// 胡萝卜橙主题色
|
|
|
|
|
|
const THEME_COLORS = {
|
|
|
|
|
|
primary: '#FF6B35',
|
|
|
|
|
|
primaryLight: '#FF8C5A',
|
|
|
|
|
|
primaryDark: '#E55A2B',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
import { authService, resolveAuthApiError } from '../../services/authService';
|
|
|
|
|
|
import { showPrompt } from '../../services/promptService';
|
|
|
|
|
|
import { useAuthStore } from '../../stores';
|
|
|
|
|
|
|
|
|
|
|
|
export const AccountSecurityScreen: React.FC = () => {
|
2026-03-25 05:16:54 +08:00
|
|
|
|
const colors = useAppColors();
|
|
|
|
|
|
const styles = useMemo(() => createAccountSecurityStyles(colors), [colors]);
|
2026-03-21 03:22:28 +08:00
|
|
|
|
const { isWideScreen, isMobile } = useResponsive();
|
|
|
|
|
|
const insets = useSafeAreaInsets();
|
2026-03-09 21:29:03 +08:00
|
|
|
|
const currentUser = useAuthStore((state) => state.currentUser);
|
|
|
|
|
|
const fetchCurrentUser = useAuthStore((state) => state.fetchCurrentUser);
|
2026-03-21 03:22:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 底部间距,避免被 TabBar 遮挡
|
|
|
|
|
|
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
const [email, setEmail] = useState(currentUser?.email || '');
|
|
|
|
|
|
const [verificationCode, setVerificationCode] = useState('');
|
|
|
|
|
|
const [sendingCode, setSendingCode] = useState(false);
|
|
|
|
|
|
const [verifyingEmail, setVerifyingEmail] = useState(false);
|
|
|
|
|
|
const [countdown, setCountdown] = useState(0);
|
|
|
|
|
|
|
|
|
|
|
|
const [oldPassword, setOldPassword] = useState('');
|
|
|
|
|
|
const [newPassword, setNewPassword] = useState('');
|
|
|
|
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
|
|
|
|
const [changePasswordCode, setChangePasswordCode] = useState('');
|
|
|
|
|
|
const [sendingChangePwdCode, setSendingChangePwdCode] = useState(false);
|
|
|
|
|
|
const [changePwdCountdown, setChangePwdCountdown] = useState(0);
|
|
|
|
|
|
const [updatingPassword, setUpdatingPassword] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (currentUser?.email) {
|
|
|
|
|
|
setEmail(currentUser.email);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [currentUser?.email]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (countdown <= 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const timer = setInterval(() => {
|
|
|
|
|
|
setCountdown((prev) => (prev <= 1 ? 0 : prev - 1));
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
return () => clearInterval(timer);
|
|
|
|
|
|
}, [countdown]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (changePwdCountdown <= 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const timer = setInterval(() => {
|
|
|
|
|
|
setChangePwdCountdown((prev) => (prev <= 1 ? 0 : prev - 1));
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
return () => clearInterval(timer);
|
|
|
|
|
|
}, [changePwdCountdown]);
|
|
|
|
|
|
|
|
|
|
|
|
const isEmailVerified = !!currentUser?.email_verified;
|
|
|
|
|
|
const emailStatusText = useMemo(() => {
|
|
|
|
|
|
if (!currentUser?.email) {
|
|
|
|
|
|
return '未绑定邮箱';
|
|
|
|
|
|
}
|
|
|
|
|
|
return isEmailVerified ? '已验证' : '未验证';
|
|
|
|
|
|
}, [currentUser?.email, isEmailVerified]);
|
|
|
|
|
|
|
|
|
|
|
|
const validateEmail = (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
|
|
|
|
|
|
|
|
|
|
const handleSendCode = async () => {
|
|
|
|
|
|
const targetEmail = email.trim();
|
|
|
|
|
|
if (!validateEmail(targetEmail)) {
|
|
|
|
|
|
showPrompt({ title: '提示', message: '请输入正确的邮箱地址', type: 'warning' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (countdown > 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
setSendingCode(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const ok = await authService.sendCurrentUserEmailVerifyCode(targetEmail);
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
setCountdown(60);
|
|
|
|
|
|
showPrompt({ title: '发送成功', message: '验证码已发送,请查收邮箱', type: 'success' });
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
showPrompt({ title: '发送失败', message: resolveAuthApiError(error, '验证码发送失败'), type: 'error' });
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSendingCode(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleVerifyEmail = async () => {
|
|
|
|
|
|
const targetEmail = email.trim();
|
|
|
|
|
|
if (!validateEmail(targetEmail)) {
|
|
|
|
|
|
showPrompt({ title: '提示', message: '请输入正确的邮箱地址', type: 'warning' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!verificationCode.trim()) {
|
|
|
|
|
|
showPrompt({ title: '提示', message: '请输入验证码', type: 'warning' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setVerifyingEmail(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const ok = await authService.verifyCurrentUserEmail({
|
|
|
|
|
|
email: targetEmail,
|
|
|
|
|
|
verification_code: verificationCode.trim(),
|
|
|
|
|
|
});
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
setVerificationCode('');
|
|
|
|
|
|
await fetchCurrentUser();
|
|
|
|
|
|
showPrompt({ title: '验证成功', message: '邮箱已完成验证', type: 'success' });
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
showPrompt({ title: '验证失败', message: resolveAuthApiError(error, '邮箱验证失败'), type: 'error' });
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setVerifyingEmail(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleChangePassword = async () => {
|
|
|
|
|
|
if (!oldPassword) {
|
|
|
|
|
|
showPrompt({ title: '提示', message: '请输入当前密码', type: 'warning' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!changePasswordCode.trim()) {
|
|
|
|
|
|
showPrompt({ title: '提示', message: '请输入邮箱验证码', type: 'warning' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-07 00:12:03 +08:00
|
|
|
|
if (newPassword.length < 8) {
|
|
|
|
|
|
showPrompt({ title: '提示', message: '新密码至少 8 位', type: 'warning' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!/[A-Z]/.test(newPassword)) {
|
|
|
|
|
|
showPrompt({ title: '提示', message: '新密码必须包含大写字母', type: 'warning' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!/[a-z]/.test(newPassword)) {
|
|
|
|
|
|
showPrompt({ title: '提示', message: '新密码必须包含小写字母', type: 'warning' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!/[0-9]/.test(newPassword)) {
|
|
|
|
|
|
showPrompt({ title: '提示', message: '新密码必须包含数字', type: 'warning' });
|
2026-03-09 21:29:03 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (newPassword !== confirmPassword) {
|
|
|
|
|
|
showPrompt({ title: '提示', message: '两次输入的新密码不一致', type: 'warning' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
setUpdatingPassword(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const ok = await authService.changePassword(oldPassword, newPassword, changePasswordCode.trim());
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
setOldPassword('');
|
|
|
|
|
|
setNewPassword('');
|
|
|
|
|
|
setConfirmPassword('');
|
|
|
|
|
|
setChangePasswordCode('');
|
|
|
|
|
|
showPrompt({ title: '修改成功', message: '密码已更新', type: 'success' });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showPrompt({ title: '修改失败', message: '请检查当前密码后重试', type: 'error' });
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
showPrompt({ title: '修改失败', message: resolveAuthApiError(error, '请稍后重试'), type: 'error' });
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setUpdatingPassword(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSendChangePasswordCode = async () => {
|
|
|
|
|
|
if (changePwdCountdown > 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
setSendingChangePwdCode(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const ok = await authService.sendChangePasswordCode();
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
setChangePwdCountdown(60);
|
|
|
|
|
|
showPrompt({ title: '发送成功', message: '改密验证码已发送到你的邮箱', type: 'success' });
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
showPrompt({ title: '发送失败', message: resolveAuthApiError(error, '验证码发送失败'), type: 'error' });
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSendingChangePwdCode(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-04 08:01:45 +08:00
|
|
|
|
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-04-04 08:01:45 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<SafeAreaView style={styles.container} edges={['bottom']}>
|
|
|
|
|
|
<ScrollView contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding }]}>
|
|
|
|
|
|
<View style={styles.content}>
|
|
|
|
|
|
{/* 邮箱验证分组 */}
|
|
|
|
|
|
<View style={styles.groupHeader}>
|
|
|
|
|
|
<Text variant="caption" style={styles.groupTitle}>
|
|
|
|
|
|
邮箱验证
|
|
|
|
|
|
</Text>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</View>
|
2026-04-04 08:01:45 +08:00
|
|
|
|
<View style={styles.card}>
|
|
|
|
|
|
{/* 状态显示 */}
|
|
|
|
|
|
<View style={styles.statusRow}>
|
|
|
|
|
|
<Text style={styles.statusLabel}>当前状态</Text>
|
|
|
|
|
|
<View style={[styles.statusBadge, isEmailVerified ? styles.statusVerified : styles.statusUnverified]}>
|
|
|
|
|
|
<Text style={[styles.statusText, isEmailVerified ? styles.statusVerifiedText : styles.statusUnverifiedText]}>
|
|
|
|
|
|
{emailStatusText}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-04-04 08:01:45 +08:00
|
|
|
|
{/* 邮箱输入框 */}
|
|
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
|
|
<MaterialCommunityIcons name="email-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
|
2026-03-09 21:29:03 +08:00
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
2026-04-04 08:01:45 +08:00
|
|
|
|
placeholder="请输入邮箱"
|
2026-03-09 21:29:03 +08:00
|
|
|
|
placeholderTextColor={colors.text.hint}
|
2026-04-04 08:01:45 +08:00
|
|
|
|
value={email}
|
|
|
|
|
|
onChangeText={setEmail}
|
|
|
|
|
|
autoCapitalize="none"
|
|
|
|
|
|
keyboardType="email-address"
|
2026-03-09 21:29:03 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
2026-04-04 08:01:45 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 验证码行 */}
|
|
|
|
|
|
<View style={styles.codeRow}>
|
|
|
|
|
|
<View style={[styles.inputWrapper, styles.codeInput]}>
|
|
|
|
|
|
<MaterialCommunityIcons name="shield-key-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="验证码"
|
|
|
|
|
|
placeholderTextColor={colors.text.hint}
|
|
|
|
|
|
value={verificationCode}
|
|
|
|
|
|
onChangeText={setVerificationCode}
|
|
|
|
|
|
keyboardType="number-pad"
|
|
|
|
|
|
maxLength={6}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
style={[styles.sendCodeButton, (sendingCode || countdown > 0) && styles.buttonDisabled]}
|
|
|
|
|
|
onPress={handleSendCode}
|
|
|
|
|
|
disabled={sendingCode || countdown > 0}
|
|
|
|
|
|
>
|
|
|
|
|
|
{sendingCode ? (
|
|
|
|
|
|
<ActivityIndicator size="small" color="#fff" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Text style={styles.sendCodeButtonText}>{countdown > 0 ? `${countdown}s` : '发送验证码'}</Text>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 验证按钮 */}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
<TouchableOpacity
|
2026-04-04 08:01:45 +08:00
|
|
|
|
style={[styles.primaryButton, verifyingEmail && styles.buttonDisabled]}
|
|
|
|
|
|
onPress={handleVerifyEmail}
|
|
|
|
|
|
disabled={verifyingEmail}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
>
|
2026-04-04 08:01:45 +08:00
|
|
|
|
{verifyingEmail ? (
|
|
|
|
|
|
<ActivityIndicator size="small" color="#fff" />
|
2026-03-09 21:29:03 +08:00
|
|
|
|
) : (
|
2026-04-04 08:01:45 +08:00
|
|
|
|
<Text style={styles.primaryButtonText}>验证邮箱</Text>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
2026-04-04 08:01:45 +08:00
|
|
|
|
{/* 修改密码分组 */}
|
|
|
|
|
|
<View style={styles.groupHeader}>
|
|
|
|
|
|
<Text variant="caption" style={styles.groupTitle}>
|
|
|
|
|
|
修改密码
|
|
|
|
|
|
</Text>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</View>
|
2026-04-04 08:01:45 +08:00
|
|
|
|
<View style={styles.card}>
|
|
|
|
|
|
{/* 当前密码 */}
|
|
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
|
|
<MaterialCommunityIcons name="lock-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="当前密码"
|
|
|
|
|
|
placeholderTextColor={colors.text.hint}
|
|
|
|
|
|
value={oldPassword}
|
|
|
|
|
|
onChangeText={setOldPassword}
|
|
|
|
|
|
secureTextEntry
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 新密码 */}
|
|
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
|
|
<MaterialCommunityIcons name="lock-plus-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
|
2026-03-09 21:29:03 +08:00
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
2026-04-07 00:12:03 +08:00
|
|
|
|
placeholder="新密码(至少8位,含大小写字母和数字)"
|
2026-03-09 21:29:03 +08:00
|
|
|
|
placeholderTextColor={colors.text.hint}
|
2026-04-04 08:01:45 +08:00
|
|
|
|
value={newPassword}
|
|
|
|
|
|
onChangeText={setNewPassword}
|
|
|
|
|
|
secureTextEntry
|
2026-03-09 21:29:03 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
2026-04-04 08:01:45 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 确认新密码 */}
|
|
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
|
|
<MaterialCommunityIcons name="lock-check-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="确认新密码"
|
|
|
|
|
|
placeholderTextColor={colors.text.hint}
|
|
|
|
|
|
value={confirmPassword}
|
|
|
|
|
|
onChangeText={setConfirmPassword}
|
|
|
|
|
|
secureTextEntry
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 验证码行 */}
|
|
|
|
|
|
<View style={styles.codeRow}>
|
|
|
|
|
|
<View style={[styles.inputWrapper, styles.codeInput]}>
|
|
|
|
|
|
<MaterialCommunityIcons name="shield-key-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
|
|
|
|
|
|
<TextInput
|
|
|
|
|
|
style={styles.input}
|
|
|
|
|
|
placeholder="邮箱验证码"
|
|
|
|
|
|
placeholderTextColor={colors.text.hint}
|
|
|
|
|
|
value={changePasswordCode}
|
|
|
|
|
|
onChangeText={setChangePasswordCode}
|
|
|
|
|
|
keyboardType="number-pad"
|
|
|
|
|
|
maxLength={6}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
style={[styles.sendCodeButton, (sendingChangePwdCode || changePwdCountdown > 0) && styles.buttonDisabled]}
|
|
|
|
|
|
onPress={handleSendChangePasswordCode}
|
|
|
|
|
|
disabled={sendingChangePwdCode || changePwdCountdown > 0}
|
|
|
|
|
|
>
|
|
|
|
|
|
{sendingChangePwdCode ? (
|
|
|
|
|
|
<ActivityIndicator size="small" color="#fff" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Text style={styles.sendCodeButtonText}>
|
|
|
|
|
|
{changePwdCountdown > 0 ? `${changePwdCountdown}s` : '发送验证码'}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 更新密码按钮 */}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
<TouchableOpacity
|
2026-04-04 08:01:45 +08:00
|
|
|
|
style={[styles.primaryButton, updatingPassword && styles.buttonDisabled]}
|
|
|
|
|
|
onPress={handleChangePassword}
|
|
|
|
|
|
disabled={updatingPassword}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
>
|
2026-04-04 08:01:45 +08:00
|
|
|
|
{updatingPassword ? (
|
|
|
|
|
|
<ActivityIndicator size="small" color="#fff" />
|
2026-03-09 21:29:03 +08:00
|
|
|
|
) : (
|
2026-04-04 08:01:45 +08:00
|
|
|
|
<Text style={styles.primaryButtonText}>更新密码</Text>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
2026-03-29 02:34:13 +08:00
|
|
|
|
</ScrollView>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</SafeAreaView>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-25 05:16:54 +08:00
|
|
|
|
function createAccountSecurityStyles(colors: AppColors) {
|
|
|
|
|
|
return StyleSheet.create({
|
|
|
|
|
|
container: {
|
|
|
|
|
|
flex: 1,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
backgroundColor: '#fff',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
scrollContent: {
|
2026-04-02 17:55:56 +08:00
|
|
|
|
paddingVertical: spacing.lg,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
2026-04-04 08:01:45 +08:00
|
|
|
|
content: {
|
|
|
|
|
|
maxWidth: CONTENT_MAX_WIDTH,
|
|
|
|
|
|
alignSelf: 'center',
|
|
|
|
|
|
width: '100%',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
2026-04-04 08:01:45 +08:00
|
|
|
|
// 分组标题
|
|
|
|
|
|
groupHeader: {
|
2026-04-02 17:55:56 +08:00
|
|
|
|
paddingHorizontal: spacing['2xl'],
|
2026-03-25 05:16:54 +08:00
|
|
|
|
marginBottom: spacing.sm,
|
2026-04-02 17:55:56 +08:00
|
|
|
|
marginTop: spacing.sm,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
2026-04-04 08:01:45 +08:00
|
|
|
|
groupTitle: {
|
2026-03-25 05:16:54 +08:00
|
|
|
|
fontWeight: '600',
|
2026-04-02 17:55:56 +08:00
|
|
|
|
fontSize: fontSizes.sm,
|
|
|
|
|
|
color: colors.text.secondary,
|
|
|
|
|
|
textTransform: 'uppercase',
|
|
|
|
|
|
letterSpacing: 0.5,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
2026-04-04 08:01:45 +08:00
|
|
|
|
// 卡片样式 - 统一
|
2026-03-25 05:16:54 +08:00
|
|
|
|
card: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
backgroundColor: '#F5F5F7',
|
|
|
|
|
|
borderRadius: 16,
|
|
|
|
|
|
padding: 6,
|
|
|
|
|
|
marginBottom: spacing['2xl'],
|
2026-04-02 17:55:56 +08:00
|
|
|
|
marginHorizontal: spacing['2xl'],
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
2026-04-04 08:01:45 +08:00
|
|
|
|
// 状态显示
|
2026-03-25 05:16:54 +08:00
|
|
|
|
statusRow: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
alignItems: 'center',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
paddingHorizontal: spacing.md,
|
|
|
|
|
|
paddingVertical: spacing.md,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
marginBottom: spacing.md,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
|
|
borderRadius: 12,
|
|
|
|
|
|
},
|
|
|
|
|
|
statusLabel: {
|
|
|
|
|
|
fontSize: 15,
|
|
|
|
|
|
color: colors.text.secondary,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
statusBadge: {
|
|
|
|
|
|
paddingHorizontal: spacing.sm,
|
|
|
|
|
|
paddingVertical: 4,
|
|
|
|
|
|
borderRadius: borderRadius.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
statusVerified: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
backgroundColor: '#E8F5E9',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
statusUnverified: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
backgroundColor: '#FFF3E0',
|
|
|
|
|
|
},
|
|
|
|
|
|
statusText: {
|
|
|
|
|
|
fontSize: 13,
|
|
|
|
|
|
fontWeight: '600',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
statusVerifiedText: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
color: '#2E7D32',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
statusUnverifiedText: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
color: '#E65100',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
2026-04-04 08:01:45 +08:00
|
|
|
|
// 输入框样式 - 统一
|
2026-03-25 05:16:54 +08:00
|
|
|
|
inputWrapper: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
backgroundColor: '#fff',
|
2026-04-02 17:55:56 +08:00
|
|
|
|
borderRadius: 14,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
paddingHorizontal: spacing.md,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
height: 56,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
marginBottom: spacing.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
inputIcon: {
|
|
|
|
|
|
marginRight: spacing.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
input: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
color: colors.text.primary,
|
|
|
|
|
|
fontSize: fontSizes.md,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
height: 56,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
2026-04-04 08:01:45 +08:00
|
|
|
|
// 验证码行
|
2026-03-25 05:16:54 +08:00
|
|
|
|
codeRow: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
gap: spacing.sm,
|
|
|
|
|
|
marginBottom: spacing.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
codeInput: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
marginBottom: 0,
|
|
|
|
|
|
},
|
2026-04-04 08:01:45 +08:00
|
|
|
|
// 发送验证码按钮
|
2026-03-25 05:16:54 +08:00
|
|
|
|
sendCodeButton: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
height: 56,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
minWidth: 110,
|
2026-04-02 17:55:56 +08:00
|
|
|
|
borderRadius: 14,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
backgroundColor: THEME_COLORS.primary,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
paddingHorizontal: spacing.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
sendCodeButtonText: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
color: '#fff',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
fontSize: fontSizes.sm,
|
|
|
|
|
|
fontWeight: '600',
|
|
|
|
|
|
},
|
2026-04-04 08:01:45 +08:00
|
|
|
|
// 主按钮样式 - 统一
|
2026-03-25 05:16:54 +08:00
|
|
|
|
primaryButton: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
height: 56,
|
2026-04-02 17:55:56 +08:00
|
|
|
|
borderRadius: 14,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
backgroundColor: THEME_COLORS.primary,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
marginTop: spacing.xs,
|
|
|
|
|
|
},
|
|
|
|
|
|
primaryButtonText: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
color: '#fff',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
fontSize: fontSizes.md,
|
2026-04-02 17:55:56 +08:00
|
|
|
|
fontWeight: '600',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
buttonDisabled: {
|
|
|
|
|
|
opacity: 0.6,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
export default AccountSecurityScreen;
|