feat: 增强食物库功能,支持自定义食物的创建与删除,优化用户体验

This commit is contained in:
2025-08-29 21:03:45 +08:00
parent e9b593a07e
commit 3fdd2acaf2
8 changed files with 301 additions and 213 deletions

View File

@@ -1,9 +1,11 @@
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 { useFoodLibrary, useFoodSearch } from '@/hooks/useFoodLibrary';
import { addDietRecord, type CreateDietRecordDto } from '@/services/dietRecords';
import { foodLibraryApi, type CreateCustomFoodDto } from '@/services/foodLibraryApi';
import type { FoodItem, MealType, SelectedFoodItem } from '@/types/food';
import { Ionicons } from '@expo/vector-icons';
import { Image } from 'expo-image';
@@ -11,6 +13,7 @@ import { useLocalSearchParams, useRouter } from 'expo-router';
import React, { useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
Alert,
Modal,
ScrollView,
StyleSheet,
@@ -36,7 +39,7 @@ export default function FoodLibraryScreen() {
const mealType = (params.mealType as MealType) || 'breakfast';
// Redux hooks
const { categories, loading, error, clearErrors } = useFoodLibrary();
const { categories, loading, error, clearErrors, loadFoodLibrary } = useFoodLibrary();
const { searchResults, searchLoading, search, clearResults } = useFoodSearch();
// 本地状态
@@ -129,6 +132,20 @@ export default function FoodLibraryScreen() {
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;
@@ -182,32 +199,51 @@ export default function FoodLibraryScreen() {
};
// 处理保存自定义食物
const handleSaveCustomFood = (customFoodData: CustomFoodData) => {
// 创建一个临时的FoodItem对象
const customFoodItem: FoodItem = {
id: `custom_${Date.now()}`,
name: customFoodData.name,
calories: customFoodData.calories,
unit: customFoodData.unit,
description: `自定义食物 - ${customFoodData.name}`,
imageUrl: customFoodData.imageUrl,
protein: customFoodData.protein,
fat: customFoodData.fat,
carbohydrate: customFoodData.carbohydrate,
};
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,
};
// 直接添加到选择列表中
const newSelectedItem: SelectedFoodItem = {
id: `custom_${Date.now()}`,
food: customFoodItem,
amount: customFoodData.defaultAmount,
unit: customFoodData.unit,
calories: Math.round((customFoodData.calories * customFoodData.defaultAmount) / 100)
};
// 调用API创建自定义食物
const createdFood = await foodLibraryApi.createCustomFood(createData);
setSelectedFoodItems(prev => [...prev, newSelectedItem]);
// 需要拉取一遍最新的食物列表
await loadFoodLibrary();
console.log('保存自定义食物:', customFoodData);
// 创建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('创建失败', '创建自定义食物时发生错误,请稍后重试');
}
};
// 关闭自定义食物弹窗
@@ -472,8 +508,10 @@ export default function FoodLibraryScreen() {
<FoodDetailModal
visible={showFoodDetail}
food={selectedFood}
category={selectedCategory}
onClose={handleCloseFoodDetail}
onSave={handleSaveFood}
onDelete={handleDeleteFood}
/>
{/* 创建自定义食物弹窗 */}
@@ -497,7 +535,7 @@ const styles = StyleSheet.create({
},
customButtonText: {
fontSize: 16,
color: '#4CAF50',
color: Colors.light.textSecondary,
fontWeight: '500',
},
searchContainer: {
@@ -546,8 +584,6 @@ const styles = StyleSheet.create({
categoryContainer: {
width: 100,
backgroundColor: 'transparent',
borderRightWidth: 1,
borderRightColor: '#E5E5E5',
},
categoryItem: {
paddingVertical: 16,
@@ -555,9 +591,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
categoryItemActive: {
backgroundColor: '#F0F9FF',
borderRightWidth: 2,
borderRightColor: '#4CAF50',
backgroundColor: '#F0F9FF'
},
categoryText: {
fontSize: 14,
@@ -565,7 +599,7 @@ const styles = StyleSheet.create({
textAlign: 'center',
},
categoryTextActive: {
color: '#4CAF50',
color: Colors.light.text,
fontWeight: '500',
},
foodContainer: {
@@ -711,7 +745,7 @@ const styles = StyleSheet.create({
marginRight: 4,
},
recordButton: {
backgroundColor: '#4CAF50',
backgroundColor: Colors.light.primary,
paddingHorizontal: 24,
paddingVertical: 10,
borderRadius: 20,
@@ -749,7 +783,7 @@ const styles = StyleSheet.create({
},
totalCalories: {
fontSize: 14,
color: '#4CAF50',
color: Colors.light.text,
fontWeight: '500',
},
selectedFoodsList: {