import { CreateCustomFoodModal, type CustomFoodData } from '@/components/model/food/CreateCustomFoodModal'; import { FoodDetailModal } from '@/components/model/food/FoodDetailModal'; import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; import { DEFAULT_IMAGE_FOOD } from '@/constants/Image'; import { useAppDispatch } from '@/hooks/redux'; import { useFoodLibrary, useFoodSearch } from '@/hooks/useFoodLibrary'; import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding'; import { addDietRecord, type CreateDietRecordDto } from '@/services/dietRecords'; import { foodLibraryApi, type CreateCustomFoodDto } from '@/services/foodLibraryApi'; import { fetchDailyNutritionData } from '@/store/nutritionSlice'; 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, { useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, Alert, Modal, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native'; // 餐次映射保持不变 // 餐次映射 const MEAL_TYPE_MAP = { breakfast: '早餐', lunch: '午餐', dinner: '晚餐', snack: '加餐' }; export default function FoodLibraryScreen() { const safeAreaTop = useSafeAreaTop() const router = useRouter(); const params = useLocalSearchParams<{ mealType?: string }>(); const mealType = (params.mealType as MealType) || 'breakfast'; // Redux hooks const dispatch = useAppDispatch(); const { categories, loading, error, clearErrors, loadFoodLibrary } = useFoodLibrary(); const { searchResults, searchLoading, search, clearResults } = useFoodSearch(); // 本地状态 const [selectedCategoryId, setSelectedCategoryId] = useState('common'); const [searchText, setSearchText] = useState(''); const [selectedFood, setSelectedFood] = useState(null); const [showFoodDetail, setShowFoodDetail] = useState(false); const [selectedFoodItems, setSelectedFoodItems] = useState([]); const [showMealSelector, setShowMealSelector] = useState(false); const [currentMealType, setCurrentMealType] = useState(mealType); const [isRecording, setIsRecording] = useState(false); const [showCreateCustomFood, setShowCreateCustomFood] = useState(false); // 获取当前选中的分类 const selectedCategory = categories.find(cat => cat.id === selectedCategoryId); // 过滤食物列表 - 优先显示搜索结果 const filteredFoods = useMemo(() => { if (searchText.trim() && searchResults.length > 0) { return searchResults; } if (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) => { console.log('选择食物:', food); setSelectedFood(food); setShowFoodDetail(true); console.log('设置弹窗状态:', { showFoodDetail: true, selectedFood: food, foodName: food.name, foodId: food.id }); }; // 处理食物保存 const handleSaveFood = (food: FoodItem, amount: number, unit: string) => { // 计算实际热量 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); }; // 移除已选择的食物 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 handleDeleteFood = async (foodId: string) => { try { await foodLibraryApi.deleteCustomFood(Number(foodId)); // 删除成功后重新加载食物库数据 await loadFoodLibrary(); // 关闭弹窗 handleCloseFoodDetail(); } catch (error) { console.error('删除食物失败:', error); Alert.alert('删除失败', '删除食物时发生错误,请稍后重试'); } }; // 处理饮食记录 const handleRecordDiet = async () => { if (selectedFoodItems.length === 0) return; setIsRecording(true); try { // 逐个记录选中的食物 for (const item of selectedFoodItems) { const dietRecordData: CreateDietRecordDto = { mealType: currentMealType, foodName: item.food.name, foodDescription: item.food.description, portionDescription: `${item.amount}${item.unit}`, estimatedCalories: item.calories, proteinGrams: item.food.protein ? Number(((item.food.protein * item.amount) / 100).toFixed(2)) : undefined, carbohydrateGrams: item.food.carbohydrate ? Number(((item.food.carbohydrate * item.amount) / 100).toFixed(2)) : undefined, fatGrams: item.food.fat ? Number(((item.food.fat * item.amount) / 100).toFixed(2)) : undefined, fiberGrams: item.food.fiber ? Number(((item.food.fiber * item.amount) / 100).toFixed(2)) : undefined, sugarGrams: item.food.sugar ? Number(((item.food.sugar * item.amount) / 100).toFixed(2)) : undefined, sodiumMg: item.food.sodium ? Number(((item.food.sodium * item.amount) / 100).toFixed(2)) : undefined, additionalNutrition: item.food.additionalNutrition, source: 'manual', mealTime: new Date().toISOString(), imageUrl: item.food.imageUrl, }; await addDietRecord(dietRecordData); } // 记录成功后,刷新当天的营养数据 const today = new Date(); await dispatch(fetchDailyNutritionData(today)); // 清空选择列表并返回 setSelectedFoodItems([]); router.back(); } catch (error) { console.error('记录饮食失败:', error); // 这里可以显示错误提示 } finally { setIsRecording(false); } }; // 处理餐次选择 const handleMealTypeSelect = (selectedMealType: MealType) => { setCurrentMealType(selectedMealType); setShowMealSelector(false); }; // 处理创建自定义食物 const handleCreateCustomFood = () => { setShowCreateCustomFood(true); }; // 处理保存自定义食物 const handleSaveCustomFood = async (customFoodData: CustomFoodData) => { try { // 转换数据格式以匹配API要求 const createData: CreateCustomFoodDto = { name: customFoodData.name, caloriesPer100g: customFoodData.calories, proteinPer100g: customFoodData.protein, carbohydratePer100g: customFoodData.carbohydrate, fatPer100g: customFoodData.fat, imageUrl: customFoodData.imageUrl, }; // 调用API创建自定义食物 const createdFood = await foodLibraryApi.createCustomFood(createData); // 需要拉取一遍最新的食物列表 await loadFoodLibrary(); // 创建FoodItem对象 const customFoodItem: FoodItem = { id: createdFood.id.toString(), name: createdFood.name, calories: createdFood.caloriesPer100g || 0, unit: 'g', description: createdFood.description || `自定义食物 - ${createdFood.name}`, imageUrl: createdFood.imageUrl, protein: createdFood.proteinPer100g, fat: createdFood.fatPer100g, carbohydrate: createdFood.carbohydratePer100g, }; // 添加到选择列表中 const newSelectedItem: SelectedFoodItem = { id: createdFood.id.toString(), food: customFoodItem, amount: customFoodData.defaultAmount, unit: 'g', calories: Math.round((customFoodItem.calories * customFoodData.defaultAmount) / 100) }; setSelectedFoodItems(prev => [...prev, newSelectedItem]); } catch (error) { console.error('创建自定义食物失败:', error); Alert.alert('创建失败', '创建自定义食物时发生错误,请稍后重试'); } }; // 关闭自定义食物弹窗 const handleCloseCreateCustomFood = () => { setShowCreateCustomFood(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 ( {/* 头部 */} router.back()} variant="elevated" right={ 自定义 } /> {/* 搜索框 */} {/* 主要内容区域 - 卡片样式 */} {loading && categories.length === 0 ? ( 加载食物库中... ) : error ? ( {error} { clearErrors(); // 这里可以重新加载数据 }} > 重试 ) : ( {/* 左侧分类导航 */} {categories.map((category) => ( { setSelectedCategoryId(category.id); // 切换分类时清除搜索 if (searchText) { setSearchText(''); clearResults(); } }} > {category.name} ))} {/* 右侧食物列表 */} {searchLoading ? ( 搜索中... ) : ( {filteredFoods.map((food) => ( {food.name} {food.calories}千卡/{food.unit} handleSelectFood(food)} > ))} {filteredFoods.length === 0 && !searchLoading && ( {searchText ? '未找到相关食物' : '暂无食物数据'} {searchText && ( 尝试使用其他关键词搜索 )} )} )} )} {/* 已选择食物列表 */} {selectedFoodItems.length > 0 && ( 已选择食物 ({selectedFoodItems.length}) 总热量: {totalCalories}千卡 {selectedFoodItems.map((item) => ( handleRemoveSelectedFood(item.id)} > {item.food.imageUrl ? : {item.food.emoji}} {item.food.name} {item.amount}{item.unit} {item.calories}千卡 ))} )} {/* 底部餐次选择和记录按钮 */} setShowMealSelector(true)} > option.key === currentMealType)?.color || '#FF6B35' } ]} /> {MEAL_TYPE_MAP[currentMealType]} {isRecording ? ( ) : ( 记录 )} {/* 餐次选择弹窗 */} setShowMealSelector(false)} > setShowMealSelector(false)} /> 选择餐次 setShowMealSelector(false)}> {mealOptions.map((option) => ( handleMealTypeSelect(option.key)} > {option.label} {currentMealType === option.key && ( )} ))} {/* 食物详情弹窗 */} {/* 创建自定义食物弹窗 */} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: Colors.light.pageBackgroundEmphasis, }, customButton: { paddingHorizontal: 8, paddingVertical: 4, }, customButtonText: { fontSize: 16, color: Colors.light.textSecondary, fontWeight: '500', }, searchContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#FFF', marginHorizontal: 16, marginVertical: 12, paddingHorizontal: 12, paddingVertical: 10, borderRadius: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 2, elevation: 2, }, searchIcon: { marginRight: 8, }, searchInput: { flex: 1, fontSize: 16, color: '#333', }, mainContentCard: { flex: 1, marginHorizontal: 16, marginBottom: 16, backgroundColor: '#FFFFFF', borderRadius: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 4, overflow: 'hidden', }, mainContent: { flex: 1, flexDirection: 'row', }, categoryContainer: { width: 100, backgroundColor: 'transparent', }, categoryItem: { paddingVertical: 16, paddingHorizontal: 12, alignItems: 'center', }, categoryItemActive: { backgroundColor: '#F0F9FF' }, categoryText: { fontSize: 14, color: '#666', textAlign: 'center', }, categoryTextActive: { color: Colors.light.text, fontWeight: '500', }, foodContainer: { flex: 1, backgroundColor: 'transparent', }, foodItem: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12, }, foodInfo: { flexDirection: 'row', 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, color: '#999', }, addButton: { width: 32, height: 32, borderRadius: 16, backgroundColor: '#F5F5F5', alignItems: 'center', justifyContent: 'center', }, emptyContainer: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: 40, }, emptyText: { 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', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12, backgroundColor: '#FFF', borderTopWidth: 1, borderTopColor: '#E5E5E5', }, mealSelector: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, paddingVertical: 8, backgroundColor: '#F8F9FA', borderRadius: 20, }, mealIndicator: { width: 8, height: 8, borderRadius: 4, backgroundColor: '#FF6B35', marginRight: 8, }, mealText: { fontSize: 14, color: '#333', marginRight: 4, }, recordButton: { backgroundColor: Colors.light.primary, paddingHorizontal: 24, paddingVertical: 10, borderRadius: 20, }, recordButtonText: { fontSize: 16, 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: Colors.light.text, 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', }, });