在健康管理类跨端应用开发领域,React Native 凭借其“一次开发、多端部署”的核心优势,成为打通iOS、Android与鸿蒙系统的关键技术载体。本文将深度拆解基于React Native开发的减重小组应用代码,从数据模型设计、状态管理、UI组件适配到鸿蒙跨端兼容的核心技术要点,全方位剖析健康管理类跨端应用的开发范式与最佳实践。

应用架构设计

这款减重小组应用聚焦于健康管理场景下的核心需求,涵盖用户体重管理、减重小组协作、饮食/运动数据记录、专业营养师指导等全流程功能。从架构层面来看,整个应用严格遵循React Native的组件化设计思想,且全程采用TypeScript进行类型约束,这不仅提升了代码的可维护性,更是实现鸿蒙系统兼容的核心基础。

从跨端适配的底层逻辑分析,代码中未引入任何平台专属API(如iOS的HealthKit、Android的Google Fit或鸿蒙的Health Ability),所有核心功能均基于React Native的通用核心组件(SafeAreaView、View、Text、TextInput、Modal等)和基础API(Dimensions、Alert、TouchableOpacity)构建。这种设计完全契合React Native的跨端理念——通过JS桥接层将统一的组件抽象映射为不同平台的原生控件,在鸿蒙系统中,这些组件会被React Native for HarmonyOS框架转换为ArkUI原生组件,从而保证UI渲染和交互逻辑在鸿蒙设备上的一致性,避免了因平台差异导致的代码碎片化问题。

TypeScript

健康管理类应用对数据准确性要求严苛,代码中通过完整的TypeScript类型定义构建了严谨的领域数据模型,这不仅规避了前端开发中的类型错误,更在跨端编译阶段提前拦截了因平台数据格式差异导致的潜在问题。

// 用户核心数据模型
type User = {
  id: string;
  name: string;
  weight: number;
  goalWeight: number;
};

// 减重小组关联模型
type WeightLossGroup = {
  id: string;
  name: string;
  members: string[];
  startDate: string;
  endDate: string;
};

// 饮食/运动记录模型
type DietRecord = {
  id: string;
  userId: string;
  date: string;
  food: string;
  calories: number;
};

type ExerciseRecord = {
  id: string;
  userId: string;
  date: string;
  activity: string;
  duration: number;
  caloriesBurned: number;
};

// 营养师专业信息模型
type Nutritionist = {
  id: string;
  name: string;
  specialty: string;
  experience: string;
  rating: number;
};

这些类型定义构建了完整的健康管理数据体系,所有状态数据均严格遵循对应的类型约束。在鸿蒙系统中,React Native的TypeScript编译器会对JS层与鸿蒙原生层之间的数据交互进行类型校验,避免了因鸿蒙ArkTS的静态类型系统与JavaScript动态类型特性不兼容导致的运行时错误。值得注意的是,所有字段均采用标准的string/number基础类型,未使用任何平台专属的复杂数据类型,这是保证跨端数据序列化/反序列化一致性的关键设计,确保体重、热量、时长等核心健康数据在鸿蒙设备上的精准传递与展示。


应用的核心业务逻辑基于React Hooks(useState)实现,这种轻量级的状态管理方式完美适配React Native的跨端生命周期模型,同时与鸿蒙系统的组件生命周期形成深度映射。

跨端交互

// 基础数据状态
const [users] = useState<User[]>([/* 初始化用户数据 */]);
const [groups, setGroups] = useState<WeightLossGroup[]>([/* 初始化小组数据 */]);
const [dietRecords, setDietRecords] = useState<DietRecord[]>([/* 初始化饮食记录 */]);
const [exerciseRecords, setExerciseRecords] = useState<ExerciseRecord[]>([/* 初始化运动记录 */]);
const [nutritionists] = useState<Nutritionist[]>([/* 初始化营养师数据 */]);

// 交互状态
const [selectedUser, setSelectedUser] = useState<string | null>(null);
const [selectedGroup, setSelectedGroup] = useState<string | null>(null);
const [selectedNutritionist, setSelectedNutritionist] = useState<string | null>(null);
const [newDietRecord, setNewDietRecord] = useState({ food: '', calories: '' });
const [newExerciseRecord, setNewExerciseRecord] = useState({ activity: '', duration: '', caloriesBurned: '' });
const [isModalVisible, setIsModalVisible] = useState(false);

useState负责管理组件内部的状态数据,其状态更新机制会触发React Native的虚拟DOM重渲染,最终映射为鸿蒙系统的ArkUI组件更新。以饮食记录添加功能为例,核心逻辑完全基于JavaScript标准语法实现,无任何平台专属代码,确保在鸿蒙系统中无缝执行:

const handleAddDietRecord = () => {
  if (newDietRecord.food && newDietRecord.calories && selectedUser) {
    const newRecord: DietRecord = {
      id: (dietRecords.length + 1).toString(),
      userId: selectedUser,
      date: new Date().toISOString().split('T')[0],
      food: newDietRecord.food,
      calories: parseInt(newDietRecord.calories)
    };
    setDietRecords([...dietRecords, newRecord]);
    setNewDietRecord({ food: '', calories: '' });
    Alert.alert('添加成功', '新的饮食记录已添加');
  } else {
    Alert.alert('提示', '请选择用户并填写完整的饮食信息');
  }
};

其中,Alert.alert作为React Native封装的跨端通用弹窗API,在鸿蒙设备上会调用原生的弹窗组件,保证交互体验的一致性;Date对象的标准化处理则避免了不同平台时间格式的差异,确保饮食/运动记录的日期在鸿蒙系统中准确显示。

应用的UI层完全基于React Native的核心组件构建,样式通过StyleSheet.create统一管理,这种设计保证了UI在鸿蒙系统中的无缝适配,同时兼顾健康管理应用的视觉体验要求。

样式

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f0f9ff', // 统一的健康主题色
  },
  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,
  },
  input: {
    flex: 1,
    backgroundColor: '#f0f9ff',
    borderRadius: 8,
    paddingHorizontal: 12,
    paddingVertical: 8,
    fontSize: 14,
    color: '#0c4a6e',
    marginRight: 8,
  },
  // 按钮、卡片等组件样式定义
});

StyleSheet将CSS样式抽象为跨平台的样式对象,在鸿蒙系统中,flex布局、padding/margin、borderRadius等核心样式属性会被精准转换为ArkUI的布局属性(如flexDirection、paddingLeft、borderRadius);而阴影相关的样式(elevation/shadow系列)则会适配鸿蒙的shadow属性,保证卡片、表单等组件在鸿蒙设备上的视觉层次感。代码中通过Dimensions.get(‘window’)获取屏幕尺寸:

const { width, height } = Dimensions.get('window');

该API在鸿蒙系统中能够准确获取设备的屏幕像素尺寸,避免了直接使用鸿蒙原生API(如getWindowSize)导致的代码碎片化,保证了不同屏幕尺寸鸿蒙设备上的布局一致性,尤其适配了鸿蒙平板、手机等多形态设备的显示需求。

交互组件

核心交互逻辑基于TouchableOpacity组件实现,该组件在鸿蒙系统中会被渲染为具备点击反馈的原生按钮组件,保证了跨端交互体验的统一:

<TouchableOpacity 
  style={styles.adviceButton}
  onPress={handleGetNutritionistAdvice}
>
  <Text style={styles.adviceText}>获取营养师建议</Text>
</TouchableOpacity>

TextInput组件作为跨端通用的输入控件,在鸿蒙系统中会适配为ArkUI的TextInput组件,其onChangeText回调机制保持完全一致;针对热量、时长等数值型输入,设置keyboardType="numeric"属性,在鸿蒙设备上会自动唤起数字键盘,提升健康数据录入的便捷性。Modal组件则通过animationType="slide"实现滑动弹窗效果,在鸿蒙系统中适配为原生的滑动模态框,保证了详情查看等交互的流畅性。


当前代码已具备基础的鸿蒙兼容能力,在生产环境中可针对鸿蒙系统的特性进行以下优化,进一步提升健康管理应用的体验:

  1. 高性能列表渲染:当前饮食记录、运动记录等采用ScrollView + map的方式渲染,在鸿蒙系统中面对大量健康数据时可能出现卡顿。可替换为React Native的FlatList组件,该组件在鸿蒙系统中会适配ArkUI的List组件,实现按需渲染和组件复用,大幅提升列表滚动性能,尤其适合展示用户长期的饮食/运动记录数据。

  2. 原生能力扩展:若需调用鸿蒙的健康相关原生能力(如运动传感器数据、系统级健康提醒),可通过React Native的Native Module机制封装鸿蒙原生API,构建JS与ArkTS的通信桥梁,在保留跨端代码架构的同时,充分利用鸿蒙系统的原生健康能力,例如自动同步运动数据到鸿蒙健康应用。

  3. 资源适配优化:代码中使用Base64编码的图标资源,避免了图片资源的平台适配问题。在鸿蒙系统中,可进一步通过React Native的Asset系统适配鸿蒙的资源分类规则(如按设备尺寸、深色模式分类),提升图标等资源的加载效率和显示效果。

这款基于React Native开发的减重小组应用,通过TypeScript强类型约束、React Hooks状态管理、通用UI组件与样式设计,构建了具备完整鸿蒙跨端兼容能力的健康管理应用架构。核心技术要点可总结为:

  • 采用通用API+组件构建核心业务逻辑,规避平台专属代码,是实现鸿蒙兼容的基础;
  • TypeScript类型系统不仅提升健康数据的准确性,更在跨端编译阶段拦截数据类型错误;
  • React Hooks的状态管理与鸿蒙组件生命周期深度适配,保证健康数据操作逻辑跨端稳定运行;
  • 统一的StyleSheet样式系统实现了UI在鸿蒙设备上的一致性渲染,兼顾健康应用的视觉体验。

基于该架构,只需少量针对鸿蒙原生能力的扩展和性能优化,即可打造出体验优良的鸿蒙版减重管理应用,为跨端健康管理类应用开发提供了可复用的技术范式。


在健康管理领域,数字化工具正逐渐成为用户实现健康目标的重要助手。本文将深入剖析一个基于 React Native 构建的减重小组应用,探讨其技术实现细节及鸿蒙跨端能力的应用。

技术选型

该应用采用了现代 React Native 函数式组件架构,通过 TypeScript 类型系统和 React Hooks 实现了一个功能完整的减重管理系统。核心技术栈包括:

  • React Native:作为跨端开发框架,提供了统一的组件 API,确保应用在 iOS、Android 及鸿蒙平台上的一致性体验
  • TypeScript:通过严格的类型定义增强代码可维护性,明确了数据结构和组件接口
  • React Hooks:使用 useState 管理应用状态,实现了声明式的状态管理
  • Base64 图标:采用 Base64 编码的图标资源,避免了不同平台资源格式的差异,提高了跨端兼容性
  • 响应式布局:使用 Dimensions API 获取屏幕尺寸,实现适配不同设备的响应式界面

数据模型

应用通过 TypeScript 接口定义了五个核心数据类型,构建了完整的健康管理数据模型体系:

// 用户类型
type User = {
  id: string;
  name: string;
  weight: number;
  goalWeight: number;
};

// 减重小组类型
type WeightLossGroup = {
  id: string;
  name: string;
  members: string[];
  startDate: string;
  endDate: string;
};

// 饮食记录类型
type DietRecord = {
  id: string;
  userId: string;
  date: string;
  food: string;
  calories: number;
};

// 运动记录类型
type ExerciseRecord = {
  id: string;
  userId: string;
  date: string;
  activity: string;
  duration: number;
  caloriesBurned: number;
};

// 营养师类型
type Nutritionist = {
  id: string;
  name: string;
  specialty: string;
  experience: string;
  rating: number;
};

这种强类型设计不仅提高了代码可读性,也为鸿蒙跨端适配提供了清晰的数据契约,确保不同平台间数据传递的一致性。数据模型的设计充分考虑了减重管理的核心要素,包含了用户基本信息、减重目标、小组管理、饮食记录、运动记录以及专业指导资源等。

状态管理

应用使用 useState Hook 管理多个复杂状态,包括用户列表、减重小组、饮食记录、运动记录、营养师信息等:

const [users] = useState<User[]>([
  {
    id: '1',
    name: '李先生',
    weight: 80,
    goalWeight: 70
  },
  {
    id: '2',
    name: '王女士',
    weight: 65,
    goalWeight: 55
  }
]);

// 其他状态定义...

状态管理采用了扁平化的设计方式,每个状态对应一个数据集合,通过数组操作实现数据的增删改查。这种设计方式在中小型应用中具有较高的可读性和维护性,同时也便于鸿蒙平台的适配。

在 React Native 鸿蒙跨端开发中,该应用体现了以下关键技术点:

  1. 组件兼容性:使用 React Native 核心组件(如 SafeAreaView、View、Text、TouchableOpacity、ScrollView、Modal 等),确保在鸿蒙系统上的兼容性
  2. 资源管理:通过 Base64 编码的图标资源,避免了不同平台资源格式的差异,提高了跨端部署的一致性
  3. 尺寸适配:使用 Dimensions API 获取屏幕尺寸,实现响应式布局,适应不同设备屏幕
  4. 状态管理:采用 React Hooks 进行状态管理,保持跨平台代码一致性
  5. 类型安全:TypeScript 类型定义确保了数据结构在不同平台间的一致性
  6. API 调用:使用 React Native 统一的 API 调用方式,如 Alert 组件,确保在鸿蒙平台上的正确显示

1. 用户与小组管理

应用实现了用户选择和小组管理功能,用户可以加入减重小组,通过集体监督提高减重效果:

const handleSelectUser = (userId: string) => {
  setSelectedUser(userId);
  Alert.alert('选择用户', '您已选择该用户进行减重管理');
};

const handleSelectGroup = (groupId: string) => {
  setSelectedGroup(groupId);
  Alert.alert('选择小组', '您已选择该减重小组');
};

2. 饮食记录管理

应用提供了饮食记录的添加功能,用户可以记录每日饮食和卡路里摄入:

const handleAddDietRecord = () => {
  if (newDietRecord.food && newDietRecord.calories && selectedUser) {
    const newRecord: DietRecord = {
      id: (dietRecords.length + 1).toString(),
      userId: selectedUser,
      date: new Date().toISOString().split('T')[0],
      food: newDietRecord.food,
      calories: parseInt(newDietRecord.calories)
    };
    setDietRecords([...dietRecords, newRecord]);
    setNewDietRecord({ food: '', calories: '' });
    Alert.alert('添加成功', '新的饮食记录已添加');
  } else {
    Alert.alert('提示', '请选择用户并填写完整的饮食信息');
  }
};

3. 运动记录管理

应用实现了运动记录的添加功能,用户可以记录运动类型、时长和消耗的卡路里:

const handleAddExerciseRecord = () => {
  if (newExerciseRecord.activity && newExerciseRecord.duration && newExerciseRecord.caloriesBurned && selectedUser) {
    const newRecord: ExerciseRecord = {
      id: (exerciseRecords.length + 1).toString(),
      userId: selectedUser,
      date: new Date().toISOString().split('T')[0],
      activity: newExerciseRecord.activity,
      duration: parseInt(newExerciseRecord.duration),
      caloriesBurned: parseInt(newExerciseRecord.caloriesBurned)
    };
    setExerciseRecords([...exerciseRecords, newRecord]);
    setNewExerciseRecord({ activity: '', duration: '', caloriesBurned: '' });
    Alert.alert('添加成功', '新的运动记录已添加');
  } else {
    Alert.alert('提示', '请选择用户并填写完整的运动信息');
  }
};

4. 专业指导资源

应用集成了营养师资源,用户可以选择专业营养师进行指导,提高减重的科学性和有效性:

const handleSelectNutritionist = (nutritionistId: string) => {
  setSelectedNutritionist(nutritionistId);
  Alert.alert('选择营养师', '您已选择该营养师进行指导');
};

应用的 UI 设计遵循了现代移动应用的设计原则,使用了以下组件和交互模式:

  • 安全区域:通过 SafeAreaView 确保内容显示在安全区域内,适应不同设备的屏幕刘海和底部指示条
  • 滚动视图:通过 ScrollView 实现内容的垂直滚动,适应不同长度的记录列表和小组信息
  • 卡片布局:使用 TouchableOpacity 和 View 组合实现卡片式列表项,提供清晰的视觉层次和交互反馈
  • 模态框:通过 Modal 组件展示详细信息,如小组详情、饮食记录详情等
  • 交互反馈:使用 Alert 组件提供操作反馈和提示信息
  • 响应式设计:根据屏幕尺寸动态调整布局,确保在不同设备上的良好显示效果

  1. 跨端架构:基于 React Native 构建,实现了一次编码多平台运行的目标,特别关注了鸿蒙平台的适配
  2. 类型安全:全面使用 TypeScript 类型定义,提高代码质量和可维护性,确保健康数据的准确性
  3. 小组互动机制:通过减重小组功能,实现了用户间的互动和监督,提高减重的持久性和成功率
  4. 多维度数据记录:同时记录饮食和运动数据,提供全面的健康管理视角
  5. 专业资源整合:集成了营养师资源,为用户提供专业指导
  6. 实时数据反馈:通过即时的 Alert 反馈,增强用户操作体验
  7. 模块化设计:通过清晰的类型定义和函数划分,实现了代码的模块化,提高了可维护性

在实际应用中,还可以考虑以下性能优化策略:

  1. 状态管理优化:对于大型应用,可以考虑使用 Redux 或 Context API 进行全局状态管理,提高状态更新的效率
  2. 组件拆分:将大型组件拆分为更小的可复用组件,提高渲染性能和代码可维护性
  3. 数据缓存:对用户数据和记录信息进行本地缓存,减少重复计算和网络请求
  4. 动画性能:使用 React Native 的 Animated API 实现流畅的过渡动画,提升用户体验
  5. 内存管理:确保及时清理不再使用的状态和事件监听器,避免内存泄漏
  6. 网络优化:对于实际应用中的远程数据同步,实现合理的网络请求策略,如批量上传、增量同步等
  7. 计算优化:对于卡路里计算等频繁操作,可以考虑使用 memoization 技术缓存计算结果

通过对这个减重小组应用的技术解读,我们可以看到 React Native 在跨端开发中的强大能力。该应用不仅实现了完整的减重管理功能,还展示了如何通过 TypeScript、React Hooks 等现代前端技术构建高质量的跨端应用。


真实演示案例代码:



// App.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, TextInput, Modal } from 'react-native';

// Base64 图标库
const ICONS_BASE64 = {
  group: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  diet: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  exercise: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  nutritionist: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

const { width, height } = Dimensions.get('window');

// 用户类型
type User = {
  id: string;
  name: string;
  weight: number;
  goalWeight: number;
};

// 减重小组类型
type WeightLossGroup = {
  id: string;
  name: string;
  members: string[];
  startDate: string;
  endDate: string;
};

// 饮食记录类型
type DietRecord = {
  id: string;
  userId: string;
  date: string;
  food: string;
  calories: number;
};

// 运动记录类型
type ExerciseRecord = {
  id: string;
  userId: string;
  date: string;
  activity: string;
  duration: number;
  caloriesBurned: number;
};

// 营养师类型
type Nutritionist = {
  id: string;
  name: string;
  specialty: string;
  experience: string;
  rating: number;
};

// 减重小组应用组件
const WeightLossGroupApp: React.FC = () => {
  const [users] = useState<User[]>([
    {
      id: '1',
      name: '李先生',
      weight: 80,
      goalWeight: 70
    },
    {
      id: '2',
      name: '王女士',
      weight: 65,
      goalWeight: 55
    }
  ]);

  const [groups, setGroups] = useState<WeightLossGroup[]>([
    {
      id: '1',
      name: '减重先锋队',
      members: ['1', '2'],
      startDate: '2023-12-01',
      endDate: '2024-03-01'
    }
  ]);

  const [dietRecords, setDietRecords] = useState<DietRecord[]>([
    {
      id: '1',
      userId: '1',
      date: '2023-12-01',
      food: '鸡胸肉沙拉',
      calories: 300
    },
    {
      id: '2',
      userId: '2',
      date: '2023-12-01',
      food: '蔬菜汤',
      calories: 150
    }
  ]);

  const [exerciseRecords, setExerciseRecords] = useState<ExerciseRecord[]>([
    {
      id: '1',
      userId: '1',
      date: '2023-12-01',
      activity: '跑步',
      duration: 30,
      caloriesBurned: 300
    },
    {
      id: '2',
      userId: '2',
      date: '2023-12-01',
      activity: '瑜伽',
      duration: 45,
      caloriesBurned: 200
    }
  ]);

  const [nutritionists] = useState<Nutritionist[]>([
    {
      id: '1',
      name: '张营养师',
      specialty: '减重营养',
      experience: '5年经验',
      rating: 4.8
    },
    {
      id: '2',
      name: '李营养师',
      specialty: '运动营养',
      experience: '8年经验',
      rating: 4.9
    }
  ]);

  const [selectedUser, setSelectedUser] = useState<string | null>(null);
  const [selectedGroup, setSelectedGroup] = useState<string | null>(null);
  const [selectedNutritionist, setSelectedNutritionist] = useState<string | null>(null);
  const [newDietRecord, setNewDietRecord] = useState({
    food: '',
    calories: ''
  });
  const [newExerciseRecord, setNewExerciseRecord] = useState({
    activity: '',
    duration: '',
    caloriesBurned: ''
  });
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [modalContent, setModalContent] = useState('');

  const handleSelectUser = (userId: string) => {
    setSelectedUser(userId);
    Alert.alert('选择用户', '您已选择该用户进行减重管理');
  };

  const handleSelectGroup = (groupId: string) => {
    setSelectedGroup(groupId);
    Alert.alert('选择小组', '您已选择该减重小组');
  };

  const handleSelectNutritionist = (nutritionistId: string) => {
    setSelectedNutritionist(nutritionistId);
    Alert.alert('选择营养师', '您已选择该营养师进行指导');
  };

  const handleAddDietRecord = () => {
    if (newDietRecord.food && newDietRecord.calories && selectedUser) {
      const newRecord: DietRecord = {
        id: (dietRecords.length + 1).toString(),
        userId: selectedUser,
        date: new Date().toISOString().split('T')[0],
        food: newDietRecord.food,
        calories: parseInt(newDietRecord.calories)
      };
      setDietRecords([...dietRecords, newRecord]);
      setNewDietRecord({ food: '', calories: '' });
      Alert.alert('添加成功', '新的饮食记录已添加');
    } else {
      Alert.alert('提示', '请选择用户并填写完整的饮食信息');
    }
  };

  const handleAddExerciseRecord = () => {
    if (newExerciseRecord.activity && newExerciseRecord.duration && newExerciseRecord.caloriesBurned && selectedUser) {
      const newRecord: ExerciseRecord = {
        id: (exerciseRecords.length + 1).toString(),
        userId: selectedUser,
        date: new Date().toISOString().split('T')[0],
        activity: newExerciseRecord.activity,
        duration: parseInt(newExerciseRecord.duration),
        caloriesBurned: parseInt(newExerciseRecord.caloriesBurned)
      };
      setExerciseRecords([...exerciseRecords, newRecord]);
      setNewExerciseRecord({ activity: '', duration: '', caloriesBurned: '' });
      Alert.alert('添加成功', '新的运动记录已添加');
    } else {
      Alert.alert('提示', '请选择用户并填写完整的运动信息');
    }
  };

  const handleViewGroup = (groupId: string) => {
    const group = groups.find(g => g.id === groupId);
    if (group) {
      const members = group.members.map(memberId => {
        const user = users.find(u => u.id === memberId);
        return user ? user.name : '';
      }).join(', ');
      setModalContent(`小组名称: ${group.name}\n成员: ${members}\n开始日期: ${group.startDate}\n结束日期: ${group.endDate}`);
      setIsModalVisible(true);
    }
  };

  const handleViewDietRecord = (recordId: string) => {
    const record = dietRecords.find(r => r.id === recordId);
    if (record) {
      const user = users.find(u => u.id === record.userId);
      setModalContent(`用户: ${user?.name}\n食物: ${record.food}\n热量: ${record.calories} kcal\n日期: ${record.date}`);
      setIsModalVisible(true);
    }
  };

  const handleViewExerciseRecord = (recordId: string) => {
    const record = exerciseRecords.find(r => r.id === recordId);
    if (record) {
      const user = users.find(u => u.id === record.userId);
      setModalContent(`用户: ${user?.name}\n活动: ${record.activity}\n时长: ${record.duration} 分钟\n消耗热量: ${record.caloriesBurned} kcal\n日期: ${record.date}`);
      setIsModalVisible(true);
    }
  };

  const handleGetNutritionistAdvice = () => {
    if (selectedNutritionist && selectedUser) {
      const nutritionist = nutritionists.find(n => n.id === selectedNutritionist);
      const user = users.find(u => u.id === selectedUser);
      if (nutritionist && user) {
        Alert.alert('营养师建议', `${nutritionist.name}营养师为${user.name}提供了专业减重建议`);
      }
    } else {
      Alert.alert('提示', '请选择营养师和用户');
    }
  };

  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}>减重小组</Text>
        <Text style={styles.subtitle}>用户可加入减重小组,记录饮食、运动数据,获得专业营养师指导及同伴激励</Text>
      </View>

      <ScrollView style={styles.content}>
        {/* 用户列表 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>用户列表</Text>
          {users.map(user => (
            <TouchableOpacity 
              key={user.id}
              style={[
                styles.card,
                selectedUser === user.id && styles.selectedCard
              ]}
              onPress={() => handleSelectUser(user.id)}
            >
              <Text style={styles.icon}>👤</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>{user.name}</Text>
                <Text style={styles.cardDescription}>当前体重: {user.weight} kg</Text>
                <Text style={styles.cardDescription}>目标体重: {user.goalWeight} kg</Text>
              </View>
            </TouchableOpacity>
          ))}
        </View>

        {/* 减重小组 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>减重小组</Text>
          {groups.map(group => (
            <TouchableOpacity 
              key={group.id}
              style={[
                styles.card,
                selectedGroup === group.id && styles.selectedCard
              ]}
              onPress={() => handleSelectGroup(group.id)}
            >
              <Text style={styles.icon}>👥</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>{group.name}</Text>
                <Text style={styles.cardDescription}>成员数量: {group.members.length}</Text>
                <Text style={styles.cardDescription}>活动周期: {group.startDate}{group.endDate}</Text>
              </View>
              <TouchableOpacity 
                style={styles.viewButton}
                onPress={() => handleViewGroup(group.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={newDietRecord.food}
              onChangeText={(text) => setNewDietRecord({ ...newDietRecord, food: text })}
            />
            <TextInput
              style={styles.input}
              placeholder="热量 (kcal)"
              value={newDietRecord.calories}
              onChangeText={(text) => setNewDietRecord({ ...newDietRecord, calories: text })}
              keyboardType="numeric"
            />
          </View>
          <TouchableOpacity 
            style={styles.addButton}
            onPress={handleAddDietRecord}
          >
            <Text style={styles.addText}>添加饮食记录</Text>
          </TouchableOpacity>
          {dietRecords.map(record => (
            <TouchableOpacity 
              key={record.id}
              style={styles.recordCard}
              onPress={() => handleViewDietRecord(record.id)}
            >
              <Text style={styles.icon}>🍎</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>{record.food}</Text>
                <Text style={styles.cardDescription}>热量: {record.calories} kcal</Text>
                <Text style={styles.cardDescription}>日期: {record.date}</Text>
              </View>
            </TouchableOpacity>
          ))}
        </View>

        {/* 运动记录 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>运动记录</Text>
          <View style={styles.inputRow}>
            <TextInput
              style={styles.input}
              placeholder="运动类型"
              value={newExerciseRecord.activity}
              onChangeText={(text) => setNewExerciseRecord({ ...newExerciseRecord, activity: text })}
            />
            <TextInput
              style={styles.input}
              placeholder="时长 (分钟)"
              value={newExerciseRecord.duration}
              onChangeText={(text) => setNewExerciseRecord({ ...newExerciseRecord, duration: text })}
              keyboardType="numeric"
            />
          </View>
          <View style={styles.inputRow}>
            <TextInput
              style={styles.input}
              placeholder="消耗热量 (kcal)"
              value={newExerciseRecord.caloriesBurned}
              onChangeText={(text) => setNewExerciseRecord({ ...newExerciseRecord, caloriesBurned: text })}
              keyboardType="numeric"
            />
          </View>
          <TouchableOpacity 
            style={styles.addButton}
            onPress={handleAddExerciseRecord}
          >
            <Text style={styles.addText}>添加运动记录</Text>
          </TouchableOpacity>
          {exerciseRecords.map(record => (
            <TouchableOpacity 
              key={record.id}
              style={styles.recordCard}
              onPress={() => handleViewExerciseRecord(record.id)}
            >
              <Text style={styles.icon}>🏃</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>{record.activity}</Text>
                <Text style={styles.cardDescription}>时长: {record.duration} 分钟</Text>
                <Text style={styles.cardDescription}>消耗热量: {record.caloriesBurned} kcal</Text>
                <Text style={styles.cardDescription}>日期: {record.date}</Text>
              </View>
            </TouchableOpacity>
          ))}
        </View>

        {/* 营养师列表 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>营养师列表</Text>
          {nutritionists.map(nutritionist => (
            <TouchableOpacity 
              key={nutritionist.id}
              style={[
                styles.card,
                selectedNutritionist === nutritionist.id && styles.selectedCard
              ]}
              onPress={() => handleSelectNutritionist(nutritionist.id)}
            >
              <Text style={styles.icon}>👩‍⚕️</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>{nutritionist.name}</Text>
                <Text style={styles.cardDescription}>专长: {nutritionist.specialty}</Text>
                <Text style={styles.cardDescription}>经验: {nutritionist.experience}</Text>
                <Text style={styles.cardDescription}>评分: {nutritionist.rating}</Text>
              </View>
            </TouchableOpacity>
          ))}
          <TouchableOpacity 
            style={styles.adviceButton}
            onPress={handleGetNutritionistAdvice}
          >
            <Text style={styles.adviceText}>获取营养师建议</Text>
          </TouchableOpacity>
        </View>

        {/* 使用说明 */}
        <View style={styles.infoCard}>
          <Text style={styles.sectionTitle}>📘 使用说明</Text>
          <Text style={styles.infoText}>• 加入减重小组与同伴一起努力</Text>
          <Text style={styles.infoText}>• 记录每日饮食和运动数据</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',
  },
  recordCard: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f0f9ff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 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',
  },
  adviceButton: {
    backgroundColor: '#10b981',
    padding: 16,
    borderRadius: 12,
    alignItems: 'center',
  },
  adviceText: {
    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 WeightLossGroupApp;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文剖析了基于React Native开发的健康管理类跨端应用的技术实现,重点分析了其在鸿蒙系统上的兼容性设计。应用采用TypeScript构建严谨的数据模型,通过React Hooks实现轻量级状态管理,并完全基于React Native核心组件开发UI层,确保在iOS、Android及鸿蒙系统上的功能一致性。文章详细展示了数据类型定义、状态管理机制、跨平台样式适配等关键技术点,特别强调了未使用平台专属API的设计原则,使得饮食记录、运动数据等核心功能在鸿蒙设备上能无缝运行。这种"一次开发、多端部署"的实现方案为健康管理类应用的跨平台开发提供了典型范例。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐