import { CalorieRingChart } from '@/components/CalorieRingChart'; import { DateSelector } from '@/components/DateSelector'; import { NutritionRecordCard } from '@/components/NutritionRecordCard'; import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useColorScheme } from '@/hooks/useColorScheme'; import { DietRecord, deleteDietRecord, getDietRecords } from '@/services/dietRecords'; import { selectHealthDataByDate } from '@/store/healthSlice'; import { fetchDailyNutritionData, selectNutritionSummaryByDate } from '@/store/nutritionSlice'; import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date'; import { Ionicons } from '@expo/vector-icons'; import dayjs from 'dayjs'; import { router } from 'expo-router'; import React, { useEffect, useState } from 'react'; import { ActivityIndicator, FlatList, RefreshControl, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; type ViewMode = 'daily' | 'all'; export default function NutritionRecordsScreen() { const theme = (useColorScheme() ?? 'light') as 'light' | 'dark'; const colorTokens = Colors[theme]; const dispatch = useAppDispatch(); // 日期相关状态 - 使用与统计页面相同的日期逻辑 const days = getMonthDaysZh(); const [selectedIndex, setSelectedIndex] = useState(getTodayIndexInMonth()); const monthTitle = getMonthTitleZh(); // 获取当前选中日期 const getCurrentSelectedDate = () => { return days[selectedIndex]?.date?.toDate() ?? new Date(); }; const currentSelectedDate = getCurrentSelectedDate(); const currentSelectedDateString = dayjs(currentSelectedDate).format('YYYY-MM-DD'); // 从 Redux 获取数据 const healthData = useAppSelector(selectHealthDataByDate(currentSelectedDateString)); const nutritionSummary = useAppSelector(selectNutritionSummaryByDate(currentSelectedDateString)); const userProfile = useAppSelector((state) => state.user.profile); // 视图模式:按天查看 vs 全部查看 const [viewMode, setViewMode] = useState('daily'); // 数据状态 const [records, setRecords] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [hasMoreData, setHasMoreData] = useState(true); const [page, setPage] = useState(1); // 加载记录数据 const loadRecords = async (isRefresh = false, loadMore = false) => { try { if (isRefresh) { setRefreshing(true); setPage(1); } else if (loadMore) { // 加载更多时不显示loading } else { setLoading(true); } const currentPage = isRefresh ? 1 : (loadMore ? page + 1 : 1); let startDate: string | undefined; let endDate: string | undefined; if (viewMode === 'daily') { // 按天查看时,获取选中日期的数据 startDate = days[selectedIndex]?.date.startOf('day').toISOString(); endDate = days[selectedIndex]?.date.endOf('day').toISOString(); } const data = await getDietRecords({ startDate, endDate, page: currentPage, limit: 10, }); if (isRefresh || currentPage === 1) { setRecords(data.records); } else { setRecords(prev => [...prev, ...data.records]); } setHasMoreData(data.records.length === 10); // 如果返回的记录数少于limit,说明没有更多数据 setPage(currentPage); } catch (error) { console.error('加载营养记录失败:', error); } finally { setLoading(false); setRefreshing(false); } }; // 当选中日期或视图模式变化时重新加载数据 useEffect(() => { loadRecords(); }, [selectedIndex, viewMode]); // 当选中日期变化时获取营养数据 useEffect(() => { if (viewMode === 'daily') { dispatch(fetchDailyNutritionData(currentSelectedDate)); } }, [selectedIndex, viewMode, currentSelectedDate, dispatch]); const onRefresh = () => { loadRecords(true); }; // 计算营养目标 const calculateNutritionGoals = () => { const weight = parseFloat(userProfile?.weight || '70'); // 默认70kg const height = parseFloat(userProfile?.height || '170'); // 默认170cm const age = userProfile?.birthDate ? dayjs().diff(dayjs(userProfile.birthDate), 'year') : 25; // 默认25岁 const isWoman = userProfile?.gender === 'female'; // 基础代谢率计算(Mifflin-St Jeor Equation) let bmr; if (isWoman) { bmr = 10 * weight + 6.25 * height - 5 * age - 161; } else { bmr = 10 * weight + 6.25 * height - 5 * age + 5; } // 总热量需求(假设轻度活动) const totalCalories = bmr * 1.375; // 计算营养素目标 const proteinGoal = weight * 1.6; // 1.6g/kg const fatGoal = totalCalories * 0.25 / 9; // 25%来自脂肪,9卡/克 const carbsGoal = (totalCalories - proteinGoal * 4 - fatGoal * 9) / 4; // 剩余来自碳水 return { proteinGoal: Math.round(proteinGoal * 10) / 10, fatGoal: Math.round(fatGoal * 10) / 10, carbsGoal: Math.round(carbsGoal * 10) / 10, }; }; const nutritionGoals = calculateNutritionGoals(); const loadMoreRecords = () => { if (hasMoreData && !loading && !refreshing) { loadRecords(false, true); } }; // 删除记录 const handleDeleteRecord = async (recordId: number) => { try { await deleteDietRecord(recordId); // 从本地状态中移除已删除的记录 setRecords(prev => prev.filter(record => record.id !== recordId)); } catch (error) { console.error('删除营养记录失败:', error); // 可以添加错误提示 } }; // 渲染视图模式切换器 const renderViewModeToggle = () => ( {monthTitle} setViewMode('daily')} > 按天查看 setViewMode('all')} > 全部记录 ); // 渲染日期选择器(仅在按天查看模式下显示) const renderDateSelector = () => { if (viewMode !== 'daily') return null; return ( setSelectedIndex(index)} showMonthTitle={false} disableFutureDates={true} /> ); }; const renderEmptyState = () => ( {viewMode === 'daily' ? '今天还没有记录' : '暂无营养记录'} {viewMode === 'daily' ? '开始记录今日营养摄入' : '开始记录你的营养摄入吧'} ); const renderRecord = ({ item, index }: { item: DietRecord; index: number }) => ( handleDeleteRecord(item.id)} /> ); const renderFooter = () => { if (!hasMoreData) { return ( 没有更多数据了 ); } if (viewMode === 'all' && records.length > 0) { return ( 加载更多 ); } return null; }; return ( router.back()} /> {renderViewModeToggle()} {renderDateSelector()} {/* Calorie Ring Chart */} {loading ? ( 加载中... ) : ( renderRecord({ item, index })} keyExtractor={(item) => item.id.toString()} contentContainerStyle={[ styles.listContainer, { paddingBottom: 40, paddingTop: 16 } ]} showsVerticalScrollIndicator={false} refreshControl={ } ListEmptyComponent={renderEmptyState} ListFooterComponent={renderFooter} onEndReached={viewMode === 'all' ? loadMoreRecords : undefined} onEndReachedThreshold={0.1} /> )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, viewModeContainer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12, marginBottom: 8, }, monthTitle: { fontSize: 22, fontWeight: '800', }, toggleContainer: { flexDirection: 'row', borderRadius: 20, padding: 2, }, toggleButton: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 18, minWidth: 80, alignItems: 'center', }, toggleText: { fontSize: 14, fontWeight: '600', }, daysContainer: { marginBottom: 12, }, daysScrollContainer: { paddingHorizontal: 16, paddingVertical: 8, }, dayPill: { width: 48, height: 48, borderRadius: 34, marginRight: 12, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 4, elevation: 3, }, dayNumber: { fontSize: 18, textAlign: 'center', }, dayLabel: { fontSize: 12, marginTop: 2, textAlign: 'center', }, addButton: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, loadingText: { marginTop: 12, fontSize: 16, fontWeight: '500', }, listContainer: { paddingHorizontal: 16, paddingTop: 8, }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingVertical: 60, paddingHorizontal: 16, }, emptyContent: { alignItems: 'center', maxWidth: 320, }, emptyTitle: { fontSize: 18, fontWeight: '700', marginTop: 16, marginBottom: 8, textAlign: 'center', }, emptySubtitle: { fontSize: 14, fontWeight: '500', textAlign: 'center', lineHeight: 20, }, footerContainer: { paddingVertical: 20, alignItems: 'center', }, footerText: { fontSize: 14, fontWeight: '500', }, loadMoreButton: { paddingVertical: 16, alignItems: 'center', }, loadMoreText: { fontSize: 16, fontWeight: '600', }, });