diff --git a/components/NutritionRadarCard.tsx b/components/NutritionRadarCard.tsx
index 1d123ba..48b1ceb 100644
--- a/components/NutritionRadarCard.tsx
+++ b/components/NutritionRadarCard.tsx
@@ -4,9 +4,10 @@ import { NutritionSummary } from '@/services/dietRecords';
import { Ionicons } from '@expo/vector-icons';
import dayjs from 'dayjs';
import { router } from 'expo-router';
-import React, { useMemo } from 'react';
+import React, { useMemo, useState } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { RadarCategory, RadarChart } from './RadarChart';
+import { FoodItem, FoodLibraryModal } from './model/food/FoodLibraryModal';
export type NutritionRadarCardProps = {
nutritionSummary: NutritionSummary | null;
@@ -37,6 +38,8 @@ export function NutritionRadarCard({
resetToken,
onMealPress
}: NutritionRadarCardProps) {
+ const [showFoodLibrary, setShowFoodLibrary] = useState(false);
+ const [currentMealType, setCurrentMealType] = useState<'breakfast' | 'lunch' | 'dinner' | 'snack'>('breakfast');
const radarValues = useMemo(() => {
// 基于推荐日摄入量计算分数
const recommendations = {
@@ -111,15 +114,24 @@ export function NutritionRadarCard({
router.push(ROUTES.NUTRITION_RECORDS);
};
+ const handleAddFood = () => {
+ setShowFoodLibrary(true);
+ };
+
+ const handleSelectFood = (food: FoodItem) => {
+ console.log('选择了食物:', food);
+ // 这里可以添加将食物添加到营养记录的逻辑
+ };
+
return (
营养摄入分析
更新: {dayjs(nutritionSummary?.updatedAt).format('MM-DD HH:mm')}
-
+
-
+
@@ -180,6 +192,14 @@ export function NutritionRadarCard({
+
+ {/* 食物库弹窗 */}
+ setShowFoodLibrary(false)}
+ onSelectFood={handleSelectFood}
+ mealType={currentMealType}
+ />
);
}
diff --git a/components/model/food/FoodLibraryModal.tsx b/components/model/food/FoodLibraryModal.tsx
new file mode 100644
index 0000000..8e1453c
--- /dev/null
+++ b/components/model/food/FoodLibraryModal.tsx
@@ -0,0 +1,445 @@
+import { Ionicons } from '@expo/vector-icons';
+import React, { useState } from 'react';
+import {
+ Modal,
+ SafeAreaView,
+ ScrollView,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TextInput,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+
+// 食物数据类型
+export interface FoodItem {
+ id: string;
+ name: string;
+ emoji: string;
+ calories: number;
+ unit: string; // 单位,如 "100克"
+}
+
+// 食物分类类型
+export interface FoodCategory {
+ id: string;
+ name: string;
+ foods: FoodItem[];
+}
+
+// 食物库弹窗属性
+export interface FoodLibraryModalProps {
+ visible: boolean;
+ onClose: () => void;
+ onSelectFood: (food: FoodItem) => void;
+ mealType?: 'breakfast' | 'lunch' | 'dinner' | 'snack';
+}
+
+// 模拟食物数据
+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 = {
+ breakfast: '早餐',
+ lunch: '午餐',
+ dinner: '晚餐',
+ snack: '加餐'
+};
+
+export function FoodLibraryModal({
+ visible,
+ onClose,
+ onSelectFood,
+ mealType = 'breakfast'
+}: FoodLibraryModalProps) {
+ const [selectedCategoryId, setSelectedCategoryId] = useState('common');
+ const [searchText, setSearchText] = useState('');
+
+ // 获取当前选中的分类
+ const selectedCategory = FOOD_DATA.find(cat => cat.id === selectedCategoryId);
+
+ // 过滤食物列表
+ const filteredFoods = selectedCategory?.foods.filter(food =>
+ food.name.toLowerCase().includes(searchText.toLowerCase())
+ ) || [];
+
+ // 处理食物选择
+ const handleSelectFood = (food: FoodItem) => {
+ onSelectFood(food);
+ onClose();
+ };
+
+ return (
+
+
+
+
+ {/* 头部 */}
+
+
+
+
+ 食物库
+
+ 自定义
+
+
+
+ {/* 搜索框 */}
+
+
+
+
+
+ {/* 主要内容区域 - 卡片样式 */}
+
+
+ {/* 左侧分类导航 */}
+
+
+ {FOOD_DATA.map((category) => (
+ setSelectedCategoryId(category.id)}
+ >
+
+ {category.name}
+
+
+ ))}
+
+
+
+ {/* 右侧食物列表 */}
+
+
+ {filteredFoods.map((food) => (
+
+
+ {food.emoji}
+
+ {food.name}
+
+ {food.calories}千卡/{food.unit}
+
+
+
+ handleSelectFood(food)}
+ >
+
+
+
+ ))}
+
+ {filteredFoods.length === 0 && (
+
+ 暂无食物数据
+
+ )}
+
+
+
+
+
+ {/* 底部餐次选择和记录按钮 */}
+
+
+
+ {MEAL_TYPE_MAP[mealType]}
+
+
+
+
+ 记录
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ 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,
+ },
+ customButtonText: {
+ fontSize: 16,
+ color: '#4CAF50',
+ 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',
+ borderRightWidth: 1,
+ borderRightColor: '#E5E5E5',
+ },
+ categoryItem: {
+ paddingVertical: 16,
+ paddingHorizontal: 12,
+ alignItems: 'center',
+ },
+ categoryItemActive: {
+ backgroundColor: '#F0F9FF',
+ borderRightWidth: 2,
+ borderRightColor: '#4CAF50',
+ },
+ categoryText: {
+ fontSize: 14,
+ color: '#666',
+ textAlign: 'center',
+ },
+ categoryTextActive: {
+ color: '#4CAF50',
+ 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,
+ },
+ foodEmoji: {
+ fontSize: 32,
+ marginRight: 12,
+ },
+ foodDetails: {
+ flex: 1,
+ },
+ 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',
+ },
+ 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: '#4CAF50',
+ paddingHorizontal: 24,
+ paddingVertical: 10,
+ borderRadius: 20,
+ },
+ recordButtonText: {
+ fontSize: 16,
+ color: '#FFF',
+ fontWeight: '500',
+ },
+});
\ No newline at end of file
diff --git a/components/model/food/README.md b/components/model/food/README.md
new file mode 100644
index 0000000..e4432f2
--- /dev/null
+++ b/components/model/food/README.md
@@ -0,0 +1,109 @@
+# 食物库弹窗组件
+
+## 功能概述
+
+食物库弹窗组件 (`FoodLibraryModal`) 是一个完整的食物选择界面,用户可以通过分类浏览和搜索来选择食物。
+
+## 主要特性
+
+- ✅ 完全按照设计图还原的UI界面
+- ✅ 左侧分类导航,右侧食物列表
+- ✅ 搜索功能
+- ✅ 食物信息显示(名称、卡路里、单位)
+- ✅ 餐次选择(早餐、午餐、晚餐、加餐)
+- ✅ 自定义和收藏功能预留
+
+## 使用方式
+
+### 1. 在营养卡片中使用
+
+在 `statistics.tsx` 页面的营养摄入分析卡片右上角,点击绿色的加号按钮即可打开食物库弹窗。
+
+### 2. 组件集成
+
+```tsx
+import { FoodLibraryModal, FoodItem } from '@/components/model/food/FoodLibraryModal';
+
+// 在组件中使用
+const [showFoodLibrary, setShowFoodLibrary] = useState(false);
+
+const handleSelectFood = (food: FoodItem) => {
+ console.log('选择了食物:', food);
+ // 处理食物选择逻辑
+};
+
+ setShowFoodLibrary(false)}
+ onSelectFood={handleSelectFood}
+ mealType="breakfast"
+/>
+```
+
+## 组件结构
+
+```
+components/
+ model/
+ food/
+ ├── FoodLibraryModal.tsx # 主要弹窗组件
+ └── index.ts # 导出文件
+```
+
+## 数据结构
+
+### FoodItem
+```tsx
+interface FoodItem {
+ id: string;
+ name: string;
+ emoji: string;
+ calories: number;
+ unit: string; // 如 "100克"
+}
+```
+
+### FoodCategory
+```tsx
+interface FoodCategory {
+ id: string;
+ name: string;
+ foods: FoodItem[];
+}
+```
+
+## 食物分类
+
+- 常见
+- 自定义
+- 收藏
+- 水果蔬菜
+- 肉蛋奶
+- 豆类坚果
+- 零食饮料
+- 主食
+- 菜肴
+
+## 已实现的功能
+
+1. **分类浏览** - 左侧导航可切换不同食物分类
+2. **搜索功能** - 顶部搜索框支持食物名称搜索
+3. **食物选择** - 点击右侧加号按钮选择食物
+4. **餐次指示** - 底部显示当前餐次(早餐、午餐、晚餐、加餐)
+5. **UI动画** - 模态弹窗的滑入滑出动画
+
+## 待扩展功能
+
+1. **自定义食物** - 用户可以添加自定义食物
+2. **收藏功能** - 用户可以收藏常用食物
+3. **营养详情** - 显示更详细的营养成分信息
+4. **数据持久化** - 将选择的食物保存到营养记录中
+5. **餐次切换** - 底部餐次选择器的交互功能
+
+## 技术实现
+
+- 使用 React Native Modal 实现全屏弹窗
+- 使用 ScrollView 实现分类和食物列表的滚动
+- 使用 TouchableOpacity 实现交互反馈
+- 使用 Ionicons 图标库
+- 完全响应式设计,适配不同屏幕尺寸
\ No newline at end of file
diff --git a/components/model/food/index.ts b/components/model/food/index.ts
new file mode 100644
index 0000000..b2467a1
--- /dev/null
+++ b/components/model/food/index.ts
@@ -0,0 +1,2 @@
+export { FoodLibraryModal } from './FoodLibraryModal';
+export type { FoodCategory, FoodItem, FoodLibraryModalProps } from './FoodLibraryModal';
diff --git a/components/weight/WeightRecordCard.tsx b/components/weight/WeightRecordCard.tsx
index 3247866..239f033 100644
--- a/components/weight/WeightRecordCard.tsx
+++ b/components/weight/WeightRecordCard.tsx
@@ -65,10 +65,8 @@ export const WeightRecordCard: React.FC = ({
rightThreshold={40}
overshootRight={false}
>
- onPress?.(record)}
- activeOpacity={0.7}
>
@@ -103,7 +101,7 @@ export const WeightRecordCard: React.FC = ({
)}
-
+
);
};
diff --git a/ios/digitalpilates.xcodeproj/project.pbxproj b/ios/digitalpilates.xcodeproj/project.pbxproj
index 92928d7..d6a1fd1 100644
--- a/ios/digitalpilates.xcodeproj/project.pbxproj
+++ b/ios/digitalpilates.xcodeproj/project.pbxproj
@@ -350,7 +350,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.5;
+ MARKETING_VERSION = 1.0.6;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -388,7 +388,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.5;
+ MARKETING_VERSION = 1.0.6;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -463,10 +463,7 @@
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";
@@ -521,10 +518,7 @@
);
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/ios/digitalpilates/Info.plist b/ios/digitalpilates/Info.plist
index 2d7390d..c428f82 100644
--- a/ios/digitalpilates/Info.plist
+++ b/ios/digitalpilates/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.0.5
+ 1.0.6
CFBundleSignature
????
CFBundleURLTypes