import { StatusBar } from 'expo-status-bar'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ActivityIndicator, Alert, KeyboardAvoidingView, Platform, ScrollView, StyleSheet, TextInput, TouchableOpacity, View, } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { authService } from '../../services'; import { spacing, fontSizes, useAppColors, type AppColors } from '../../theme'; import { Text, SimpleHeader } from '../../components/common'; import { useResponsive, useResponsiveSpacing } from '../../hooks'; import { useAuthStore } from '../../stores'; import type { DeletionStatusDTO } from '../../types/dto'; import * as hrefs from '../../navigation/hrefs'; const CONTENT_MAX_WIDTH = 720; function createAccountDeletionStyles(colors: AppColors) { return StyleSheet.create({ container: { flex: 1, backgroundColor: colors.background.paper, }, loadingWrap: { flex: 1, alignItems: 'center', justifyContent: 'center', }, keyboardView: { flex: 1, }, scrollContent: { paddingVertical: spacing.lg, }, content: { maxWidth: CONTENT_MAX_WIDTH, alignSelf: 'center', width: '100%', }, // 顶部叙述区:左对齐、有呼吸感 heroSection: { paddingHorizontal: spacing['2xl'], paddingTop: spacing.md, paddingBottom: spacing.xl, }, heroEyebrow: { fontSize: 13, color: colors.text.hint, letterSpacing: 1, marginBottom: spacing.sm, }, heroTitle: { fontSize: 24, fontWeight: '700', color: colors.text.primary, lineHeight: 32, marginBottom: spacing.sm, }, heroDesc: { fontSize: 15, color: colors.text.secondary, lineHeight: 22, }, // 倒计时区:圆环 + 数字 + 文案,替代"大数字 + 标签"卡片 countdownSection: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: spacing['2xl'], paddingVertical: spacing.lg, }, countdownRing: { width: 88, height: 88, borderRadius: 44, borderWidth: 4, borderColor: colors.warning.main, alignItems: 'center', justifyContent: 'center', marginRight: spacing.lg, }, countdownNumber: { fontSize: 32, fontWeight: '700', color: colors.warning.dark, lineHeight: 36, }, countdownUnit: { fontSize: 11, color: colors.text.secondary, marginTop: -2, }, countdownTextWrap: { flex: 1, }, countdownTitle: { fontSize: 16, fontWeight: '600', color: colors.text.primary, marginBottom: 4, }, countdownDesc: { fontSize: 13, color: colors.text.secondary, lineHeight: 19, }, // 引导文 + 列表(无背景、无圆角,自然排版) guideSection: { paddingHorizontal: spacing['2xl'], paddingTop: spacing.lg, }, guideLabel: { fontSize: 13, color: colors.text.secondary, fontWeight: '500', marginBottom: spacing.md, }, guideItem: { flexDirection: 'row', alignItems: 'flex-start', paddingVertical: 10, }, guideIcon: { marginRight: spacing.md, marginTop: 2, }, guideTextWrap: { flex: 1, }, guideTitle: { fontSize: 15, fontWeight: '500', color: colors.text.primary, marginBottom: 2, }, guideDesc: { fontSize: 13, color: colors.text.secondary, lineHeight: 19, }, guideDivider: { height: StyleSheet.hairlineWidth, backgroundColor: colors.divider, marginLeft: spacing['2xl'] + 22 + spacing.md, }, // 注意事项(无填色,仅左侧色条) noticeSection: { flexDirection: 'row', alignItems: 'flex-start', paddingHorizontal: spacing['2xl'], paddingTop: spacing.lg, paddingBottom: spacing.md, }, noticeBar: { width: 3, alignSelf: 'stretch', backgroundColor: colors.error.main, borderRadius: 2, marginRight: spacing.md, }, noticeTextWrap: { flex: 1, }, noticeTitle: { fontSize: 14, fontWeight: '600', color: colors.error.main, marginBottom: 4, }, noticeDesc: { fontSize: 13, color: colors.text.secondary, lineHeight: 20, }, // 表单区(与 AccountSecurity 风格一致:分节标题 + 输入框) formSection: { marginTop: spacing.lg, }, sectionHeader: { paddingHorizontal: spacing['2xl'], marginBottom: spacing.sm, marginTop: spacing.sm, }, sectionTitle: { fontWeight: '600', fontSize: fontSizes.sm, color: colors.text.secondary, textTransform: 'uppercase', letterSpacing: 0.5, }, // 输入框 inputWrapper: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.background.default, borderRadius: 14, paddingHorizontal: spacing.lg, height: 56, marginHorizontal: spacing['2xl'], marginBottom: spacing.md, }, inputIcon: { marginRight: spacing.sm, }, input: { flex: 1, color: colors.text.primary, fontSize: fontSizes.md, height: 56, }, eyeButton: { padding: 4, marginLeft: 4, }, // 按钮行:次按钮在左、危险按钮在右,与全站保持一致 buttonRow: { flexDirection: 'row', gap: spacing.md, marginTop: spacing.lg, marginHorizontal: spacing['2xl'], }, secondaryButton: { flex: 1, height: 56, borderRadius: 14, backgroundColor: 'transparent', alignItems: 'center', justifyContent: 'center', borderWidth: 1.5, borderColor: colors.divider, }, secondaryButtonText: { color: colors.text.primary, fontSize: fontSizes.md, fontWeight: '600', }, dangerButton: { flex: 1, height: 56, borderRadius: 14, backgroundColor: colors.error.main, alignItems: 'center', justifyContent: 'center', }, dangerButtonText: { color: colors.text.inverse, fontSize: fontSizes.md, fontWeight: '600', }, buttonDisabled: { opacity: 0.5, }, primaryButton: { height: 56, borderRadius: 14, backgroundColor: colors.primary.main, alignItems: 'center', justifyContent: 'center', marginHorizontal: spacing['2xl'], marginTop: spacing.md, }, primaryButtonText: { color: colors.text.inverse, fontSize: fontSizes.md, fontWeight: '600', }, // 页脚:联系客服 footer: { alignItems: 'center', marginTop: spacing.xl, paddingHorizontal: spacing['2xl'], }, footerText: { fontSize: 13, color: colors.text.hint, lineHeight: 20, textAlign: 'center', }, footerLink: { color: colors.primary.main, fontWeight: '500', }, }); } export const AccountDeletionScreen: React.FC = () => { const colors = useAppColors(); const styles = useMemo(() => createAccountDeletionStyles(colors), [colors]); const router = useRouter(); const { isMobile } = useResponsive(); const insets = useSafeAreaInsets(); const logout = useAuthStore((s) => s.logout); const [status, setStatus] = useState(null); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md; const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 }); const loadStatus = useCallback(async () => { try { const result = await authService.getDeletionStatus(); setStatus(result); } catch (error) { console.error('加载注销状态失败:', error); } finally { setLoading(false); } }, []); useEffect(() => { loadStatus(); }, [loadStatus]); const handleRequestDeletion = useCallback(async () => { if (!password.trim()) { Alert.alert('提示', '请输入密码确认'); return; } Alert.alert( '确认注销', '注销后,您的账号将在90天后永久删除。在此期间,您可以通过重新登录来取消注销。确定要继续吗?', [ { text: '取消', style: 'cancel' }, { text: '确定注销', style: 'destructive', onPress: async () => { setSubmitting(true); try { const result = await authService.requestAccountDeletion(password); if (result) { setStatus(result); setPassword(''); Alert.alert('已申请注销', '您的账号将在90天后永久删除。在此期间登录即可取消注销。'); await logout(); router.replace(hrefs.hrefAuthLogin()); } else { Alert.alert('失败', '密码错误或申请失败,请重试'); } } catch (error) { console.error('申请注销失败:', error); Alert.alert('失败', '申请注销失败,请稍后重试'); } finally { setSubmitting(false); } }, }, ] ); }, [password, logout, router]); const handleCancelDeletion = useCallback(async () => { Alert.alert( '取消注销', '确定要取消注销申请吗?您的账号将恢复正常使用。', [ { text: '继续注销', style: 'cancel' }, { text: '确定取消', onPress: async () => { setSubmitting(true); try { const ok = await authService.cancelAccountDeletion(); if (ok) { setStatus({ is_pending_deletion: false }); Alert.alert('已取消', '注销申请已取消,您的账号已恢复正常使用'); } else { Alert.alert('失败', '取消注销失败,请稍后重试'); } } catch (error) { console.error('取消注销失败:', error); Alert.alert('失败', '取消注销失败,请稍后重试'); } finally { setSubmitting(false); } }, }, ] ); }, []); if (loading) { return ( ); } // 待注销状态 if (status?.is_pending_deletion) { return ( router.back()} /> {/* 顶部叙述 */} ACCOUNT · 注销申请中 我们将在倒计时结束后清除你的账号 在此期间,你随时可以撤销申请,账号会立即恢复正常使用。 {/* 倒计时 */} {status.cool_down_days || 90} 距离永久删除 再次登录或点击下方按钮可立即取消注销。 {/* 取消按钮 */} {submitting ? ( ) : ( 撤销注销申请 )} {/* 页脚 */} 如有疑虑可联系 客服 获取帮助 ); } // 正常状态:申请注销 return ( router.back()} /> {/* 顶部叙述 */} ACCOUNT · 注销 在离开之前,我们想让你知道这些 注销并非立即生效,提交后你有 90 天的冷静期,反悔了随时可以回来。 {/* 注销影响 - 列表式(无背景) */} 注销后会发生什么 个人资料会被清除 头像、昵称、简介等所有个人信息都将被永久删除。 历史内容会保留 你发布的帖子、评论将保留,但作者会显示为「已注销用户」。 关注关系被解绑 你关注的人、粉丝、收藏、点赞等社交关系会被一并清除。 90 天冷静期 期间重新登录即可撤销申请,账号会立刻恢复。 {/* 红色提示 - 左侧细线代替大色块 */} 这是不可恢复的操作 90 天后所有数据将被永久删除,届时无法通过任何方式找回。请确认你已备份好需要保留的内容。 {/* 密码确认 */} 身份验证 setShowPassword(!showPassword)} style={styles.eyeButton} > {/* 按钮行 */} router.back()} activeOpacity={0.8} > 再想想 {submitting ? ( ) : ( 确认申请注销 )} {/* 页脚 */} 遇到问题?可以联系 客服 我们会帮你处理 ); }; export default AccountDeletionScreen;