diff --git a/app/(tabs)/statistics.tsx b/app/(tabs)/statistics.tsx
index a48534c..31f85ca 100644
--- a/app/(tabs)/statistics.tsx
+++ b/app/(tabs)/statistics.tsx
@@ -345,14 +345,16 @@ export default function ExploreScreen() {
{/* 左列 */}
-
-
+ pushIfAuthedElseLogin('/mood/calendar')}
+ isLoading={isMoodLoading}
/>
+
消耗卡路里
{activeCalories != null ? (
@@ -389,12 +391,11 @@ export default function ExploreScreen() {
showLabel={false}
/>
- {/* 心情卡片 */}
-
- pushIfAuthedElseLogin('/mood/calendar')}
- isLoading={isMoodLoading}
+
+
diff --git a/app/food-library.tsx b/app/food-library.tsx
index fc37003..678e4dc 100644
--- a/app/food-library.tsx
+++ b/app/food-library.tsx
@@ -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() {
{/* 创建自定义食物弹窗 */}
@@ -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: {
diff --git a/components/NutritionRadarCard.tsx b/components/NutritionRadarCard.tsx
index 49693dc..67d4be4 100644
--- a/components/NutritionRadarCard.tsx
+++ b/components/NutritionRadarCard.tsx
@@ -85,30 +85,6 @@ export function NutritionRadarCard({
const consumedCalories = nutritionSummary?.totalCalories || 0;
const remainingCalories = burnedCalories - consumedCalories - calorieDeficit;
- // 餐次数据
- const meals = [
- {
- type: 'breakfast' as const,
- name: '早餐',
- emoji: '🥚',
- },
- {
- type: 'lunch' as const,
- name: '午餐',
- emoji: '🍔',
- },
- {
- type: 'dinner' as const,
- name: '晚餐',
- emoji: '🥣',
- },
- {
- type: 'snack' as const,
- name: '加餐',
- emoji: '🍎',
- },
- ];
-
const handleNavigateToRecords = () => {
router.push(ROUTES.NUTRITION_RECORDS);
};
@@ -124,7 +100,7 @@ export function NutritionRadarCard({
更新: {dayjs(nutritionSummary?.updatedAt).format('MM-DD HH:mm')}
-
+
@@ -142,7 +118,6 @@ export function NutritionRadarCard({
{nutritionStats.map((stat, index) => (
-
{stat.label}
{stat.value}
@@ -267,7 +242,6 @@ const styles = StyleSheet.create({
},
// 卡路里相关样式
calorieSection: {
- marginTop: 12,
},
calorieTitleContainer: {
@@ -341,10 +315,11 @@ const styles = StyleSheet.create({
fontSize: 24,
},
addButton: {
- width: 16,
- height: 16,
+ width: 18,
+ height: 18,
borderRadius: 8,
- backgroundColor: '#10B981',
+ backgroundColor: '#9AA3AE',
+ marginLeft: 8,
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#000',
diff --git a/components/NutritionRecordCard.tsx b/components/NutritionRecordCard.tsx
index 45737df..e85ca94 100644
--- a/components/NutritionRecordCard.tsx
+++ b/components/NutritionRecordCard.tsx
@@ -6,7 +6,6 @@ import dayjs from 'dayjs';
import React, { useMemo, useRef, useState } from 'react';
import { Alert, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { Swipeable } from 'react-native-gesture-handler';
-import Popover from 'react-native-popover-view';
export type NutritionRecordCardProps = {
record: DietRecord;
@@ -46,7 +45,6 @@ export function NutritionRecordCard({
const surfaceColor = useThemeColor({}, 'surface');
const textColor = useThemeColor({}, 'text');
const textSecondaryColor = useThemeColor({}, 'textSecondary');
- const primaryColor = useThemeColor({}, 'primary');
// Popover 状态管理
const [showPopover, setShowPopover] = useState(false);
@@ -80,7 +78,6 @@ export function NutritionRecordCard({
}, [record]);
const mealTypeColor = MEAL_TYPE_COLORS[record.mealType];
- const mealTypeIcon = MEAL_TYPE_ICONS[record.mealType];
const mealTypeLabel = MEAL_TYPE_LABELS[record.mealType];
// 处理删除操作
@@ -182,8 +179,8 @@ export function NutritionRecordCard({
{/* 餐次标签 */}
-
-
+
+
{mealTypeLabel}
@@ -201,29 +198,6 @@ export function NutritionRecordCard({
- {/* Popover for more options */}
- setShowPopover(false)}
- popoverStyle={styles.popoverContainer}
- backgroundStyle={styles.popoverBackground}
- >
-
- {
- setShowPopover(false);
- onDelete?.();
- }}
- >
-
-
- 删除记录
-
-
-
-
);
}
@@ -245,8 +219,6 @@ const styles = StyleSheet.create({
backgroundColor: '#FFFFFF',
borderRadius: 12,
padding: 12,
-
-
},
mainContent: {
flex: 1,
@@ -276,13 +248,13 @@ const styles = StyleSheet.create({
},
foodName: {
- fontSize: 15,
+ fontSize: 14,
fontWeight: '600',
color: '#333333',
- marginBottom: 2,
+ marginTop: 2,
},
mealTime: {
- fontSize: 12,
+ fontSize: 10,
fontWeight: '400',
color: '#999999',
},
@@ -324,6 +296,7 @@ const styles = StyleSheet.create({
caloriesText: {
fontSize: 12,
color: '#473c3cff',
+ fontWeight: '500',
},
mealTypeBadge: {
paddingHorizontal: 8,
diff --git a/components/model/food/CreateCustomFoodModal.tsx b/components/model/food/CreateCustomFoodModal.tsx
index ef30c36..dccbc53 100644
--- a/components/model/food/CreateCustomFoodModal.tsx
+++ b/components/model/food/CreateCustomFoodModal.tsx
@@ -1,8 +1,9 @@
import { Ionicons } from '@expo/vector-icons';
import { Image } from 'expo-image';
import * as ImagePicker from 'expo-image-picker';
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import {
+ ActivityIndicator,
Alert,
Dimensions,
Keyboard,
@@ -16,6 +17,9 @@ import {
TouchableOpacity,
View
} from 'react-native';
+import { useAppSelector } from '@/hooks/redux';
+import { useCosUpload } from '@/hooks/useCosUpload';
+import { Colors } from '@/constants/Colors';
export interface CreateCustomFoodModalProps {
visible: boolean;
@@ -25,7 +29,6 @@ export interface CreateCustomFoodModalProps {
export interface CustomFoodData {
name: string;
- unit: string;
defaultAmount: number;
caloriesUnit: string;
calories: number;
@@ -41,7 +44,6 @@ export function CreateCustomFoodModal({
onSave
}: CreateCustomFoodModalProps) {
const [foodName, setFoodName] = useState('');
- const [foodUnit, setFoodUnit] = useState('');
const [defaultAmount, setDefaultAmount] = useState('100');
const [caloriesUnit, setCaloriesUnit] = useState('千卡');
const [calories, setCalories] = useState('100');
@@ -51,6 +53,20 @@ export function CreateCustomFoodModal({
const [carbohydrate, setCarbohydrate] = useState('0');
const [keyboardHeight, setKeyboardHeight] = useState(0);
+ // 获取用户ID和上传功能
+ const accountProfile = useAppSelector((s) => (s as any)?.user?.profile as any);
+ const userId: string | undefined = useMemo(() => {
+ return (
+ accountProfile?.userId ||
+ accountProfile?.id ||
+ accountProfile?._id ||
+ accountProfile?.uid ||
+ undefined
+ ) as string | undefined;
+ }, [accountProfile]);
+
+ const { upload, uploading } = useCosUpload();
+
// 键盘监听
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
@@ -76,7 +92,6 @@ export function CreateCustomFoodModal({
useEffect(() => {
if (visible) {
setFoodName('');
- setFoodUnit('');
setDefaultAmount('100');
setCaloriesUnit('千卡');
setCalories('100');
@@ -92,21 +107,41 @@ export function CreateCustomFoodModal({
// 选择图片
const handleSelectImage = async () => {
- const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
- if (status !== 'granted') {
- Alert.alert('需要相册权限', '请在设置中开启相册权限');
- return;
- }
-
- const result = await ImagePicker.launchImageLibraryAsync({
- mediaTypes: ImagePicker.MediaTypeOptions.Images,
- allowsEditing: true,
- aspect: [1, 1],
- quality: 1,
- });
-
- if (!result.canceled && result.assets[0]) {
- setImageUrl(result.assets[0].uri);
+ try {
+ const resp = await ImagePicker.requestMediaLibraryPermissionsAsync();
+ const libGranted = resp.status === 'granted' || (resp as any).accessPrivileges === 'limited';
+ if (!libGranted) {
+ Alert.alert('权限不足', '需要相册权限以选择照片');
+ return;
+ }
+
+ const result = await ImagePicker.launchImageLibraryAsync({
+ allowsEditing: true,
+ quality: 0.9,
+ aspect: [1, 1],
+ mediaTypes: ['images'],
+ base64: false,
+ });
+
+ if (!result.canceled) {
+ const asset = result.assets?.[0];
+ if (!asset?.uri) return;
+
+ // 直接上传到 COS,成功后写入 URL
+ try {
+ const { url } = await upload(
+ { uri: asset.uri, name: asset.fileName || 'food.jpg', type: asset.mimeType || 'image/jpeg' },
+ { prefix: 'foods/', userId }
+ );
+
+ setImageUrl(url);
+ } catch (e) {
+ console.warn('上传照片失败', e);
+ Alert.alert('上传失败', '照片上传失败,请重试');
+ }
+ }
+ } catch (e) {
+ Alert.alert('发生错误', '选择照片失败,请重试');
}
};
@@ -119,10 +154,7 @@ export function CreateCustomFoodModal({
Alert.alert('提示', '请输入食物名称');
return;
}
- if (!foodUnit.trim()) {
- Alert.alert('提示', '请输入食物单位');
- return;
- }
+
if (!calories.trim() || parseFloat(calories) <= 0) {
Alert.alert('提示', '请输入有效的热量值');
return;
@@ -130,7 +162,6 @@ export function CreateCustomFoodModal({
const foodData: CustomFoodData = {
name: foodName.trim(),
- unit: foodUnit.trim(),
defaultAmount: parseFloat(defaultAmount) || 100,
caloriesUnit,
calories: parseFloat(calories) || 0,
@@ -182,14 +213,14 @@ export function CreateCustomFoodModal({
保存
@@ -211,7 +242,7 @@ export function CreateCustomFoodModal({
{foodName || '食物名称'}
- {actualCalories}{caloriesUnit}/{defaultAmount}{foodUnit}
+ {actualCalories}{caloriesUnit}/{defaultAmount}g
@@ -226,26 +257,18 @@ export function CreateCustomFoodModal({
{/* 食物名称和单位 */}
-
-
- 食物名称
-
-
-
- 食物单位
-
+
+ 食物名称
+
+
+
+
@@ -262,17 +285,11 @@ export function CreateCustomFoodModal({
placeholder="100"
placeholderTextColor="#A0A0A0"
/>
- {foodUnit || '单位'}
+ g
- {/* 热量单位 */}
-
- 热量单位
- 千卡
-
-
{/* 食物热量 */}
食物热量
@@ -301,7 +318,11 @@ export function CreateCustomFoodModal({
照片
-
+
{imageUrl ? (
) : (
@@ -310,6 +331,11 @@ export function CreateCustomFoodModal({
添加照片
)}
+ {uploading && (
+
+
+
+ )}
@@ -369,13 +395,6 @@ export function CreateCustomFoodModal({
-
- {/* 底部提示 */}
-
-
- *为了避免历史饮食数据混乱,自定义食物创建后不支持修改。
-
-
@@ -384,7 +403,7 @@ export function CreateCustomFoodModal({
);
}
-const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
+const { height: screenHeight } = Dimensions.get('window');
const styles = StyleSheet.create({
overlay: {
@@ -410,8 +429,6 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 16,
- borderBottomWidth: 1,
- borderBottomColor: '#E5E5E5',
},
backButton: {
padding: 4,
@@ -434,15 +451,14 @@ const styles = StyleSheet.create({
},
saveButtonText: {
fontSize: 16,
- color: '#4CAF50',
+ color: Colors.light.primary,
fontWeight: '500',
},
saveButtonTextDisabled: {
- color: '#999',
+ color: Colors.light.textMuted,
},
previewSection: {
paddingHorizontal: 16,
- paddingTop: 20,
paddingBottom: 16,
},
previewCard: {
@@ -489,15 +505,16 @@ const styles = StyleSheet.create({
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
- marginBottom: 16,
+ marginBottom: 4,
+
},
sectionTitle: {
- fontSize: 18,
- fontWeight: '600',
+ fontSize: 14,
color: '#333',
+ marginLeft: 8
},
requiredIndicator: {
- fontSize: 18,
+ fontSize: 16,
color: '#FF4444',
marginLeft: 4,
},
@@ -515,16 +532,15 @@ const styles = StyleSheet.create({
inputLabel: {
fontSize: 14,
color: '#666',
- marginBottom: 8,
fontWeight: '500',
},
modernTextInput: {
- borderWidth: 1.5,
- borderColor: '#E8E8E8',
+ flex: 1,
borderRadius: 12,
- paddingHorizontal: 16,
- paddingVertical: 14,
+ paddingHorizontal: 12,
+ paddingVertical: 8,
fontSize: 16,
+ marginLeft: 20,
color: '#333',
backgroundColor: '#FFFFFF',
shadowColor: '#000',
@@ -536,9 +552,8 @@ const styles = StyleSheet.create({
numberInputContainer: {
flexDirection: 'row',
alignItems: 'center',
- borderWidth: 1.5,
- borderColor: '#E8E8E8',
borderRadius: 12,
+
backgroundColor: '#FFFFFF',
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
@@ -548,8 +563,8 @@ const styles = StyleSheet.create({
},
modernNumberInput: {
flex: 1,
- paddingHorizontal: 16,
- paddingVertical: 14,
+ paddingHorizontal: 12,
+ paddingVertical: 8,
fontSize: 16,
color: '#333',
textAlign: 'right',
@@ -578,8 +593,8 @@ const styles = StyleSheet.create({
elevation: 1,
},
selectButtonText: {
- fontSize: 16,
- color: '#333',
+ fontSize: 14,
+ color: 'gray',
fontWeight: '500',
},
modernImageSelector: {
@@ -695,6 +710,7 @@ const styles = StyleSheet.create({
// 新增行布局样式
inputRowContainer: {
flexDirection: 'row',
+ justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 20,
},
@@ -708,4 +724,15 @@ const styles = StyleSheet.create({
inputRowContent: {
flex: 1,
},
+ imageLoadingOverlay: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0,
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: 'rgba(0,0,0,0.5)',
+ borderRadius: 16,
+ },
});
\ No newline at end of file
diff --git a/components/model/food/FoodDetailModal.tsx b/components/model/food/FoodDetailModal.tsx
index 4812589..7db9792 100644
--- a/components/model/food/FoodDetailModal.tsx
+++ b/components/model/food/FoodDetailModal.tsx
@@ -1,6 +1,7 @@
import { Ionicons } from '@expo/vector-icons';
import React, { useEffect, useState } from 'react';
import {
+ Alert,
Dimensions,
Keyboard,
KeyboardAvoidingView,
@@ -14,6 +15,7 @@ import {
View,
} from 'react-native';
// 导入统一的食物类型定义
+import { Colors } from '@/constants/Colors';
import { DEFAULT_IMAGE_FOOD } from '@/constants/Image';
import type { FoodItem } from '@/types/food';
import { Image } from 'expo-image';
@@ -38,8 +40,10 @@ const QUICK_SELECT_OPTIONS = [
export interface FoodDetailModalProps {
visible: boolean;
food: FoodItem | null;
+ category?: { id: string; isSystem?: boolean } | null;
onClose: () => void;
onSave: (food: FoodItem, amount: number, unit: string) => void;
+ onDelete?: (foodId: string) => void;
}
// 获取营养数据,优先使用FoodItem中的数据
@@ -78,8 +82,10 @@ const getNutritionInfo = (food: FoodItem): NutritionInfo => {
export function FoodDetailModal({
visible,
food,
+ category,
onClose,
- onSave
+ onSave,
+ onDelete
}: FoodDetailModalProps) {
const [amount, setAmount] = useState('100');
const [isFavorite, setIsFavorite] = useState(false);
@@ -189,9 +195,9 @@ export function FoodDetailModal({
-
+ {/*
我要纠错
-
+ */}
{/* 食物信息 */}
@@ -203,16 +209,44 @@ export function FoodDetailModal({
/>
{food.name}
- setIsFavorite(!isFavorite)}
- style={styles.favoriteButton}
- >
-
-
+
+ {/* setIsFavorite(!isFavorite)}
+ style={styles.favoriteButton}
+ >
+
+ */}
+ {/* 删除按钮 - 仅对自定义食物显示 */}
+ {category && category.id === 'custom' && onDelete && (
+ {
+ Alert.alert(
+ '删除食物',
+ '确定要删除这个自定义食物吗?',
+ [
+ { text: '取消', style: 'cancel' },
+ {
+ text: '删除',
+ style: 'destructive',
+ onPress: () => onDelete(food.id)
+ }
+ ]
+ );
+ }}
+ style={styles.deleteButton}
+ >
+
+
+ )}
+
{/* 营养信息 */}
@@ -367,13 +401,20 @@ const styles = StyleSheet.create({
color: '#333',
marginRight: 8,
},
+ actionButtons: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 4,
+ },
favoriteButton: {
padding: 8,
},
+ deleteButton: {
+ padding: 8,
+ },
nutritionContainer: {
flexDirection: 'row',
paddingHorizontal: screenWidth > 400 ? 24 : 16,
- paddingVertical: 16,
justifyContent: 'space-between',
},
nutritionItem: {
@@ -392,28 +433,29 @@ const styles = StyleSheet.create({
},
amountContainer: {
alignItems: 'center',
- paddingVertical: 16,
},
inputWithUnit: {
flexDirection: 'row',
alignItems: 'center',
- backgroundColor: '#E8F5E8',
borderRadius: 12,
paddingHorizontal: 20,
paddingVertical: 16,
+
},
amountInput: {
fontSize: 24,
fontWeight: '600',
- color: '#4CAF50',
+ color: Colors.light.text,
textAlign: 'center',
minWidth: 80,
backgroundColor: 'transparent',
+ borderBottomWidth: 1,
+ borderBottomColor: 'gray',
},
unitLabel: {
fontSize: 18,
fontWeight: '500',
- color: '#4CAF50',
+ color: Colors.light.text,
marginLeft: 8,
},
quickSelectContainer: {
@@ -432,7 +474,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
quickSelectOptionActive: {
- backgroundColor: '#4CAF50',
+ backgroundColor: Colors.light.primary,
},
quickSelectText: {
fontSize: 14,
@@ -443,7 +485,7 @@ const styles = StyleSheet.create({
color: '#FFFFFF',
},
saveButton: {
- backgroundColor: '#4CAF50',
+ backgroundColor: Colors.light.primary,
marginHorizontal: screenWidth > 400 ? 24 : 16,
marginBottom: 16,
paddingVertical: 14,
diff --git a/ios/digitalpilates.xcodeproj/project.pbxproj b/ios/digitalpilates.xcodeproj/project.pbxproj
index d6a1fd1..15c13e5 100644
--- a/ios/digitalpilates.xcodeproj/project.pbxproj
+++ b/ios/digitalpilates.xcodeproj/project.pbxproj
@@ -463,7 +463,10 @@
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
- OTHER_LDFLAGS = "$(inherited) ";
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ " ",
+ );
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@@ -518,7 +521,10 @@
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = NO;
- OTHER_LDFLAGS = "$(inherited) ";
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ " ",
+ );
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = false;
diff --git a/services/foodLibraryApi.ts b/services/foodLibraryApi.ts
index e7c8512..46d336d 100644
--- a/services/foodLibraryApi.ts
+++ b/services/foodLibraryApi.ts
@@ -6,6 +6,20 @@ import type {
} from '@/types/food';
import { api } from './api';
+export interface CreateCustomFoodDto {
+ name: string;
+ description?: string;
+ caloriesPer100g?: number;
+ proteinPer100g?: number;
+ carbohydratePer100g?: number;
+ fatPer100g?: number;
+ fiberPer100g?: number;
+ sugarPer100g?: number;
+ sodiumPer100g?: number;
+ additionalNutrition?: Record;
+ imageUrl?: string;
+}
+
/**
* 食物库 API 服务
*/
@@ -39,6 +53,20 @@ export class FoodLibraryApi {
const { id } = params;
return api.get(`${this.BASE_PATH}/${id}`);
}
+
+ /**
+ * 创建自定义食物
+ */
+ static async createCustomFood(data: CreateCustomFoodDto): Promise {
+ return api.post(`${this.BASE_PATH}/custom`, data);
+ }
+
+ /**
+ * 删除自定义食物
+ */
+ static async deleteCustomFood(id: number): Promise {
+ return api.delete(`${this.BASE_PATH}/custom/${id}`);
+ }
}
// 导出便捷方法
@@ -46,4 +74,6 @@ export const foodLibraryApi = {
getFoodLibrary: () => FoodLibraryApi.getFoodLibrary(),
searchFoods: (keyword: string) => FoodLibraryApi.searchFoods({ keyword }),
getFoodById: (id: number) => FoodLibraryApi.getFoodById({ id }),
+ createCustomFood: (data: CreateCustomFoodDto) => FoodLibraryApi.createCustomFood(data),
+ deleteCustomFood: (id: number) => FoodLibraryApi.deleteCustomFood(id),
};
\ No newline at end of file