欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。


在健康管理领域,饮食监测是一项基础但重要的功能。今天我们来深入分析一个基于 React Native 开发的 AI 食物识别应用,该应用通过拍照上传食物,利用 AI 技术识别食物的热量及营养成分,并生成详细的饮食报告,为用户的健康管理提供科学依据。

通过类型定义确保数据结构的一致性和代码的可维护性

该应用采用了 React Native 现代开发技术栈,主要包括:

  • React Hooks:使用 useState 管理应用状态,包括食物数据、饮食报告和用户输入等
  • TypeScript:通过类型定义确保数据结构的一致性和代码的可维护性
  • 跨平台组件:使用 SafeAreaViewTouchableOpacityScrollViewModalImage 等实现跨平台兼容的用户界面
  • 响应式布局:利用 Dimensions API 获取屏幕尺寸,确保在不同设备上的良好显示效果
  • 基础组件:使用 ViewTextTextInput 等构建用户界面

表示食物信息,包含 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:对应鸿蒙的 ButtonText 组件配合点击事件
  • ScrollView:对应鸿蒙的 ListContainerScroll 组件
  • Modal:对应鸿蒙的 Dialog 组件
  • Alert:对应鸿蒙的 ToastDialogAlertDialog 组件
  • TextInput:对应鸿蒙的 TextField 组件
  • Image:对应鸿蒙的 Image 组件,但需要注意图片加载方式的差异

鸿蒙 OS 对 Flexbox 布局的支持与 React Native 基本一致,但在细节上仍需注意:

  • 确保样式命名符合鸿蒙规范
  • 调整间距和尺寸以适应鸿蒙设备的显示特性
  • 注意字体大小和行高的适配
  • 对于表单输入控件,需要适配鸿蒙的输入框样式和交互方式
  • 对于图片加载,需要适配鸿蒙的图片加载机制

在鸿蒙跨端开发中,性能优化是一个重要考虑因素:

  • 使用 memo 优化组件渲染,特别是对于食物列表、饮食报告列表等重复渲染的场景
  • 合理使用 useCallbackuseMemo 减少不必要的计算
  • 优化图片资源,考虑使用鸿蒙的资源加载机制
  • 对于 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内置的基础组件(SafeAreaViewScrollViewTouchableOpacityModal等)构建完整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组件
      这种方案的核心是“逻辑复用,组件替换”,无需重构业务代码,适配成本最低,适合本案例这类轻量级应用。
  • 方案二:基于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框架有自己的生命周期(如onInitonReadyonDestroy)。在适配时,需要将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) → 鸿蒙的camera API
  • React Native的图片选择插件(如react-native-image-picker) → 鸿蒙的photoAccessHelper API

业务逻辑层面(如图片上传后的AI识别)完全复用,仅需替换底层的设备能力调用代码。

鸿蒙的核心优势是分布式能力,适配后的应用可以利用鸿蒙的分布式数据管理、分布式任务调度等特性,扩展原React Native应用的能力:

  • 分布式数据管理:将foodItemsdietReports等状态存储到鸿蒙的分布式数据中心,实现多设备(手机、平板、智慧屏)的数据同步;
  • 分布式任务调度:将AI食物识别的计算任务分发到性能更强的设备(如平板、PC)执行,提升识别效率;
  • 跨设备交互:支持在手机上选择食物,在智慧屏上查看饮食报告,实现多设备协同交互。

这些扩展能力无需修改原业务逻辑,仅需在鸿蒙适配层增加分布式能力的调用,即可提升应用的跨端体验。


基于本案例的React Native应用及鸿蒙适配分析,跨端开发的核心最佳实践可总结为:

  1. 业务逻辑与UI解耦:像本案例一样,将数据处理、状态管理等核心业务逻辑与UI组件解耦,确保业务逻辑可在不同平台复用;
  2. 采用标准化技术栈:TypeScript的强类型定义不仅提升代码质量,也为跨端适配提供了清晰的接口规范;
  3. 优先复用核心逻辑:跨端适配的核心是“复用业务逻辑,适配底层组件”,避免重复开发相同的业务代码;
  4. 利用平台特性扩展能力:在适配鸿蒙时,不仅要实现功能兼容,更要利用其分布式特性扩展应用能力,提升跨端体验。

本文以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在健康管理领域的应用实践,为跨平台开发提供了重要参考价值。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐