feat: 支持食物库接口

This commit is contained in:
richarjiang
2025-08-29 09:41:05 +08:00
parent c15a9176f4
commit 8d567fb4cb
14 changed files with 1349 additions and 234 deletions

View File

@@ -14,9 +14,9 @@ import { getTabBarBottomPadding } from '@/constants/TabBar';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { useBackgroundTasks } from '@/hooks/useBackgroundTasks';
import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords';
import { selectHealthDataByDate, setHealthData } from '@/store/healthSlice';
import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice';
import { fetchDailyNutritionData, selectNutritionSummaryByDate } from '@/store/nutritionSlice';
import { getMonthDaysZh, getTodayIndexInMonth } from '@/utils/date';
import { ensureHealthPermissions, fetchHealthDataForDate } from '@/utils/health';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
@@ -141,8 +141,8 @@ export default function ExploreScreen() {
// 用于触发动画重置的 token当日期或数据变化时更新
const [animToken, setAnimToken] = useState(0);
// 营养数据状态
const [nutritionSummary, setNutritionSummary] = useState<NutritionSummary | null>(null);
// 从 Redux 获取营养数据
const nutritionSummary = useAppSelector(selectNutritionSummaryByDate(currentSelectedDateString));
const { registerTask } = useBackgroundTasks();
// 心情相关状态
@@ -239,7 +239,6 @@ export default function ExploreScreen() {
// 加载营养数据
const loadNutritionData = async (targetDate?: Date) => {
try {
// 确定要查询的日期:优先使用传入的日期,否则使用当前选中索引对应的日期
let derivedDate: Date;
if (targetDate) {
@@ -249,23 +248,11 @@ export default function ExploreScreen() {
}
console.log('加载营养数据...', derivedDate);
const data = await getDietRecords({
startDate: dayjs(derivedDate).startOf('day').toISOString(),
endDate: dayjs(derivedDate).endOf('day').toISOString(),
});
if (data.records.length > 0) {
const summary = calculateNutritionSummary(data.records);
summary.updatedAt = data.records[0].updatedAt;
setNutritionSummary(summary);
} else {
setNutritionSummary(null);
}
console.log('营养数据加载完成:', data);
await dispatch(fetchDailyNutritionData(derivedDate));
console.log('营养数据加载完成');
} catch (error) {
console.error('营养数据加载失败:', error);
setNutritionSummary(null);
}
};

View File

@@ -1,9 +1,13 @@
import type { FoodItem } from '@/components/model/food/FoodDetailModal';
import { FoodDetailModal } from '@/components/model/food/FoodDetailModal';
import { useFoodLibrary, useFoodSearch } from '@/hooks/useFoodLibrary';
import type { FoodItem, MealType, SelectedFoodItem } from '@/types/food';
import { Ionicons } from '@expo/vector-icons';
import { Image } from 'expo-image';
import { useLocalSearchParams, useRouter } from 'expo-router';
import React, { useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
Modal,
SafeAreaView,
ScrollView,
StatusBar,
@@ -11,89 +15,10 @@ import {
Text,
TextInput,
TouchableOpacity,
View,
View
} from 'react-native';
// 食物分类类型
export interface FoodCategory {
id: string;
name: string;
foods: FoodItem[];
}
// 模拟食物数据
const FOOD_DATA: FoodCategory[] = [
{
id: 'common',
name: '常见',
foods: [
{ id: '1', name: '无糖美式咖啡', emoji: '☕', calories: 1, unit: '100克' },
{ id: '2', name: '荷包蛋(油煎)', emoji: '🍳', calories: 195, unit: '100克' },
{ id: '3', name: '鸡蛋', emoji: '🥚', calories: 139, unit: '100克' },
{ id: '4', name: '香蕉', emoji: '🍌', calories: 93, unit: '100克' },
{ id: '5', name: '猕猴桃', emoji: '🥝', calories: 61, unit: '100克' },
{ id: '6', name: '苹果', emoji: '🍎', calories: 53, unit: '100克' },
{ id: '7', name: '草莓', emoji: '🍓', calories: 32, unit: '100克' },
{ id: '8', name: '蛋烧麦', emoji: '🥟', calories: 157, unit: '100克' },
{ id: '9', name: '米饭', emoji: '🍚', calories: 116, unit: '100克' },
{ id: '10', name: '鲜玉米', emoji: '🌽', calories: 112, unit: '100克' },
]
},
{
id: 'custom',
name: '自定义',
foods: []
},
{
id: 'favorite',
name: '收藏',
foods: []
},
{
id: 'fruits',
name: '水果蔬菜',
foods: [
{ id: '11', name: '苹果', emoji: '🍎', calories: 53, unit: '100克' },
{ id: '12', name: '香蕉', emoji: '🍌', calories: 93, unit: '100克' },
{ id: '13', name: '草莓', emoji: '🍓', calories: 32, unit: '100克' },
{ id: '14', name: '猕猴桃', emoji: '🥝', calories: 61, unit: '100克' },
]
},
{
id: 'meat',
name: '肉蛋奶',
foods: [
{ id: '15', name: '鸡蛋', emoji: '🥚', calories: 139, unit: '100克' },
{ id: '16', name: '荷包蛋(油煎)', emoji: '🍳', calories: 195, unit: '100克' },
]
},
{
id: 'beans',
name: '豆类坚果',
foods: []
},
{
id: 'drinks',
name: '零食饮料',
foods: [
{ id: '17', name: '无糖美式咖啡', emoji: '☕', calories: 1, unit: '100克' },
]
},
{
id: 'staple',
name: '主食',
foods: [
{ id: '18', name: '米饭', emoji: '🍚', calories: 116, unit: '100克' },
{ id: '19', name: '鲜玉米', emoji: '🌽', calories: 112, unit: '100克' },
{ id: '20', name: '蛋烧麦', emoji: '🥟', calories: 157, unit: '100克' },
]
},
{
id: 'vegetables',
name: '菜肴',
foods: []
}
];
// 餐次映射保持不变
// 餐次映射
const MEAL_TYPE_MAP = {
@@ -106,20 +31,53 @@ const MEAL_TYPE_MAP = {
export default function FoodLibraryScreen() {
const router = useRouter();
const params = useLocalSearchParams<{ mealType?: string }>();
const mealType = (params.mealType as 'breakfast' | 'lunch' | 'dinner' | 'snack') || 'breakfast';
const mealType = (params.mealType as MealType) || 'breakfast';
// Redux hooks
const { categories, loading, error, clearErrors } = useFoodLibrary();
const { searchResults, searchLoading, search, clearResults } = useFoodSearch();
// 本地状态
const [selectedCategoryId, setSelectedCategoryId] = useState('common');
const [searchText, setSearchText] = useState('');
const [selectedFood, setSelectedFood] = useState<FoodItem | null>(null);
const [showFoodDetail, setShowFoodDetail] = useState(false);
const [selectedFoodItems, setSelectedFoodItems] = useState<SelectedFoodItem[]>([]);
const [showMealSelector, setShowMealSelector] = useState(false);
const [currentMealType, setCurrentMealType] = useState<MealType>(mealType);
// 获取当前选中的分类
const selectedCategory = FOOD_DATA.find(cat => cat.id === selectedCategoryId);
const selectedCategory = categories.find(cat => cat.id === selectedCategoryId);
// 过滤食物列表
const filteredFoods = selectedCategory?.foods.filter(food =>
food.name.toLowerCase().includes(searchText.toLowerCase())
) || [];
// 过滤食物列表 - 优先显示搜索结果
const filteredFoods = useMemo(() => {
if (searchText.trim() && searchResults.length > 0) {
return searchResults;
}
if (selectedCategory) {
console.log('selectedCategory', selectedCategory);
return selectedCategory.foods
}
return [];
}, [searchText, searchResults, selectedCategory]);
// 处理搜索
useEffect(() => {
const timeoutId = setTimeout(() => {
if (searchText.trim()) {
search(searchText);
} else {
clearResults();
}
}, 300); // 防抖
return () => clearTimeout(timeoutId);
}, [searchText, search, clearResults]);
// 处理食物选择 - 显示详情弹窗
const handleSelectFood = (food: FoodItem) => {
@@ -136,18 +94,53 @@ export default function FoodLibraryScreen() {
// 处理食物保存
const handleSaveFood = (food: FoodItem, amount: number, unit: string) => {
// 这里可以处理保存逻辑,比如添加到营养记录
console.log('保存食物:', food, amount, unit);
// 计算实际热量
const actualCalories = Math.round((food.calories * amount) / 100);
// 创建新的选择项目
const newSelectedItem: SelectedFoodItem = {
id: `${food.id}_${Date.now()}`, // 使用时间戳确保唯一性
food,
amount,
unit,
calories: actualCalories
};
// 添加到已选择列表
setSelectedFoodItems(prev => [...prev, newSelectedItem]);
console.log('保存食物:', food, amount, unit, '热量:', actualCalories);
setShowFoodDetail(false);
router.back(); // 返回上一页
};
// 移除已选择的食物
const handleRemoveSelectedFood = (itemId: string) => {
setSelectedFoodItems(prev => prev.filter(item => item.id !== itemId));
};
// 计算总热量
const totalCalories = selectedFoodItems.reduce((sum, item) => sum + item.calories, 0);
// 关闭详情弹窗
const handleCloseFoodDetail = () => {
setShowFoodDetail(false);
setSelectedFood(null);
};
// 处理餐次选择
const handleMealTypeSelect = (selectedMealType: MealType) => {
setCurrentMealType(selectedMealType);
setShowMealSelector(false);
};
// 餐次选择选项
const mealOptions = [
{ key: 'breakfast' as const, label: '早餐', color: '#FF6B35' },
{ key: 'lunch' as const, label: '午餐', color: '#4CAF50' },
{ key: 'dinner' as const, label: '晚餐', color: '#2196F3' },
{ key: 'snack' as const, label: '加餐', color: '#FF9800' },
];
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor="#F8F9FA" />
@@ -158,20 +151,8 @@ export default function FoodLibraryScreen() {
<Ionicons name="chevron-back" size={24} color="#333" />
</TouchableOpacity>
<Text style={styles.headerTitle}></Text>
<TouchableOpacity
style={styles.customButton}
onPress={() => {
const testFood: FoodItem = {
id: 'test',
name: '测试食物',
emoji: '🍎',
calories: 100,
unit: '100克'
};
handleSelectFood(testFood);
}}
>
<Text style={styles.customButtonText}></Text>
<TouchableOpacity style={styles.customButton}>
<Text style={styles.customButtonText}></Text>
</TouchableOpacity>
</View>
@@ -189,76 +170,223 @@ export default function FoodLibraryScreen() {
{/* 主要内容区域 - 卡片样式 */}
<View style={styles.mainContentCard}>
<View style={styles.mainContent}>
{/* 左侧分类导航 */}
<View style={styles.categoryContainer}>
<ScrollView showsVerticalScrollIndicator={false}>
{FOOD_DATA.map((category) => (
<TouchableOpacity
key={category.id}
style={[
styles.categoryItem,
selectedCategoryId === category.id && styles.categoryItemActive
]}
onPress={() => setSelectedCategoryId(category.id)}
>
<Text style={[
styles.categoryText,
selectedCategoryId === category.id && styles.categoryTextActive
]}>
{category.name}
</Text>
</TouchableOpacity>
))}
</ScrollView>
{loading && categories.length === 0 ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#4CAF50" />
<Text style={styles.loadingText}>...</Text>
</View>
{/* 右侧食物列表 */}
<View style={styles.foodContainer}>
<ScrollView showsVerticalScrollIndicator={false}>
{filteredFoods.map((food) => (
<View key={food.id} style={styles.foodItem}>
<View style={styles.foodInfo}>
<Text style={styles.foodEmoji}>{food.emoji}</Text>
<View style={styles.foodDetails}>
<Text style={styles.foodName}>{food.name}</Text>
<Text style={styles.foodCalories}>
{food.calories}/{food.unit}
</Text>
</View>
</View>
) : error ? (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{error}</Text>
<TouchableOpacity
style={styles.retryButton}
onPress={() => {
clearErrors();
// 这里可以重新加载数据
}}
>
<Text style={styles.retryButtonText}></Text>
</TouchableOpacity>
</View>
) : (
<View style={styles.mainContent}>
{/* 左侧分类导航 */}
<View style={styles.categoryContainer}>
<ScrollView showsVerticalScrollIndicator={false}>
{categories.map((category) => (
<TouchableOpacity
style={styles.addButton}
onPress={() => handleSelectFood(food)}
key={category.id}
style={[
styles.categoryItem,
selectedCategoryId === category.id && styles.categoryItemActive
]}
onPress={() => {
setSelectedCategoryId(category.id);
// 切换分类时清除搜索
if (searchText) {
setSearchText('');
clearResults();
}
}}
>
<Ionicons name="add" size={20} color="#666" />
<Text style={[
styles.categoryText,
selectedCategoryId === category.id && styles.categoryTextActive
]}>
{category.name}
</Text>
</TouchableOpacity>
</View>
))}
))}
</ScrollView>
</View>
{filteredFoods.length === 0 && (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}></Text>
{/* 右侧食物列表 */}
<View style={styles.foodContainer}>
{searchLoading ? (
<View style={styles.searchLoadingContainer}>
<ActivityIndicator size="small" color="#4CAF50" />
<Text style={styles.searchLoadingText}>...</Text>
</View>
) : (
<ScrollView showsVerticalScrollIndicator={false}>
{filteredFoods.map((food) => (
<View key={food.id} style={styles.foodItem}>
<View style={styles.foodInfo}>
{food.imageUrl ? <Image
style={styles.foodImage}
source={{ uri: food.imageUrl }}
cachePolicy={'memory-disk'}
/> : <Text style={styles.foodEmoji}>{food.emoji || '🍽️'}</Text>}
<View style={styles.foodDetails}>
<Text style={styles.foodName}>{food.name}</Text>
<Text style={styles.foodCalories}>
{food.calories}/{food.unit}
</Text>
</View>
</View>
<TouchableOpacity
style={styles.addButton}
onPress={() => handleSelectFood(food)}
>
<Ionicons name="add" size={20} color="#666" />
</TouchableOpacity>
</View>
))}
{filteredFoods.length === 0 && !searchLoading && (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>
{searchText ? '未找到相关食物' : '暂无食物数据'}
</Text>
{searchText && (
<Text style={styles.emptySubText}>
使
</Text>
)}
</View>
)}
</ScrollView>
)}
</ScrollView>
</View>
</View>
</View>
)}
</View>
{/* 已选择食物列表 */}
{selectedFoodItems.length > 0 && (
<View style={styles.selectedFoodsContainer}>
<View style={styles.selectedFoodsHeader}>
<Text style={styles.selectedFoodsTitle}> ({selectedFoodItems.length})</Text>
<Text style={styles.totalCalories}>: {totalCalories}</Text>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.selectedFoodsList}
contentContainerStyle={styles.selectedFoodsContent}
>
{selectedFoodItems.map((item) => (
<View key={item.id} style={styles.selectedFoodItem}>
<TouchableOpacity
style={styles.removeButton}
onPress={() => handleRemoveSelectedFood(item.id)}
>
<Ionicons name="close-circle" size={16} color="#FF4444" />
</TouchableOpacity>
{item.food.imageUrl ? <Image
style={styles.selectedFoodImage}
source={{ uri: item.food.imageUrl }}
/> : <Text style={styles.selectedFoodEmoji}>{item.food.emoji}</Text>}
<Text style={styles.selectedFoodName} numberOfLines={1}>{item.food.name}</Text>
<Text style={styles.selectedFoodAmount}>{item.amount}{item.unit}</Text>
<Text style={styles.selectedFoodCalories}>{item.calories}</Text>
</View>
))}
</ScrollView>
</View>
)}
{/* 底部餐次选择和记录按钮 */}
<View style={styles.bottomContainer}>
<View style={styles.mealSelector}>
<View style={styles.mealIndicator} />
<Text style={styles.mealText}>{MEAL_TYPE_MAP[mealType]}</Text>
<TouchableOpacity
style={styles.mealSelector}
onPress={() => setShowMealSelector(true)}
>
<View style={[
styles.mealIndicator,
{ backgroundColor: mealOptions.find(option => option.key === currentMealType)?.color || '#FF6B35' }
]} />
<Text style={styles.mealText}>{MEAL_TYPE_MAP[currentMealType]}</Text>
<Ionicons name="chevron-down" size={16} color="#333" />
</View>
</TouchableOpacity>
<TouchableOpacity style={styles.recordButton}>
<Text style={styles.recordButtonText}></Text>
<TouchableOpacity
style={[
styles.recordButton,
selectedFoodItems.length === 0 && styles.recordButtonDisabled
]}
disabled={selectedFoodItems.length === 0}
onPress={() => {
// 这里可以处理记录逻辑
console.log('记录食物:', selectedFoodItems);
// 记录成功后可以清空选择列表或返回上一页
router.back();
}}
>
<Text style={[
styles.recordButtonText,
selectedFoodItems.length === 0 && styles.recordButtonTextDisabled
]}></Text>
</TouchableOpacity>
</View>
{/* 餐次选择弹窗 */}
<Modal
visible={showMealSelector}
transparent={true}
animationType="fade"
onRequestClose={() => setShowMealSelector(false)}
>
<View style={styles.mealSelectorOverlay}>
<TouchableOpacity
style={styles.mealSelectorBackdrop}
onPress={() => setShowMealSelector(false)}
/>
<View style={styles.mealSelectorModal}>
<View style={styles.mealSelectorHeader}>
<Text style={styles.mealSelectorTitle}></Text>
<TouchableOpacity onPress={() => setShowMealSelector(false)}>
<Ionicons name="close" size={24} color="#666" />
</TouchableOpacity>
</View>
<View style={styles.mealOptionsContainer}>
{mealOptions.map((option) => (
<TouchableOpacity
key={option.key}
style={[
styles.mealOption,
currentMealType === option.key && styles.mealOptionActive
]}
onPress={() => handleMealTypeSelect(option.key)}
>
<View style={[styles.mealOptionIndicator, { backgroundColor: option.color }]} />
<Text style={[
styles.mealOptionText,
currentMealType === option.key && styles.mealOptionTextActive
]}>
{option.label}
</Text>
{currentMealType === option.key && (
<Ionicons name="checkmark" size={20} color="#4CAF50" />
)}
</TouchableOpacity>
))}
</View>
</View>
</View>
</Modal>
{/* 食物详情弹窗 */}
<FoodDetailModal
visible={showFoodDetail}
@@ -386,18 +514,24 @@ const styles = StyleSheet.create({
alignItems: 'center',
flex: 1,
},
foodImage: {
width: 32,
height: 32,
},
foodEmoji: {
fontSize: 32,
marginRight: 12,
},
foodDetails: {
flex: 1,
marginLeft: 12
},
foodName: {
fontSize: 16,
color: '#333',
fontWeight: '500',
marginBottom: 2,
},
foodCalories: {
fontSize: 14,
@@ -421,6 +555,61 @@ const styles = StyleSheet.create({
fontSize: 16,
color: '#999',
},
emptySubText: {
fontSize: 14,
color: '#CCC',
marginTop: 8,
textAlign: 'center',
},
// 加载状态样式
loadingContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 60,
},
loadingText: {
fontSize: 16,
color: '#666',
marginTop: 12,
},
// 错误状态样式
errorContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 60,
paddingHorizontal: 20,
},
errorText: {
fontSize: 16,
color: '#FF4444',
textAlign: 'center',
marginBottom: 16,
},
retryButton: {
backgroundColor: '#4CAF50',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 20,
},
retryButtonText: {
fontSize: 14,
color: '#FFF',
fontWeight: '500',
},
// 搜索加载状态样式
searchLoadingContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 20,
},
searchLoadingText: {
fontSize: 14,
color: '#666',
marginLeft: 8,
},
bottomContainer: {
flexDirection: 'row',
alignItems: 'center',
@@ -462,4 +651,147 @@ const styles = StyleSheet.create({
color: '#FFF',
fontWeight: '500',
},
recordButtonDisabled: {
backgroundColor: '#CCC',
},
recordButtonTextDisabled: {
color: '#999',
},
// 已选择食物列表样式
selectedFoodsContainer: {
backgroundColor: '#FFF',
borderTopWidth: 1,
borderTopColor: '#E5E5E5',
paddingVertical: 12,
maxHeight: 140, // 限制最大高度
},
selectedFoodsHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
marginBottom: 8,
},
selectedFoodsTitle: {
fontSize: 14,
fontWeight: '600',
color: '#333',
},
totalCalories: {
fontSize: 14,
color: '#4CAF50',
fontWeight: '500',
},
selectedFoodsList: {
paddingHorizontal: 16,
},
selectedFoodsContent: {
paddingRight: 16,
},
selectedFoodItem: {
backgroundColor: '#F8F9FA',
borderRadius: 12,
padding: 8,
marginRight: 8,
minWidth: 80,
alignItems: 'center',
position: 'relative',
},
removeButton: {
position: 'absolute',
top: 4,
right: 4,
backgroundColor: '#FFF',
borderRadius: 8,
zIndex: 1,
},
selectedFoodImage: {
width: 20,
height: 20,
marginBottom: 4,
},
selectedFoodEmoji: {
fontSize: 20,
marginBottom: 4,
},
selectedFoodName: {
fontSize: 12,
color: '#333',
fontWeight: '500',
textAlign: 'center',
marginBottom: 2,
maxWidth: 64,
},
selectedFoodAmount: {
fontSize: 11,
color: '#666',
marginBottom: 2,
},
selectedFoodCalories: {
fontSize: 11,
color: '#4CAF50',
fontWeight: '500',
},
// 餐次选择弹窗样式
mealSelectorOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'flex-end',
},
mealSelectorBackdrop: {
flex: 1,
},
mealSelectorModal: {
backgroundColor: '#FFF',
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
paddingBottom: 34, // 为底部安全区域留出空间
},
mealSelectorHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingVertical: 16,
borderBottomWidth: 1,
borderBottomColor: '#E5E5E5',
},
mealSelectorTitle: {
fontSize: 18,
fontWeight: '600',
color: '#333',
},
mealOptionsContainer: {
paddingHorizontal: 20,
paddingVertical: 16,
},
mealOption: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 16,
paddingHorizontal: 16,
borderRadius: 12,
marginBottom: 8,
backgroundColor: '#F8F9FA',
},
mealOptionActive: {
backgroundColor: '#E8F5E8',
borderWidth: 1,
borderColor: '#4CAF50',
},
mealOptionIndicator: {
width: 12,
height: 12,
borderRadius: 6,
marginRight: 12,
},
mealOptionText: {
flex: 1,
fontSize: 16,
color: '#333',
fontWeight: '500',
},
mealOptionTextActive: {
color: '#4CAF50',
},
});

View File

@@ -1,12 +1,11 @@
import { DateSelector } from '@/components/DateSelector';
import { NutritionRecordCard } from '@/components/NutritionRecordCard';
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { DateSelector } from '@/components/DateSelector';
import { useColorScheme } from '@/hooks/useColorScheme';
import { DietRecord, deleteDietRecord, getDietRecords } from '@/services/dietRecords';
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, useRef, useState } from 'react';
import {
@@ -202,21 +201,14 @@ export default function NutritionRecordsScreen() {
const renderEmptyState = () => (
<View style={styles.emptyContainer}>
<View style={styles.emptyTimelineContainer}>
<View style={styles.emptyTimeline}>
<View style={[styles.emptyTimelineDot, { backgroundColor: colorTokens.primary }]}>
<Ionicons name="add-outline" size={16} color="#FFFFFF" />
</View>
</View>
<View style={styles.emptyContent}>
<Ionicons name="restaurant-outline" size={48} color={colorTokens.textSecondary} />
<Text style={[styles.emptyTitle, { color: colorTokens.text }]}>
{viewMode === 'daily' ? '今天还没有记录' : '暂无营养记录'}
</Text>
<Text style={[styles.emptySubtitle, { color: colorTokens.textSecondary }]}>
{viewMode === 'daily' ? '开始记录今日营养摄入' : '开始记录你的营养摄入吧'}
</Text>
</View>
<View style={styles.emptyContent}>
<Ionicons name="restaurant-outline" size={48} color={colorTokens.textSecondary} />
<Text style={[styles.emptyTitle, { color: colorTokens.text }]}>
{viewMode === 'daily' ? '今天还没有记录' : '暂无营养记录'}
</Text>
<Text style={[styles.emptySubtitle, { color: colorTokens.textSecondary }]}>
{viewMode === 'daily' ? '开始记录今日营养摄入' : '开始记录你的营养摄入吧'}
</Text>
</View>
</View>
);
@@ -389,33 +381,10 @@ const styles = StyleSheet.create({
paddingVertical: 60,
paddingHorizontal: 16,
},
emptyTimelineContainer: {
flexDirection: 'row',
emptyContent: {
alignItems: 'center',
maxWidth: 320,
},
emptyTimeline: {
width: 64,
alignItems: 'center',
paddingTop: 8,
},
emptyTimelineDot: {
width: 32,
height: 32,
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2,
},
emptyContent: {
flex: 1,
alignItems: 'center',
marginLeft: 16,
},
emptyTitle: {
fontSize: 18,
fontWeight: '700',