import { Colors } from '@/constants/Colors'; import { getTabBarBottomPadding } from '@/constants/TabBar'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useColorScheme } from '@/hooks/useColorScheme'; import { api } from '@/services/api'; import { DEFAULT_MEMBER_NAME, fetchMyProfile, logout } from '@/store/userSlice'; import { Ionicons } from '@expo/vector-icons'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; import { useFocusEffect } from '@react-navigation/native'; import type { Href } from 'expo-router'; import { router } from 'expo-router'; import React, { useEffect, useMemo, useState } from 'react'; import { Alert, Image, SafeAreaView, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; export default function PersonalScreen() { const dispatch = useAppDispatch(); const insets = useSafeAreaInsets(); const tabBarHeight = useBottomTabBarHeight(); const bottomPadding = useMemo(() => { // 统一的页面底部留白:TabBar 高度 + TabBar 与底部的额外间距 + 安全区底部 return getTabBarBottomPadding(tabBarHeight) + (insets?.bottom ?? 0); }, [tabBarHeight, insets?.bottom]); const [notificationEnabled, setNotificationEnabled] = useState(true); const colorScheme = useColorScheme(); const colors = Colors[colorScheme ?? 'light']; const theme = (colorScheme ?? 'light') as 'light' | 'dark'; const colorTokens = Colors[theme]; const DEFAULT_AVATAR_URL = 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/avatar/avatarGirl01.jpeg'; type UserProfile = { name?: string; email?: string; gender?: 'male' | 'female' | ''; age?: string; weightKg?: number; heightCm?: number; avatarUri?: string | null; }; const userProfileFromRedux = useAppSelector((s) => s.user.profile); const [profile, setProfile] = useState({}); const load = async () => { try { const [p, o] = await Promise.all([ AsyncStorage.getItem('@user_profile'), AsyncStorage.getItem('@user_personal_info'), ]); let next: UserProfile = {}; if (o) { try { const parsed = JSON.parse(o); next = { ...next, age: parsed?.age ? String(parsed.age) : undefined, gender: parsed?.gender || '', heightCm: parsed?.height ? parseFloat(parsed.height) : undefined, weightKg: parsed?.weight ? parseFloat(parsed.weight) : undefined, }; } catch { } } if (p) { try { next = { ...next, ...JSON.parse(p) }; } catch { } } setProfile(next); } catch (e) { console.warn('加载用户资料失败', e); } }; useEffect(() => { load(); }, []); useFocusEffect(React.useCallback(() => { // 聚焦时只拉后端,避免与本地 load 循环触发 dispatch(fetchMyProfile()); return () => { }; }, [dispatch])); useEffect(() => { const r = userProfileFromRedux as any; if (!r) return; setProfile((prev) => { const next = { ...prev } as any; const nameNext = (r.name && String(r.name)) || prev.name; const genderNext = (r.gender === 'male' || r.gender === 'female') ? r.gender : (prev.gender ?? ''); const avatarUriNext = typeof r.avatar === 'string' && (r.avatar.startsWith('http') || r.avatar.startsWith('data:')) ? r.avatar : prev.avatarUri; let changed = false; if (next.name !== nameNext) { next.name = nameNext; changed = true; } if (next.gender !== genderNext) { next.gender = genderNext; changed = true; } if (next.avatarUri !== avatarUriNext) { next.avatarUri = avatarUriNext; changed = true; } return changed ? next : prev; }); }, [userProfileFromRedux]); const formatHeight = () => { if (profile.heightCm == null) return '--'; return `${Math.round(profile.heightCm)}cm`; }; const formatWeight = () => { if (profile.weightKg == null) return '--'; return `${round(profile.weightKg, 1)}kg`; }; const formatAge = () => (profile.age ? `${profile.age}岁` : '--'); const round = (n: number, d = 0) => { const p = Math.pow(10, d); return Math.round(n * p) / p; }; const handleResetOnboarding = () => { Alert.alert( '重置引导', '确定要重置引导流程吗?下次启动应用时将重新显示引导页面。', [ { text: '取消', style: 'cancel', }, { text: '确定', style: 'destructive', onPress: async () => { try { await AsyncStorage.multiRemove(['@onboarding_completed', '@user_personal_info']); Alert.alert('成功', '引导状态已重置,请重启应用查看效果。'); } catch (error) { console.error('重置引导状态失败:', error); Alert.alert('错误', '重置失败,请稍后重试。'); } }, }, ] ); }; const displayName = (profile.name && profile.name.trim()) ? profile.name : DEFAULT_MEMBER_NAME; const handleDeleteAccount = () => { Alert.alert( '确认注销帐号', '此操作不可恢复,将删除您的帐号及相关数据。确定继续吗?', [ { text: '取消', style: 'cancel' }, { text: '确认注销', style: 'destructive', onPress: async () => { try { await api.delete('/api/users/delete-account'); await AsyncStorage.multiRemove(['@user_personal_info']); await dispatch(logout()).unwrap(); Alert.alert('帐号已注销', '您的帐号已成功注销'); router.replace('/auth/login'); } catch (err: any) { const message = err?.message || '注销失败,请稍后重试'; Alert.alert('注销失败', message); } }, }, ], { cancelable: true } ); }; const UserInfoSection = () => ( {/* 头像 */} {/* 用户信息 */} {displayName} {/* 编辑按钮 */} router.push('/profile/edit')}> 编辑 ); const StatsSection = () => ( {formatHeight()} 身高 {formatWeight()} 体重 {formatAge()} 年龄 ); const MenuSection = ({ title, items }: { title: string; items: any[] }) => ( {title} {items.map((item, index) => ( {item.title} {item.type === 'switch' ? ( ) : ( )} ))} ); // 动态创建样式 const dynamicStyles = { editButton: { backgroundColor: colors.primary, paddingHorizontal: 20, paddingVertical: 10, borderRadius: 20, }, editButtonText: { color: '#192126', fontSize: 14, fontWeight: '600' as const, }, statValue: { fontSize: 18, fontWeight: 'bold' as const, color: colors.primary, marginBottom: 4, }, floatingButton: { width: 56, height: 56, borderRadius: 28, backgroundColor: colors.primary, alignItems: 'center' as const, justifyContent: 'center' as const, shadowColor: colors.primary, shadowOffset: { width: 0, height: 4, }, shadowOpacity: 0.3, shadowRadius: 8, elevation: 8, }, }; const accountItems = [ { icon: 'flag-outline', iconBg: '#E8F5E8', iconColor: '#4ADE80', title: '目标管理', onPress: () => router.push('/profile/goals' as Href), }, { icon: 'stats-chart-outline', iconBg: '#E8F5E8', iconColor: '#4ADE80', title: '训练进度', }, ]; const notificationItems = [ { icon: 'notifications-outline', iconBg: '#E8F5E8', iconColor: '#4ADE80', title: '消息推送', type: 'switch', }, ]; const otherItems = [ { icon: 'mail-outline', iconBg: '#E8F5E8', iconColor: '#4ADE80', title: '联系我们', }, { icon: 'shield-checkmark-outline', iconBg: '#E8F5E8', iconColor: '#4ADE80', title: '隐私政策', }, { icon: 'settings-outline', iconBg: '#E8F5E8', iconColor: '#4ADE80', title: '设置', }, ]; const securityItems = [ { icon: 'trash-outline', iconBg: '#FFE8E8', iconColor: '#FF4444', title: '注销帐号', onPress: handleDeleteAccount, }, ]; const developerItems = [ { icon: 'refresh-outline', iconBg: '#FFE8E8', iconColor: '#FF4444', title: '重置引导流程', onPress: handleResetOnboarding, }, ]; return ( {/* 底部浮动按钮 */} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F5F5F5', // 浅灰色背景 }, safeArea: { flex: 1, }, scrollView: { flex: 1, paddingHorizontal: 20, backgroundColor: '#F5F5F5', }, // 用户信息区域 userInfoCard: { borderRadius: 16, marginBottom: 20, backgroundColor: '#FFFFFF', shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.08, shadowRadius: 6, elevation: 3, }, userInfoContainer: { flexDirection: 'row', alignItems: 'center', padding: 20, }, avatarContainer: { marginRight: 15, }, avatar: { width: 80, height: 80, borderRadius: 40, backgroundColor: '#E8D4F0', alignItems: 'center', justifyContent: 'center', overflow: 'hidden', }, avatarContent: { width: '100%', height: '100%', alignItems: 'center', justifyContent: 'center', }, avatarIcon: { alignItems: 'center', justifyContent: 'center', }, avatarFace: { width: 25, height: 25, borderRadius: 12.5, backgroundColor: '#D4A574', marginBottom: 5, }, avatarBody: { width: 30, height: 20, borderRadius: 15, backgroundColor: '#F4C842', }, userDetails: { flex: 1, }, userName: { fontSize: 18, fontWeight: 'bold', color: '#192126', marginBottom: 4, }, // 统计信息区域 statsContainer: { flexDirection: 'row', justifyContent: 'space-between', backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, statItem: { alignItems: 'center', flex: 1, }, statLabel: { fontSize: 12, color: '#687076', }, // 菜单区域 menuSection: { marginBottom: 20, backgroundColor: '#FFFFFF', padding: 16, borderRadius: 16, }, sectionTitle: { fontSize: 20, fontWeight: '800', color: '#192126', marginBottom: 12, paddingHorizontal: 4, }, menuItem: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 16, paddingHorizontal: 16, borderRadius: 12, marginBottom: 8, }, menuItemLeft: { flexDirection: 'row', alignItems: 'center', flex: 1, }, menuIcon: { width: 36, height: 36, borderRadius: 8, alignItems: 'center', justifyContent: 'center', marginRight: 12, }, menuItemText: { fontSize: 16, color: '#192126', flex: 1, }, switch: { transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }], }, // 浮动按钮 floatingButtonContainer: { position: 'absolute', bottom: 30, left: 0, right: 0, alignItems: 'center', pointerEvents: 'box-none', }, });