import { AnimatedNumber } from '@/components/AnimatedNumber'; import { FloatingFoodOverlay } from '@/components/FloatingFoodOverlay'; import { ROUTES } from '@/constants/Routes'; import { NutritionSummary } from '@/services/dietRecords'; import { NutritionGoals, calculateRemainingCalories } from '@/utils/nutrition'; import { Ionicons } from '@expo/vector-icons'; import dayjs from 'dayjs'; import { router } from 'expo-router'; import React, { useMemo, useState } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { RadarCategory, RadarChart } from './RadarChart'; export type NutritionRadarCardProps = { nutritionSummary: NutritionSummary | null; /** 营养目标 */ nutritionGoals?: NutritionGoals; /** 基础代谢消耗的卡路里 */ burnedCalories?: number; /** 基础代谢率 */ basalMetabolism?: number; /** 运动消耗卡路里 */ activeCalories?: number; /** 动画重置令牌 */ resetToken?: number; /** 餐次点击回调 */ onMealPress?: (mealType: 'breakfast' | 'lunch' | 'dinner' | 'snack') => void; }; // 营养维度定义 const NUTRITION_DIMENSIONS: RadarCategory[] = [ { key: 'calories', label: '热量' }, { key: 'protein', label: '蛋白质' }, { key: 'carbohydrate', label: '碳水' }, { key: 'fat', label: '脂肪' }, { key: 'fiber', label: '纤维' }, { key: 'sodium', label: '钠' }, ]; export function NutritionRadarCard({ nutritionSummary, nutritionGoals, burnedCalories = 1618, basalMetabolism, activeCalories, resetToken, onMealPress }: NutritionRadarCardProps) { const [currentMealType, setCurrentMealType] = useState<'breakfast' | 'lunch' | 'dinner' | 'snack'>('breakfast'); const [showFoodOverlay, setShowFoodOverlay] = useState(false); const radarValues = useMemo(() => { // 基于动态计算的营养目标或默认推荐值 const recommendations = { calories: nutritionGoals?.calories ?? 2000, // 卡路里 protein: nutritionGoals?.proteinGoal ?? 50, // 蛋白质(g) carbohydrate: nutritionGoals?.carbsGoal ?? 300, // 碳水化合物(g) fat: nutritionGoals?.fatGoal ?? 65, // 脂肪(g) fiber: nutritionGoals?.fiberGoal ?? 25, // 膳食纤维(g) sodium: nutritionGoals?.sodiumGoal ?? 2300, // 钠(mg) }; if (!nutritionSummary) return [0, 0, 0, 0, 0, 0]; // 检查每个营养素是否有实际值,没有则返回0 const calories = nutritionSummary.totalCalories || 0; const protein = nutritionSummary.totalProtein || 0; const carbohydrate = nutritionSummary.totalCarbohydrate || 0; const fat = nutritionSummary.totalFat || 0; const fiber = nutritionSummary.totalFiber || 0; const sodium = nutritionSummary.totalSodium || 0; return [ calories > 0 ? Math.min(5, (calories / recommendations.calories) * 5) : 0, protein > 0 ? Math.min(5, (protein / recommendations.protein) * 5) : 0, carbohydrate > 0 ? Math.min(5, (carbohydrate / recommendations.carbohydrate) * 5) : 0, fat > 0 ? Math.min(5, (fat / recommendations.fat) * 5) : 0, fiber > 0 ? Math.min(5, (fiber / recommendations.fiber) * 5) : 0, sodium > 0 ? Math.min(5, Math.max(0, 5 - (sodium / recommendations.sodium) * 5)) : 0, // 钠含量越低越好 ]; }, [nutritionSummary, nutritionGoals]); const nutritionStats = useMemo(() => { return [ { label: '热量', value: nutritionSummary ? `${Math.round(nutritionSummary.totalCalories)} 千卡` : '0 千卡', color: '#FF6B6B' }, { label: '蛋白质', value: nutritionSummary ? `${nutritionSummary.totalProtein.toFixed(1)} g` : '0.0 g', color: '#4ECDC4' }, { label: '碳水', value: nutritionSummary ? `${nutritionSummary.totalCarbohydrate.toFixed(1)} g` : '0.0 g', color: '#45B7D1' }, { label: '脂肪', value: nutritionSummary ? `${nutritionSummary.totalFat.toFixed(1)} g` : '0.0 g', color: '#FFA07A' }, { label: '纤维', value: nutritionSummary ? `${nutritionSummary.totalFiber.toFixed(1)} g` : '0.0 g', color: '#98D8C8' }, { label: '钠', value: nutritionSummary ? `${Math.round(nutritionSummary.totalSodium)} mg` : '0 mg', color: '#F7DC6F' }, ]; }, [nutritionSummary]); // 计算还能吃的卡路里 const consumedCalories = nutritionSummary?.totalCalories || 0; // 使用分离的代谢和运动数据,如果没有提供则从burnedCalories推算 const effectiveBasalMetabolism = basalMetabolism ?? (burnedCalories * 0.7); // 假设70%是基础代谢 const effectiveActiveCalories = activeCalories ?? (burnedCalories * 0.3); // 假设30%是运动消耗 const remainingCalories = calculateRemainingCalories({ basalMetabolism: effectiveBasalMetabolism, activeCalories: effectiveActiveCalories, consumedCalories, }); const handleNavigateToRecords = () => { router.push(ROUTES.NUTRITION_RECORDS); }; const handleAddFood = () => { setShowFoodOverlay(true); }; return ( 营养摄入分析 更新: {dayjs(nutritionSummary?.updatedAt).format('MM-DD HH:mm')} {nutritionStats.map((stat, index) => ( {stat.label} {stat.value} ))} {/* 卡路里计算区域 */} 还能吃 Math.round(v).toString()} /> 千卡 = 基代 Math.round(v).toString()} /> + 运动 Math.round(v).toString()} /> - 饮食 Math.round(v).toString()} /> {/* 食物添加悬浮窗 */} setShowFoodOverlay(false)} mealType={currentMealType} /> ); } const styles = StyleSheet.create({ card: { backgroundColor: '#FFFFFF', borderRadius: 22, padding: 18, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.1, shadowRadius: 3.84, elevation: 5, marginTop: 12 }, cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, }, cardTitle: { fontSize: 14, color: '#192126', }, cardRightContainer: { flexDirection: 'row', alignItems: 'center', gap: 4, }, cardSubtitle: { fontSize: 10, color: '#9AA3AE', fontWeight: '600', }, contentContainer: { flexDirection: 'row', alignItems: 'center', }, radarContainer: { alignItems: 'center', marginRight: 6, }, statsContainer: { flex: 1, flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', marginLeft: 4 }, statItem: { flexDirection: 'row', alignItems: 'center', width: '48%', marginBottom: 16, }, statDot: { width: 8, height: 8, borderRadius: 4, marginRight: 8, }, statLabel: { fontSize: 10, color: '#9AA3AE', flex: 1, }, statValue: { fontSize: 12, color: '#192126', fontWeight: '600', }, // 卡路里相关样式 calorieSection: { }, calorieTitleContainer: { flexDirection: 'row', alignItems: 'center', }, calorieIcon: { fontSize: 16, marginRight: 8, }, calorieTitle: { fontSize: 16, fontWeight: '800', color: '#192126', }, calorieContent: { }, calorieSubtitle: { fontSize: 10, color: '#64748B', fontWeight: '600', marginRight: 4, }, calculationRow: { flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', gap: 4, }, mainValue: { fontSize: 14, fontWeight: '600', color: '#192126', }, calculationText: { fontSize: 10, fontWeight: '600', color: '#64748B', }, calculationItem: { flexDirection: 'row', alignItems: 'center', gap: 2, }, calculationLabel: { fontSize: 9, color: '#64748B', fontWeight: '500', }, calculationValue: { fontSize: 11, fontWeight: '700', color: '#192126', }, remainingCaloriesContainer: { flexDirection: 'row', alignItems: 'baseline', gap: 2, }, calorieUnit: { fontSize: 10, color: '#64748B', fontWeight: '500', }, mealsContainer: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 12, borderTopWidth: 1, borderTopColor: '#F1F5F9', }, mealItem: { alignItems: 'center', flex: 1, }, mealIconContainer: { position: 'relative', marginBottom: 6, }, mealEmoji: { fontSize: 24, }, addButton: { width: 16, height: 16, borderRadius: 8, backgroundColor: '#e5e8ecff', marginLeft: 8, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, }, mealName: { fontSize: 10, color: '#64748B', fontWeight: '600', }, });