React Native鸿蒙跨平台新增食物的表单采用TextInput组件实现,通过onChangeText绑定状态更新逻辑,并在提交时进行数据完整性校验
本文介绍了一个基于React Native开发的AI食物识别健康管理应用。该应用通过拍照上传食物,利用AI技术识别食物种类并分析其热量及营养成分,生成详细的饮食报告。应用采用React Hooks进行状态管理,使用TypeScript确保类型安全,并实现了跨平台兼容的UI组件。核心功能包括食物管理、AI识别和饮食报告生成,形成了完整的饮食管理闭环。文章重点分析了该应用的技术架构、数据结构设计以及向
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
在健康管理领域,饮食监测是一项基础但重要的功能。今天我们来深入分析一个基于 React Native 开发的 AI 食物识别应用,该应用通过拍照上传食物,利用 AI 技术识别食物的热量及营养成分,并生成详细的饮食报告,为用户的健康管理提供科学依据。
通过类型定义确保数据结构的一致性和代码的可维护性
该应用采用了 React Native 现代开发技术栈,主要包括:
- React Hooks:使用
useState管理应用状态,包括食物数据、饮食报告和用户输入等 - TypeScript:通过类型定义确保数据结构的一致性和代码的可维护性
- 跨平台组件:使用
SafeAreaView、TouchableOpacity、ScrollView、Modal、Image等实现跨平台兼容的用户界面 - 响应式布局:利用
Dimensions API获取屏幕尺寸,确保在不同设备上的良好显示效果 - 基础组件:使用
View、Text、TextInput等构建用户界面
表示食物信息,包含 ID、名称、热量、蛋白质、脂肪、碳水化合物和图片链接
应用通过 TypeScript 定义了两个核心数据类型,构建了完整的 AI 食物识别数据模型:
FoodItem:表示食物信息,包含 ID、名称、热量、蛋白质、脂肪、碳水化合物和图片链接DietReport:表示饮食报告,包含 ID、日期、总热量、总蛋白质、总脂肪、总碳水化合物和食物列表
这种强类型定义不仅提高了代码的可读性和可维护性,也为鸿蒙跨端适配提供了清晰的数据结构映射基础。
状态管理
应用采用了基于 useState 的轻量级状态管理方案,为不同功能模块分别管理状态:
- 动态数据(食物列表、饮食报告)通过
useState管理,并支持添加新数据 - 表单数据(新食物信息)通过独立的
useState管理 - 交互状态(选中的食物、模态框可见性)通过独立的
useState管理
这种模式在中小型应用中非常高效,既避免了过度设计,又保证了状态管理的清晰性。
食物管理
食物管理是应用的基础功能,主要包括:
- 展示食物列表,包括食物名称、营养成分和图片
- 添加新食物到数据库
- 选择食物进行分析
- 查看食物详细营养信息
应用使用了示例图片链接(https://example.com/apple.jpg 和 https://example.com/chicken.jpg)来模拟食物图片的展示,实际应用中可以通过相机拍照或相册选择获取真实的食物图片。
AI 食物识别是应用的核心功能,通过 AI 技术识别食物的种类和营养成分:
- 拍照上传食物图片
- AI 分析食物种类
- 计算食物的热量和营养成分
- 将识别结果添加到食物数据库
虽然示例代码中没有直接实现 AI 识别的具体逻辑,但通过数据结构和功能设计,可以看出应用预留了 AI 识别的接口和数据处理流程。
饮食报告生成功能为用户提供了详细的饮食分析:
- 基于选择的食物生成饮食报告
- 计算总热量和营养成分
- 记录饮食日期
- 展示详细的营养成分 breakdown
这一功能让用户能够清晰地了解自己的饮食情况,为健康管理提供数据支持。
应用通过卡片式布局展示食物信息和饮食报告,使用 TouchableOpacity 组件实现交互功能,点击卡片后可以查看详细信息或执行相关操作。此外,应用使用 Modal 组件展示详细信息,使用 Image 组件展示食物图片,提升了用户体验。
组件映射
在鸿蒙 OS 适配过程中,需要注意以下组件映射:
SafeAreaView:在鸿蒙上需要使用SafeArea组件或自定义适配TouchableOpacity:对应鸿蒙的Button或Text组件配合点击事件ScrollView:对应鸿蒙的ListContainer或Scroll组件Modal:对应鸿蒙的Dialog组件Alert:对应鸿蒙的ToastDialog或AlertDialog组件TextInput:对应鸿蒙的TextField组件Image:对应鸿蒙的Image组件,但需要注意图片加载方式的差异
鸿蒙 OS 对 Flexbox 布局的支持与 React Native 基本一致,但在细节上仍需注意:
- 确保样式命名符合鸿蒙规范
- 调整间距和尺寸以适应鸿蒙设备的显示特性
- 注意字体大小和行高的适配
- 对于表单输入控件,需要适配鸿蒙的输入框样式和交互方式
- 对于图片加载,需要适配鸿蒙的图片加载机制
在鸿蒙跨端开发中,性能优化是一个重要考虑因素:
- 使用
memo优化组件渲染,特别是对于食物列表、饮食报告列表等重复渲染的场景 - 合理使用
useCallback和useMemo减少不必要的计算 - 优化图片资源,考虑使用鸿蒙的资源加载机制
- 对于 AI 识别功能,考虑使用鸿蒙的 AI 能力或云服务
- 对于列表数据的渲染,考虑使用虚拟化技术减少内存占用
应用的核心创新点是集成了 AI 食物识别技术:
- 通过拍照上传食物图片
- 利用 AI 技术自动识别食物种类
- 计算食物的热量和营养成分
- 为用户提供准确的营养信息
这种 AI 驱动的食物识别技术,比传统的手动输入方式更加便捷和准确,能够更好地帮助用户进行饮食管理。
应用提供了详细的营养成分分析:
- 记录食物的热量、蛋白质、脂肪和碳水化合物
- 生成详细的饮食报告
- 计算总营养成分摄入量
- 为用户提供全面的营养信息
这种详细的营养成分分析,比简单的热量计算更加科学,能够帮助用户更全面地了解自己的饮食情况。
应用实现了完整的饮食管理闭环:
- 食物识别和管理
- 营养成分分析
- 饮食报告生成
- 历史记录查看
这种完整的饮食管理闭环,为用户提供了从食物识别到营养分析的全方位饮食管理服务。
应用采用了清晰的模块化设计:
- 功能按模块划分(食物管理、AI 识别、报告生成)
- 组件职责单一,便于维护和扩展
- 状态管理逻辑与 UI 渲染分离
Base64 图标库
应用使用 Base64 编码的图标,这种方式有几个优点:
- 减少网络请求,提高加载速度
- 避免图标资源的跨平台适配问题
- 减小应用包体积
虽然示例中使用的是占位 Base64 编码,但实际应用中可以使用真实的图标编码。
文件结构
示例代码集中在 App.tsx 文件中,适合小型应用。对于大型应用,建议按功能模块拆分文件:
-
/components:存放可复用组件,如食物卡片、报告卡片等 -
/types:存放类型定义,如 FoodItem、DietReport 等 -
/hooks:存放自定义 hooks,如食物管理逻辑、报告生成逻辑等 -
/services:存放 API 调用和业务逻辑,如 AI 识别服务、数据存储服务等 -
/utils:存放工具函数,如日期处理、数据格式化等 -
命名规范:变量和函数命名清晰,符合语义化要求
-
类型安全:使用 TypeScript 确保类型安全
-
错误处理:通过条件判断处理可能的异常情况,如表单验证
-
注释:代码结构清晰,关键部分有适当注释
-
性能考虑:合理使用 React Hooks,避免不必要的渲染和计算
应用架构具有良好的扩展性:
- 可以轻松添加新的食物类型和营养成分
- 可以集成真实的 AI 食物识别 API,提高识别准确性
- 可以扩展支持更多的营养成分分析,如维生素、矿物质等
- 可以集成云服务,实现数据的云端存储和多设备同步
- 可以添加数据可视化功能,如营养摄入趋势图表、饮食平衡分析等
这个 AI 食物识别应用展示了如何使用 React Native 构建功能完备的饮食管理工具,特别是在 AI 食物识别和详细营养成分分析方面的实践具有重要参考价值。通过跨端开发技术,可以在不同平台为用户提供一致的服务体验。
随着人们对健康管理重视程度的提高,这类 AI 食物识别应用的需求将不断增长。未来可以考虑:
- 集成更先进的 AI 食物识别技术,提高识别准确性和速度
- 与更多健康管理设备和应用对接,实现健康数据的综合分析
- 利用大数据和机器学习技术,为用户提供个性化的饮食建议
- 支持更多的食物类型和烹饪方式,提高应用的适用范围
- 开发配套的营养师端应用,为用户提供专业的饮食指导
React Native 鸿蒙跨端开发代表了移动应用开发的未来趋势,通过一套代码库覆盖多个平台,不仅可以降低开发成本,还可以确保用户体验的一致性。在健康管理应用领域,这种开发模式尤为重要,因为它可以让开发者更专注于核心功能的实现,而不是平台差异的处理。
在移动应用开发领域,跨端技术一直是提升开发效率、降低多端维护成本的核心方向。React Native 凭借“一次编写,多端运行”的特性成为跨端开发的主流选择,而鸿蒙(HarmonyOS)作为新一代分布式操作系统,也为跨端应用提供了全新的适配思路。本文将以一个完整的AI食物识别应用为例,深度解析基于React Native的应用架构设计,并探讨其向鸿蒙生态跨端适配的核心技术要点。
本次案例中的AI食物识别应用,是一个典型的React Native前端应用,涵盖了状态管理、UI组件封装、交互逻辑处理等核心能力,先从技术层面拆解其实现逻辑:
1. 状态管理设计
应用基于TypeScript开发,通过类型定义强化代码的可维护性和可读性,这也是React Native大型应用的最佳实践:
// 食物类型定义
type FoodItem = {
id: string;
name: string;
calories: number;
protein: number;
fat: number;
carbs: number;
imageUrl: string;
};
// 饮食报告类型定义
type DietReport = {
id: string;
date: string;
totalCalories: number;
totalProtein: number;
totalFat: number;
totalCarbs: number;
foods: FoodItem[];
};
状态管理层面,应用采用React核心的useState Hook实现组件内状态管理,核心状态包括:
foodItems:存储食物基础数据列表,是整个应用的核心数据源;dietReports:存储生成的饮食报告数据,关联已选食物的营养信息;selectedFood:标记当前选中的食物ID,实现交互状态的可视化反馈;newFood:管理新增食物的表单输入状态,包含名称、营养成分、图片URL等字段;isModalVisible/modalContent:控制模态框的显示状态和内容,实现详情弹窗交互。
这种状态设计遵循了React的单向数据流原则,每个状态仅服务于特定的业务逻辑,避免了状态混乱,也为后续跨端适配时的状态迁移奠定了清晰的结构基础。
2. 核心UI组件
应用基于React Native内置的基础组件(SafeAreaView、ScrollView、TouchableOpacity、Modal等)构建完整UI体系,核心交互逻辑包括:
(1)食物列表渲染与选中交互
通过map方法遍历foodItems数组渲染食物卡片,结合selectedFood状态实现选中样式的动态切换:
{foodItems.map(food => (
<TouchableOpacity
key={food.id}
style={[
styles.card,
selectedFood === food.id && styles.selectedCard // 选中状态样式
]}
onPress={() => handleSelectFood(food.id)}
>
<Image source={{ uri: food.imageUrl }} style={styles.foodImage} />
<View style={styles.cardInfo}>
<Text style={styles.cardTitle}>{food.name}</Text>
<Text style={styles.cardDescription}>热量: {food.calories} kcal</Text>
{/* 营养成分展示 */}
</View>
<TouchableOpacity
style={styles.viewButton}
onPress={() => handleViewFood(food.id)}
>
<Text style={styles.viewText}>查看详情</Text>
</TouchableOpacity>
</TouchableOpacity>
))}
这里的关键技术点是React Native的样式动态绑定,通过数组拼接的方式结合基础样式和状态样式,实现了原生级别的交互反馈,同时TouchableOpacity组件替代了原生按钮,兼顾了iOS和Android的交互体验一致性。
(2)表单处理与数据校验
新增食物的表单采用TextInput组件实现,通过onChangeText绑定状态更新逻辑,并在提交时进行数据完整性校验:
const handleAddFood = () => {
if (newFood.name && newFood.calories && newFood.protein && newFood.fat && newFood.carbs && newFood.imageUrl) {
const newFoodItem: FoodItem = {
id: (foodItems.length + 1).toString(),
name: newFood.name,
calories: parseInt(newFood.calories),
protein: parseFloat(newFood.protein),
fat: parseFloat(newFood.fat),
carbs: parseFloat(newFood.carbs),
imageUrl: newFood.imageUrl
};
setFoodItems([...foodItems, newFoodItem]);
// 重置表单状态
setNewFood({ name: '', calories: '', protein: '', fat: '', carbs: '', imageUrl: '' });
Alert.alert('添加成功', '新食物已添加到数据库中');
} else {
Alert.alert('提示', '请填写完整的食物信息');
}
};
数据类型转换(parseInt/parseFloat)和完整性校验是前端数据处理的基础,也是跨端适配时需要保持一致的核心逻辑——无论最终运行在iOS、Android还是鸿蒙系统,数据校验规则都需统一。
(3)模态框交互与报告生成
应用通过Modal组件实现详情弹窗,封装了openModal/closeModal方法控制弹窗状态;报告生成逻辑则基于选中的食物数据,计算并存储饮食报告信息:
const handleGenerateReport = () => {
if (selectedFood) {
const food = foodItems.find(f => f.id === selectedFood);
if (food) {
const newReport: DietReport = {
id: (dietReports.length + 1).toString(),
date: new Date().toISOString().split('T')[0],
totalCalories: food.calories,
totalProtein: food.protein,
totalFat: food.fat,
totalCarbs: food.carbs,
foods: [food]
};
setDietReports([...dietReports, newReport]);
Alert.alert('报告生成', '饮食报告已成功生成');
}
} else {
Alert.alert('提示', '请先选择食物');
}
};
这里的日期格式化、数据关联等逻辑是纯业务层代码,与底层平台无关,也是跨端适配中“业务逻辑复用”的核心部分。
3. 样式系统
应用通过StyleSheet.create封装样式,采用弹性布局(flex)、百分比宽度、设备尺寸适配(Dimensions.get('window'))等方式,保证在不同尺寸的移动设备上的显示一致性:
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f0f9ff',
},
card: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#f0f9ff',
borderRadius: 12,
padding: 16,
marginBottom: 12,
},
input: {
flex: 1,
backgroundColor: '#f0f9ff',
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 8,
fontSize: 14,
color: '#0c4a6e',
marginRight: 8,
},
// 其他样式定义
});
React Native的样式系统基于CSS子集,去除了CSS中与布局无关的复杂特性,同时增加了适配移动设备的属性(如elevation实现阴影),这种轻量化的样式设计为鸿蒙适配降低了样式迁移的成本。
鸿蒙系统作为分布式操作系统,支持多设备、多形态的应用部署,将React Native应用适配到鸿蒙生态,核心在于“复用业务逻辑,适配底层组件与生命周期”,以下是关键技术路径:
1. 适配方案
目前React Native向鸿蒙适配主要有两种路径:
-
方案一:基于鸿蒙JS/TS API适配
鸿蒙提供了基于JS/TS的应用开发框架,其组件生命周期、状态管理思路与React Native有相似性(如鸿蒙的@State装饰器对应React的useState)。可以将原React Native应用的业务逻辑(如foodItems状态管理、handleAddFood数据处理)完全复用,仅将UI组件替换为鸿蒙的内置组件:- React Native的
View→ 鸿蒙的div/stack - React Native的
Text→ 鸿蒙的text - React Native的
TouchableOpacity→ 鸿蒙的button/text(绑定onClick) - React Native的
Modal→ 鸿蒙的dialog组件
这种方案的核心是“逻辑复用,组件替换”,无需重构业务代码,适配成本最低,适合本案例这类轻量级应用。
- React Native的
-
方案二:基于HarmonyOS React Native 插件
华为官方提供了React Native for HarmonyOS的适配插件(https://gitee.com/openharmony-tpc/react-native),该插件实现了React Native核心组件与鸿蒙原生组件的映射,开发者可以在鸿蒙工程中直接运行React Native代码,仅需少量配置即可实现跨端运行。这种方案几乎无需修改原应用代码,适合已有成熟React Native项目的快速迁移。
(1)组件生命周期
React Native基于React的生命周期(如useEffect),而鸿蒙JS/TS框架有自己的生命周期(如onInit、onReady、onDestroy)。在适配时,需要将React的Hook逻辑映射到鸿蒙的生命周期中:
- React的
useState→ 鸿蒙的@State/@Link装饰器 - React的
useEffect(无依赖) → 鸿蒙的onReady生命周期 - React的
useEffect(带依赖) → 鸿蒙的watch监听属性变化
以食物列表状态为例,鸿蒙中的适配代码示例:
// 鸿蒙TS代码
@Entry
@Component
struct AIFoodRecognitionApp {
@State foodItems: FoodItem[] = [
{
id: '1',
name: '苹果',
calories: 52,
protein: 0.3,
fat: 0.2,
carbs: 14,
imageUrl: 'https://example.com/apple.jpg'
}
];
// 对应React Native的handleAddFood
handleAddFood(newFood: Omit<FoodItem, 'id'>) {
const newFoodItem: FoodItem = {
id: (this.foodItems.length + 1).toString(),
...newFood
};
this.foodItems = [...this.foodItems, newFoodItem];
}
build() {
Column() {
// 渲染食物列表
ForEach(this.foodItems, (food) => {
Stack({ direction: Direction.Horizontal }) {
Image(food.imageUrl).width(60).height(60).borderRadius(8);
Column() {
Text(food.name).fontSize(16).fontWeight(FontWeight.Medium);
Text(`热量: ${food.calories} kcal`).fontSize(14);
}
Button('查看详情').onClick(() => this.handleViewFood(food.id));
}
})
}
}
}
(2)样式
React Native的StyleSheet在鸿蒙中可替换为内联样式或@Styles装饰器,布局系统方面,鸿蒙的Flex布局与React Native的flex布局语法高度一致,仅需少量调整:
- React Native的
flexDirection: 'row'→ 鸿蒙的flexDirection: FlexDirection.Row - React Native的
alignItems: 'center'→ 鸿蒙的alignItems: ItemAlign.Center - React Native的
marginHorizontal→ 鸿蒙的marginLeft+marginRight(或自定义样式变量)
本案例中的卡片布局、表单布局等核心样式,几乎可以无缝迁移到鸿蒙的Flex布局中,仅需调整样式属性的命名规范。
React Native通过原生模块(Native Modules)调用设备能力(如相机、相册),而鸿蒙通过@ohos.multimedia.camera、@ohos.fileio等API实现相同功能。在本案例中,“拍照上传食物图片”功能的适配需要替换原生能力调用方式:
- React Native的相机插件(如
react-native-camera) → 鸿蒙的cameraAPI - React Native的图片选择插件(如
react-native-image-picker) → 鸿蒙的photoAccessHelperAPI
业务逻辑层面(如图片上传后的AI识别)完全复用,仅需替换底层的设备能力调用代码。
鸿蒙的核心优势是分布式能力,适配后的应用可以利用鸿蒙的分布式数据管理、分布式任务调度等特性,扩展原React Native应用的能力:
- 分布式数据管理:将
foodItems、dietReports等状态存储到鸿蒙的分布式数据中心,实现多设备(手机、平板、智慧屏)的数据同步; - 分布式任务调度:将AI食物识别的计算任务分发到性能更强的设备(如平板、PC)执行,提升识别效率;
- 跨设备交互:支持在手机上选择食物,在智慧屏上查看饮食报告,实现多设备协同交互。
这些扩展能力无需修改原业务逻辑,仅需在鸿蒙适配层增加分布式能力的调用,即可提升应用的跨端体验。
基于本案例的React Native应用及鸿蒙适配分析,跨端开发的核心最佳实践可总结为:
- 业务逻辑与UI解耦:像本案例一样,将数据处理、状态管理等核心业务逻辑与UI组件解耦,确保业务逻辑可在不同平台复用;
- 采用标准化技术栈:TypeScript的强类型定义不仅提升代码质量,也为跨端适配提供了清晰的接口规范;
- 优先复用核心逻辑:跨端适配的核心是“复用业务逻辑,适配底层组件”,避免重复开发相同的业务代码;
- 利用平台特性扩展能力:在适配鸿蒙时,不仅要实现功能兼容,更要利用其分布式特性扩展应用能力,提升跨端体验。
本文以AI食物识别应用为例,深度解析了React Native应用的核心技术架构,包括TypeScript类型设计、状态管理、UI组件封装、交互逻辑实现等,并详细探讨了向鸿蒙系统跨端适配的核心技术路径。React Native的“一次编写,多端运行”理念与鸿蒙的分布式跨端能力相结合,既可以复用已有React Native项目的开发成果,又能借助鸿蒙的特性实现更丰富的跨端体验。对于开发者而言,掌握这种跨端适配思路,能够有效降低多端应用的开发成本,提升应用的覆盖范围和用户体验。
真实演示案例代码:
// App.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, TextInput, Modal, Image } from 'react-native';
// Base64 图标库
const ICONS_BASE64 = {
camera: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
food: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
report: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
ai: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};
const { width, height } = Dimensions.get('window');
// 食物类型
type FoodItem = {
id: string;
name: string;
calories: number;
protein: number;
fat: number;
carbs: number;
imageUrl: string;
};
// 饮食报告类型
type DietReport = {
id: string;
date: string;
totalCalories: number;
totalProtein: number;
totalFat: number;
totalCarbs: number;
foods: FoodItem[];
};
// AI食物识别应用组件
const AIFoodRecognitionApp: React.FC = () => {
const [foodItems, setFoodItems] = useState<FoodItem[]>([
{
id: '1',
name: '苹果',
calories: 52,
protein: 0.3,
fat: 0.2,
carbs: 14,
imageUrl: 'https://example.com/apple.jpg'
},
{
id: '2',
name: '鸡胸肉',
calories: 165,
protein: 31,
fat: 3.6,
carbs: 0,
imageUrl: 'https://example.com/chicken.jpg'
}
]);
const [dietReports, setDietReports] = useState<DietReport[]>([
{
id: '1',
date: '2023-12-01',
totalCalories: 217,
totalProtein: 31.3,
totalFat: 3.8,
totalCarbs: 14,
foods: [
{
id: '1',
name: '苹果',
calories: 52,
protein: 0.3,
fat: 0.2,
carbs: 14,
imageUrl: 'https://example.com/apple.jpg'
},
{
id: '2',
name: '鸡胸肉',
calories: 165,
protein: 31,
fat: 3.6,
carbs: 0,
imageUrl: 'https://example.com/chicken.jpg'
}
]
}
]);
const [selectedFood, setSelectedFood] = useState<string | null>(null);
const [newFood, setNewFood] = useState({
name: '',
calories: '',
protein: '',
fat: '',
carbs: '',
imageUrl: ''
});
const [isModalVisible, setIsModalVisible] = useState(false);
const [modalContent, setModalContent] = useState('');
const handleSelectFood = (foodId: string) => {
setSelectedFood(foodId);
Alert.alert('选择食物', '您已选择该食物进行分析');
};
const handleAddFood = () => {
if (newFood.name && newFood.calories && newFood.protein && newFood.fat && newFood.carbs && newFood.imageUrl) {
const newFoodItem: FoodItem = {
id: (foodItems.length + 1).toString(),
name: newFood.name,
calories: parseInt(newFood.calories),
protein: parseFloat(newFood.protein),
fat: parseFloat(newFood.fat),
carbs: parseFloat(newFood.carbs),
imageUrl: newFood.imageUrl
};
setFoodItems([...foodItems, newFoodItem]);
setNewFood({ name: '', calories: '', protein: '', fat: '', carbs: '', imageUrl: '' });
Alert.alert('添加成功', '新食物已添加到数据库中');
} else {
Alert.alert('提示', '请填写完整的食物信息');
}
};
const handleGenerateReport = () => {
if (selectedFood) {
const food = foodItems.find(f => f.id === selectedFood);
if (food) {
const newReport: DietReport = {
id: (dietReports.length + 1).toString(),
date: new Date().toISOString().split('T')[0],
totalCalories: food.calories,
totalProtein: food.protein,
totalFat: food.fat,
totalCarbs: food.carbs,
foods: [food]
};
setDietReports([...dietReports, newReport]);
Alert.alert('报告生成', '饮食报告已成功生成');
}
} else {
Alert.alert('提示', '请先选择食物');
}
};
const handleViewReport = (reportId: string) => {
const report = dietReports.find(r => r.id === reportId);
if (report) {
setModalContent(`日期: ${report.date}\n总热量: ${report.totalCalories} kcal\n总蛋白质: ${report.totalProtein} g\n总脂肪: ${report.totalFat} g\n总碳水化合物: ${report.totalCarbs} g`);
setIsModalVisible(true);
}
};
const handleViewFood = (foodId: string) => {
const food = foodItems.find(f => f.id === foodId);
if (food) {
setModalContent(`食物名称: ${food.name}\n热量: ${food.calories} kcal\n蛋白质: ${food.protein} g\n脂肪: ${food.fat} g\n碳水化合物: ${food.carbs} g`);
setIsModalVisible(true);
}
};
const openModal = (content: string) => {
setModalContent(content);
setIsModalVisible(true);
};
const closeModal = () => {
setIsModalVisible(false);
};
return (
<SafeAreaView style={styles.container}>
{/* 头部 */}
<View style={styles.header}>
<Text style={styles.title}>AI食物识别</Text>
<Text style={styles.subtitle}>用户拍照上传食物,AI识别热量及营养成分,生成饮食报告</Text>
</View>
<ScrollView style={styles.content}>
{/* 食物列表 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>食物列表</Text>
{foodItems.map(food => (
<TouchableOpacity
key={food.id}
style={[
styles.card,
selectedFood === food.id && styles.selectedCard
]}
onPress={() => handleSelectFood(food.id)}
>
<Image source={{ uri: food.imageUrl }} style={styles.foodImage} />
<View style={styles.cardInfo}>
<Text style={styles.cardTitle}>{food.name}</Text>
<Text style={styles.cardDescription}>热量: {food.calories} kcal</Text>
<Text style={styles.cardDescription}>蛋白质: {food.protein} g</Text>
<Text style={styles.cardDescription}>脂肪: {food.fat} g</Text>
<Text style={styles.cardDescription}>碳水化合物: {food.carbs} g</Text>
</View>
<TouchableOpacity
style={styles.viewButton}
onPress={() => handleViewFood(food.id)}
>
<Text style={styles.viewText}>查看详情</Text>
</TouchableOpacity>
</TouchableOpacity>
))}
</View>
{/* 添加食物 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>添加食物</Text>
<View style={styles.inputRow}>
<TextInput
style={styles.input}
placeholder="食物名称"
value={newFood.name}
onChangeText={(text) => setNewFood({ ...newFood, name: text })}
/>
<TextInput
style={styles.input}
placeholder="热量 (kcal)"
value={newFood.calories}
onChangeText={(text) => setNewFood({ ...newFood, calories: text })}
keyboardType="numeric"
/>
</View>
<View style={styles.inputRow}>
<TextInput
style={styles.input}
placeholder="蛋白质 (g)"
value={newFood.protein}
onChangeText={(text) => setNewFood({ ...newFood, protein: text })}
keyboardType="numeric"
/>
<TextInput
style={styles.input}
placeholder="脂肪 (g)"
value={newFood.fat}
onChangeText={(text) => setNewFood({ ...newFood, fat: text })}
keyboardType="numeric"
/>
</View>
<View style={styles.inputRow}>
<TextInput
style={styles.input}
placeholder="碳水化合物 (g)"
value={newFood.carbs}
onChangeText={(text) => setNewFood({ ...newFood, carbs: text })}
keyboardType="numeric"
/>
<TextInput
style={styles.input}
placeholder="图片URL"
value={newFood.imageUrl}
onChangeText={(text) => setNewFood({ ...newFood, imageUrl: text })}
/>
</View>
<TouchableOpacity
style={styles.addButton}
onPress={handleAddFood}
>
<Text style={styles.addText}>添加食物</Text>
</TouchableOpacity>
</View>
{/* 生成报告 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>生成报告</Text>
<TouchableOpacity
style={styles.generateButton}
onPress={handleGenerateReport}
>
<Text style={styles.generateText}>生成饮食报告</Text>
</TouchableOpacity>
</View>
{/* 饮食报告 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>饮食报告</Text>
{dietReports.map(report => (
<TouchableOpacity
key={report.id}
style={styles.reportCard}
onPress={() => handleViewReport(report.id)}
>
<Text style={styles.icon}>📊</Text>
<View style={styles.cardInfo}>
<Text style={styles.cardTitle}>报告ID: {report.id}</Text>
<Text style={styles.cardDescription}>日期: {report.date}</Text>
<Text style={styles.cardDescription}>总热量: {report.totalCalories} kcal</Text>
<Text style={styles.cardDescription}>总蛋白质: {report.totalProtein} g</Text>
<Text style={styles.cardDescription}>总脂肪: {report.totalFat} g</Text>
<Text style={styles.cardDescription}>总碳水化合物: {report.totalCarbs} g</Text>
</View>
</TouchableOpacity>
))}
</View>
{/* 使用说明 */}
<View style={styles.infoCard}>
<Text style={styles.sectionTitle}>📘 使用说明</Text>
<Text style={styles.infoText}>• 拍照上传食物图片</Text>
<Text style={styles.infoText}>• AI自动识别食物热量及营养成分</Text>
<Text style={styles.infoText}>• 生成详细的饮食报告</Text>
<Text style={styles.infoText}>• 帮助用户科学管理饮食</Text>
</View>
{/* 弹框内容 */}
<Modal
animationType="slide"
transparent={true}
visible={isModalVisible}
onRequestClose={closeModal}
>
<View style={styles.modalContainer}>
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>详细信息</Text>
<Text style={styles.modalText}>{modalContent}</Text>
<TouchableOpacity
style={styles.closeButton}
onPress={closeModal}
>
<Text style={styles.closeButtonText}>关闭</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f0f9ff',
},
header: {
flexDirection: 'column',
padding: 16,
backgroundColor: '#ffffff',
borderBottomWidth: 1,
borderBottomColor: '#bae6fd',
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#0c4a6e',
marginBottom: 4,
},
subtitle: {
fontSize: 14,
color: '#0284c7',
},
content: {
flex: 1,
marginTop: 12,
},
section: {
backgroundColor: '#ffffff',
marginHorizontal: 16,
marginBottom: 12,
borderRadius: 12,
padding: 16,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
color: '#0c4a6e',
marginBottom: 12,
},
card: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#f0f9ff',
borderRadius: 12,
padding: 16,
marginBottom: 12,
},
selectedCard: {
borderWidth: 2,
borderColor: '#0284c7',
},
reportCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#f0f9ff',
borderRadius: 12,
padding: 16,
marginBottom: 12,
},
foodImage: {
width: 60,
height: 60,
borderRadius: 8,
marginRight: 12,
},
icon: {
fontSize: 28,
marginRight: 12,
},
cardInfo: {
flex: 1,
},
cardTitle: {
fontSize: 16,
fontWeight: '500',
color: '#0c4a6e',
marginBottom: 4,
},
cardDescription: {
fontSize: 14,
color: '#0284c7',
marginBottom: 2,
},
viewButton: {
backgroundColor: '#0284c7',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 8,
},
viewText: {
color: '#ffffff',
fontSize: 12,
fontWeight: '500',
},
inputRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 12,
},
input: {
flex: 1,
backgroundColor: '#f0f9ff',
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 8,
fontSize: 14,
color: '#0c4a6e',
marginRight: 8,
},
addButton: {
backgroundColor: '#0284c7',
padding: 12,
borderRadius: 8,
alignItems: 'center',
},
addText: {
color: '#ffffff',
fontSize: 14,
fontWeight: '500',
},
generateButton: {
backgroundColor: '#10b981',
padding: 16,
borderRadius: 12,
alignItems: 'center',
},
generateText: {
color: '#ffffff',
fontSize: 16,
fontWeight: '500',
},
infoCard: {
backgroundColor: '#ffffff',
marginHorizontal: 16,
marginBottom: 80,
borderRadius: 12,
padding: 16,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
infoText: {
fontSize: 14,
color: '#64748b',
lineHeight: 20,
marginBottom: 4,
},
modalContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
modalContent: {
width: '80%',
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 20,
elevation: 5,
},
modalTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#0c4a6e',
marginBottom: 12,
textAlign: 'center',
},
modalText: {
fontSize: 14,
color: '#0c4a6e',
lineHeight: 20,
marginBottom: 20,
},
closeButton: {
backgroundColor: '#0284c7',
padding: 10,
borderRadius: 8,
alignItems: 'center',
},
closeButtonText: {
color: '#ffffff',
fontSize: 14,
fontWeight: '500',
},
});
export default AIFoodRecognitionApp;

打包
接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

最后运行效果图如下显示:

本文介绍了一个基于React Native开发的AI食物识别健康管理应用。该应用通过拍照上传食物,利用AI技术识别食物种类并分析其热量及营养成分,生成详细的饮食报告。应用采用React Hooks进行状态管理,使用TypeScript确保类型安全,并实现了跨平台兼容的UI组件。核心功能包括食物管理、AI识别和饮食报告生成,形成了完整的饮食管理闭环。文章重点分析了该应用的技术架构、数据结构设计以及向鸿蒙OS的跨端适配方案,包括组件映射、布局适配和性能优化策略。该案例展示了React Native在健康管理领域的应用实践,为跨平台开发提供了重要参考价值。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐




所有评论(0)