diff --git a/app/food-library.tsx b/app/food-library.tsx index 52aef4e..fc37003 100644 --- a/app/food-library.tsx +++ b/app/food-library.tsx @@ -1,7 +1,10 @@ +import { CreateCustomFoodModal, type CustomFoodData } from '@/components/model/food/CreateCustomFoodModal'; import { FoodDetailModal } from '@/components/model/food/FoodDetailModal'; +import { HeaderBar } from '@/components/ui/HeaderBar'; +import { DEFAULT_IMAGE_FOOD } from '@/constants/Image'; import { useFoodLibrary, useFoodSearch } from '@/hooks/useFoodLibrary'; -import type { FoodItem, MealType, SelectedFoodItem } from '@/types/food'; import { addDietRecord, type CreateDietRecordDto } from '@/services/dietRecords'; +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'; @@ -9,9 +12,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, Modal, - SafeAreaView, ScrollView, - StatusBar, StyleSheet, Text, TextInput, @@ -47,6 +48,7 @@ export default function FoodLibraryScreen() { 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); @@ -59,8 +61,6 @@ export default function FoodLibraryScreen() { } if (selectedCategory) { - console.log('selectedCategory', selectedCategory); - return selectedCategory.foods } @@ -132,9 +132,9 @@ export default function FoodLibraryScreen() { // 处理饮食记录 const handleRecordDiet = async () => { if (selectedFoodItems.length === 0) return; - + setIsRecording(true); - + try { // 逐个记录选中的食物 for (const item of selectedFoodItems) { @@ -155,10 +155,10 @@ export default function FoodLibraryScreen() { mealTime: new Date().toISOString(), imageUrl: item.food.imageUrl, }; - + await addDietRecord(dietRecordData); } - + // 记录成功后清空选择列表并返回 setSelectedFoodItems([]); router.back(); @@ -176,6 +176,45 @@ export default function FoodLibraryScreen() { setShowMealSelector(false); }; + // 处理创建自定义食物 + const handleCreateCustomFood = () => { + setShowCreateCustomFood(true); + }; + + // 处理保存自定义食物 + 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 newSelectedItem: SelectedFoodItem = { + id: `custom_${Date.now()}`, + food: customFoodItem, + amount: customFoodData.defaultAmount, + unit: customFoodData.unit, + calories: Math.round((customFoodData.calories * customFoodData.defaultAmount) / 100) + }; + + setSelectedFoodItems(prev => [...prev, newSelectedItem]); + + console.log('保存自定义食物:', customFoodData); + }; + + // 关闭自定义食物弹窗 + const handleCloseCreateCustomFood = () => { + setShowCreateCustomFood(false); + }; + // 餐次选择选项 const mealOptions = [ { key: 'breakfast' as const, label: '早餐', color: '#FF6B35' }, @@ -185,19 +224,19 @@ export default function FoodLibraryScreen() { ]; return ( - - - + {/* 头部 */} - - router.back()} style={styles.backButton}> - - - 食物库 - - 自定义 - - + router.back()} + transparent={false} + variant="elevated" + right={ + + 自定义 + + } + /> {/* 搜索框 */} @@ -275,11 +314,11 @@ export default function FoodLibraryScreen() { {filteredFoods.map((food) => ( - {food.imageUrl ? : {food.emoji || '🍽️'}} + /> {food.name} @@ -436,7 +475,14 @@ export default function FoodLibraryScreen() { onClose={handleCloseFoodDetail} onSave={handleSaveFood} /> - + + {/* 创建自定义食物弹窗 */} + + ); } @@ -445,24 +491,6 @@ const styles = StyleSheet.create({ flex: 1, backgroundColor: '#F8F9FA', }, - header: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 16, - paddingVertical: 12, - backgroundColor: '#F8F9FA', - borderBottomWidth: 1, - borderBottomColor: '#E5E5E5', - }, - backButton: { - padding: 4, - }, - headerTitle: { - fontSize: 18, - fontWeight: '600', - color: '#333', - }, customButton: { paddingHorizontal: 8, paddingVertical: 4, diff --git a/components/NutritionRecordCard.tsx b/components/NutritionRecordCard.tsx index 0bd674e..45737df 100644 --- a/components/NutritionRecordCard.tsx +++ b/components/NutritionRecordCard.tsx @@ -231,6 +231,13 @@ export function NutritionRecordCard({ const styles = StyleSheet.create({ container: { marginBottom: 12, + // iOS 阴影效果 - 更自然的阴影 + shadowColor: '#000000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.08, + shadowRadius: 4, + // Android 阴影效果 + elevation: 2, }, card: { flex: 1, @@ -238,12 +245,8 @@ const styles = StyleSheet.create({ backgroundColor: '#FFFFFF', borderRadius: 12, padding: 12, - marginHorizontal: 4, - shadowColor: '#000', - shadowOffset: { width: 0, height: 1 }, - shadowOpacity: 0.05, - shadowRadius: 6, - elevation: 2, + + }, mainContent: { flex: 1, @@ -348,11 +351,17 @@ const styles = StyleSheet.create({ }, popoverContainer: { borderRadius: 12, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 8, - elevation: 5, + backgroundColor: '#FFFFFF', + // iOS 阴影效果 + shadowColor: '#000000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 12, + // Android 阴影效果 + elevation: 8, + // 添加边框 + borderWidth: 0.5, + borderColor: 'rgba(0, 0, 0, 0.08)', }, popoverBackground: { backgroundColor: 'rgba(0, 0, 0, 0.3)', diff --git a/components/model/food/CreateCustomFoodModal.tsx b/components/model/food/CreateCustomFoodModal.tsx new file mode 100644 index 0000000..ef30c36 --- /dev/null +++ b/components/model/food/CreateCustomFoodModal.tsx @@ -0,0 +1,711 @@ +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 { + Alert, + Dimensions, + Keyboard, + KeyboardAvoidingView, + Modal, + Platform, + ScrollView, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View +} from 'react-native'; + +export interface CreateCustomFoodModalProps { + visible: boolean; + onClose: () => void; + onSave: (foodData: CustomFoodData) => void; +} + +export interface CustomFoodData { + name: string; + unit: string; + defaultAmount: number; + caloriesUnit: string; + calories: number; + imageUrl?: string; + protein?: number; + fat?: number; + carbohydrate?: number; +} + +export function CreateCustomFoodModal({ + visible, + onClose, + onSave +}: CreateCustomFoodModalProps) { + const [foodName, setFoodName] = useState(''); + const [foodUnit, setFoodUnit] = useState(''); + const [defaultAmount, setDefaultAmount] = useState('100'); + const [caloriesUnit, setCaloriesUnit] = useState('千卡'); + const [calories, setCalories] = useState('100'); + const [imageUrl, setImageUrl] = useState(''); + const [protein, setProtein] = useState('0'); + const [fat, setFat] = useState('0'); + const [carbohydrate, setCarbohydrate] = useState('0'); + const [keyboardHeight, setKeyboardHeight] = useState(0); + + // 键盘监听 + useEffect(() => { + const keyboardDidShowListener = Keyboard.addListener( + Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', + (e) => { + setKeyboardHeight(e.endCoordinates.height); + } + ); + const keyboardDidHideListener = Keyboard.addListener( + Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', + () => { + setKeyboardHeight(0); + } + ); + + return () => { + keyboardDidShowListener?.remove(); + keyboardDidHideListener?.remove(); + }; + }, []); + + // 重置表单 + useEffect(() => { + if (visible) { + setFoodName(''); + setFoodUnit(''); + setDefaultAmount('100'); + setCaloriesUnit('千卡'); + setCalories('100'); + setImageUrl(''); + setProtein('0'); + setFat('0'); + setCarbohydrate('0'); + } + }, [visible]); + + // 选择热量单位 + + + // 选择图片 + 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); + } + }; + + // 计算实际热量预览 + const actualCalories = Math.round((parseFloat(calories) || 0) * (parseFloat(defaultAmount) || 0) / 100); + + // 保存自定义食物 + const handleSave = () => { + if (!foodName.trim()) { + Alert.alert('提示', '请输入食物名称'); + return; + } + if (!foodUnit.trim()) { + Alert.alert('提示', '请输入食物单位'); + return; + } + if (!calories.trim() || parseFloat(calories) <= 0) { + Alert.alert('提示', '请输入有效的热量值'); + return; + } + + const foodData: CustomFoodData = { + name: foodName.trim(), + unit: foodUnit.trim(), + defaultAmount: parseFloat(defaultAmount) || 100, + caloriesUnit, + calories: parseFloat(calories) || 0, + imageUrl: imageUrl || undefined, + protein: parseFloat(protein) || undefined, + fat: parseFloat(fat) || undefined, + carbohydrate: parseFloat(carbohydrate) || undefined, + }; + + onSave(foodData); + onClose(); + }; + + return ( + + + + 0 && { + height: screenHeight - keyboardHeight, + maxHeight: screenHeight - keyboardHeight, + } + ]}> + 0 ? 20 : 0 + }} + > + {/* 头部 */} + + + + + 创建自定义食物 + + 保存 + + + + {/* 效果预览区域 */} + + 效果预览 + + + {imageUrl ? ( + + ) : ( + + + + )} + + + {foodName || '食物名称'} + + + {actualCalories}{caloriesUnit}/{defaultAmount}{foodUnit} + + + + + + + {/* 基本信息 */} + + + 基本信息 + * + + + {/* 食物名称和单位 */} + + + 食物名称 + + + + 食物单位 + + + + + {/* 默认数量 */} + + 默认数量 + + + + {foodUnit || '单位'} + + + + + {/* 热量单位 */} + + 热量单位 + 千卡 + + + {/* 食物热量 */} + + 食物热量 + + + + 千卡 + + + + + + + {/* 可选信息 */} + + 可选信息 + + {/* 照片 */} + + 照片 + + + {imageUrl ? ( + + ) : ( + + + 添加照片 + + )} + + + + + {/* 蛋白质 */} + + 蛋白质 + + + + + + + + + {/* 脂肪 */} + + 脂肪 + + + + + + + + + {/* 碳水化合物 */} + + 碳水化合物 + + + + + + + + + + + {/* 底部提示 */} + + + *为了避免历史饮食数据混乱,自定义食物创建后不支持修改。 + + + + + + + + ); +} + +const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + keyboardAvoidingView: { + flex: 1, + }, + modalContainer: { + flex: 1, + backgroundColor: '#FFFFFF', + marginTop: 50, + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + }, + scrollView: { + flex: 1, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 16, + borderBottomWidth: 1, + borderBottomColor: '#E5E5E5', + }, + backButton: { + padding: 4, + }, + headerTitle: { + fontSize: 18, + fontWeight: '600', + color: '#333', + flex: 1, + textAlign: 'center', + marginHorizontal: 20, + }, + saveButton: { + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 16, + }, + saveButtonDisabled: { + opacity: 0.5, + }, + saveButtonText: { + fontSize: 16, + color: '#4CAF50', + fontWeight: '500', + }, + saveButtonTextDisabled: { + color: '#999', + }, + previewSection: { + paddingHorizontal: 16, + paddingTop: 20, + paddingBottom: 16, + }, + previewCard: { + backgroundColor: '#F8F9FA', + borderRadius: 12, + padding: 16, + marginTop: 8, + }, + previewContent: { + flexDirection: 'row', + alignItems: 'center', + }, + previewImage: { + width: 32, + height: 32, + borderRadius: 4, + }, + previewImagePlaceholder: { + width: 32, + height: 32, + borderRadius: 4, + backgroundColor: '#E5E5E5', + alignItems: 'center', + justifyContent: 'center', + }, + previewInfo: { + flex: 1, + marginLeft: 12, + }, + previewName: { + fontSize: 16, + fontWeight: '500', + color: '#333', + marginBottom: 2, + }, + previewCalories: { + fontSize: 14, + color: '#666', + }, + section: { + paddingHorizontal: 16, + paddingVertical: 12, + }, + sectionHeader: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 16, + }, + sectionTitle: { + fontSize: 18, + fontWeight: '600', + color: '#333', + }, + requiredIndicator: { + fontSize: 18, + color: '#FF4444', + marginLeft: 4, + }, + inputGroup: { + marginBottom: 20, + }, + inputRowGroup: { + flexDirection: 'row', + gap: 12, + marginBottom: 20, + }, + inputRowItem: { + flex: 1, + }, + inputLabel: { + fontSize: 14, + color: '#666', + marginBottom: 8, + fontWeight: '500', + }, + modernTextInput: { + borderWidth: 1.5, + borderColor: '#E8E8E8', + borderRadius: 12, + paddingHorizontal: 16, + paddingVertical: 14, + fontSize: 16, + color: '#333', + backgroundColor: '#FFFFFF', + shadowColor: '#000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.05, + shadowRadius: 2, + elevation: 1, + }, + numberInputContainer: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 1.5, + borderColor: '#E8E8E8', + borderRadius: 12, + backgroundColor: '#FFFFFF', + shadowColor: '#000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.05, + shadowRadius: 2, + elevation: 1, + }, + modernNumberInput: { + flex: 1, + paddingHorizontal: 16, + paddingVertical: 14, + fontSize: 16, + color: '#333', + textAlign: 'right', + }, + unitText: { + fontSize: 14, + color: '#666', + paddingRight: 16, + minWidth: 40, + textAlign: 'center', + }, + modernSelectButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + borderWidth: 1.5, + borderColor: '#E8E8E8', + borderRadius: 12, + paddingHorizontal: 16, + paddingVertical: 14, + backgroundColor: '#FFFFFF', + shadowColor: '#000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.05, + shadowRadius: 2, + elevation: 1, + }, + selectButtonText: { + fontSize: 16, + color: '#333', + fontWeight: '500', + }, + modernImageSelector: { + alignSelf: 'flex-end', + borderRadius: 16, + overflow: 'hidden', + }, + selectedImage: { + width: 80, + height: 80, + borderRadius: 16, + }, + modernImagePlaceholder: { + width: 80, + height: 80, + borderRadius: 16, + backgroundColor: '#F8F8F8', + alignItems: 'center', + justifyContent: 'center', + borderWidth: 1.5, + borderColor: '#E8E8E8', + borderStyle: 'dashed', + }, + imagePlaceholderText: { + fontSize: 12, + color: '#A0A0A0', + marginTop: 4, + fontWeight: '500', + }, + nutritionRow: { + flexDirection: 'row', + gap: 12, + marginBottom: 20, + }, + nutritionItem: { + flex: 1, + }, + // 保留旧样式以防兼容性问题 + textInput: { + borderWidth: 1, + borderColor: '#E5E5E5', + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 12, + fontSize: 16, + color: '#333', + backgroundColor: '#FFFFFF', + }, + numberInput: { + flex: 1, + borderWidth: 1, + borderColor: '#E5E5E5', + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 12, + fontSize: 16, + color: '#333', + backgroundColor: '#FFFFFF', + textAlign: 'right', + }, + inputWithUnit: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + }, + inputUnit: { + fontSize: 16, + color: '#666', + minWidth: 30, + }, + selectButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + borderWidth: 1, + borderColor: '#E5E5E5', + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 12, + backgroundColor: '#FFFFFF', + }, + imageSelector: { + alignSelf: 'flex-end', + borderRadius: 12, + overflow: 'hidden', + }, + imagePlaceholder: { + width: 60, + height: 60, + borderRadius: 12, + backgroundColor: '#F0F0F0', + alignItems: 'center', + justifyContent: 'center', + borderWidth: 1, + borderColor: '#E5E5E5', + }, + disclaimer: { + paddingHorizontal: 16, + paddingVertical: 20, + paddingBottom: 40, + }, + disclaimerText: { + fontSize: 12, + color: '#999', + lineHeight: 18, + }, + sectionCard: { + backgroundColor: '#F8F9FA', + borderRadius: 12, + padding: 16, + marginTop: 8, + }, + // 新增行布局样式 + inputRowContainer: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 20, + }, + inputRowLabel: { + fontSize: 14, + color: '#666', + fontWeight: '500', + width: 80, + marginRight: 12, + }, + inputRowContent: { + flex: 1, + }, +}); \ No newline at end of file diff --git a/components/model/food/FoodDetailModal.tsx b/components/model/food/FoodDetailModal.tsx index d197eee..4812589 100644 --- a/components/model/food/FoodDetailModal.tsx +++ b/components/model/food/FoodDetailModal.tsx @@ -14,7 +14,9 @@ import { View, } from 'react-native'; // 导入统一的食物类型定义 +import { DEFAULT_IMAGE_FOOD } from '@/constants/Image'; import type { FoodItem } from '@/types/food'; +import { Image } from 'expo-image'; // 导入统一的食物类型定义 @@ -25,12 +27,11 @@ interface NutritionInfo { carbs: number; // 碳水化合物(克) } -// 单位选项 -const UNIT_OPTIONS = [ - { id: 'gram', name: '克', ratio: 1 }, - { id: 'small', name: '小份', ratio: 0.8 }, - { id: 'medium', name: '中份', ratio: 1.2 }, - { id: 'large', name: '大份', ratio: 1.5 }, +// 快捷选择选项(基于100克的倍数) +const QUICK_SELECT_OPTIONS = [ + { id: 'small', name: '小份', amount: 80 }, // 80克 + { id: 'medium', name: '中份', amount: 120 }, // 120克 + { id: 'large', name: '大份', amount: 150 }, // 150克 ]; // 食物详情弹窗属性 @@ -41,9 +42,24 @@ export interface FoodDetailModalProps { onSave: (food: FoodItem, amount: number, unit: string) => void; } -// 模拟营养数据 -const getNutritionInfo = (foodId: string): NutritionInfo => { - const nutritionData: Record = { +// 获取营养数据,优先使用FoodItem中的数据 +const getNutritionInfo = (food: FoodItem): NutritionInfo => { + // 检查是否有任何营养数据 + const hasNutritionData = food.protein !== undefined || + food.fat !== undefined || + food.carbohydrate !== undefined; + + if (hasNutritionData) { + // 优先使用FoodItem中的营养数据 + return { + protein: food.protein || 0, + fat: food.fat || 0, + carbs: food.carbohydrate || 0, + }; + } + + // 如果FoodItem中没有营养数据,使用模拟数据作为后备 + const fallbackNutritionData: Record = { '5': { protein: 0.8, fat: 0.6, carbs: 14.5 }, // 猕猴桃 '1': { protein: 0.2, fat: 0.0, carbs: 0.1 }, // 咖啡 '2': { protein: 12.8, fat: 11.1, carbs: 0.7 }, // 荷包蛋 @@ -56,7 +72,7 @@ const getNutritionInfo = (foodId: string): NutritionInfo => { '10': { protein: 4.0, fat: 1.2, carbs: 22.8 }, // 玉米 }; - return nutritionData[foodId] || { protein: 0, fat: 0, carbs: 0 }; + return fallbackNutritionData[food.id] || { protein: 0, fat: 0, carbs: 0 }; }; export function FoodDetailModal({ @@ -66,7 +82,6 @@ export function FoodDetailModal({ onSave }: FoodDetailModalProps) { const [amount, setAmount] = useState('100'); - const [selectedUnit, setSelectedUnit] = useState(UNIT_OPTIONS[0]); const [isFavorite, setIsFavorite] = useState(false); const [keyboardHeight, setKeyboardHeight] = useState(0); @@ -98,7 +113,6 @@ export function FoodDetailModal({ if (visible && food) { console.log('FoodDetailModal: 接收到食物数据:', food); setAmount('100'); - setSelectedUnit(UNIT_OPTIONS[0]); setIsFavorite(false); } }, [visible, food]); @@ -116,19 +130,22 @@ export function FoodDetailModal({ return null; } - const nutrition = getNutritionInfo(food.id); + const nutrition = getNutritionInfo(food); const amountNum = parseFloat(amount) || 0; - const unitRatio = selectedUnit.ratio; - const finalAmount = amountNum * unitRatio; - // 计算实际营养值 - const actualCalories = Math.round((food.calories * finalAmount) / 100); - const actualProtein = ((nutrition.protein * finalAmount) / 100).toFixed(1); - const actualFat = ((nutrition.fat * finalAmount) / 100).toFixed(1); - const actualCarbs = ((nutrition.carbs * finalAmount) / 100).toFixed(1); + // 计算实际营养值(基于输入的克数) + const actualCalories = Math.round((food.calories * amountNum) / 100); + const actualProtein = ((nutrition.protein * amountNum) / 100).toFixed(1); + const actualFat = ((nutrition.fat * amountNum) / 100).toFixed(1); + const actualCarbs = ((nutrition.carbs * amountNum) / 100).toFixed(1); + + // 快捷选择处理函数 + const handleQuickSelect = (selectedAmount: number) => { + setAmount(selectedAmount.toString()); + }; const handleSave = () => { - onSave(food, finalAmount, selectedUnit.name); + onSave(food, amountNum, '克'); onClose(); }; @@ -180,7 +197,10 @@ export function FoodDetailModal({ {/* 食物信息 */} - {food.emoji || '🍽️'} + {food.name} - + + + + - {/* 单位选择 */} - - {UNIT_OPTIONS.map((unit) => ( + {/* 快捷选择 */} + + {QUICK_SELECT_OPTIONS.map((option) => ( setSelectedUnit(unit)} + onPress={() => handleQuickSelect(option.amount)} > - {unit.name} + {option.name} ))} @@ -329,6 +352,11 @@ const styles = StyleSheet.create({ alignItems: 'center', flex: 1, }, + foodImage: { + width: 40, + height: 40, + marginRight: 12, + }, foodEmoji: { fontSize: 40, marginRight: 12, @@ -366,25 +394,36 @@ const styles = StyleSheet.create({ alignItems: 'center', paddingVertical: 16, }, - amountInput: { + inputWithUnit: { + flexDirection: 'row', + alignItems: 'center', backgroundColor: '#E8F5E8', borderRadius: 12, - paddingHorizontal: 24, + paddingHorizontal: 20, paddingVertical: 16, + }, + amountInput: { fontSize: 24, fontWeight: '600', color: '#4CAF50', textAlign: 'center', - minWidth: 120, + minWidth: 80, + backgroundColor: 'transparent', }, - unitContainer: { + unitLabel: { + fontSize: 18, + fontWeight: '500', + color: '#4CAF50', + marginLeft: 8, + }, + quickSelectContainer: { flexDirection: 'row', justifyContent: 'center', paddingHorizontal: screenWidth > 400 ? 24 : 16, paddingBottom: 20, gap: screenWidth > 400 ? 16 : 12, // 根据屏幕宽度调整间距 }, - unitOption: { + quickSelectOption: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, @@ -392,15 +431,15 @@ const styles = StyleSheet.create({ minWidth: 60, alignItems: 'center', }, - unitOptionActive: { + quickSelectOptionActive: { backgroundColor: '#4CAF50', }, - unitText: { + quickSelectText: { fontSize: 14, color: '#666', fontWeight: '500', }, - unitTextActive: { + quickSelectTextActive: { color: '#FFFFFF', }, saveButton: { diff --git a/constants/Image.ts b/constants/Image.ts new file mode 100644 index 0000000..bc6d4de --- /dev/null +++ b/constants/Image.ts @@ -0,0 +1 @@ +export const DEFAULT_IMAGE_FOOD = 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/icons/food/icon-food-default.png' \ No newline at end of file