React Native鸿蒙跨平台 HarmonyOS ArkTS API 24 完成Modal组件则通过animationType=“slide“实现滑动弹窗效果
在健康管理类跨端应用开发领域,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"实现滑动弹窗效果,在鸿蒙系统中适配为原生的滑动模态框,保证了详情查看等交互的流畅性。
当前代码已具备基础的鸿蒙兼容能力,在生产环境中可针对鸿蒙系统的特性进行以下优化,进一步提升健康管理应用的体验:
-
高性能列表渲染:当前饮食记录、运动记录等采用ScrollView + map的方式渲染,在鸿蒙系统中面对大量健康数据时可能出现卡顿。可替换为React Native的FlatList组件,该组件在鸿蒙系统中会适配ArkUI的List组件,实现按需渲染和组件复用,大幅提升列表滚动性能,尤其适合展示用户长期的饮食/运动记录数据。
-
原生能力扩展:若需调用鸿蒙的健康相关原生能力(如运动传感器数据、系统级健康提醒),可通过React Native的Native Module机制封装鸿蒙原生API,构建JS与ArkTS的通信桥梁,在保留跨端代码架构的同时,充分利用鸿蒙系统的原生健康能力,例如自动同步运动数据到鸿蒙健康应用。
-
资源适配优化:代码中使用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 鸿蒙跨端开发中,该应用体现了以下关键技术点:
- 组件兼容性:使用 React Native 核心组件(如 SafeAreaView、View、Text、TouchableOpacity、ScrollView、Modal 等),确保在鸿蒙系统上的兼容性
- 资源管理:通过 Base64 编码的图标资源,避免了不同平台资源格式的差异,提高了跨端部署的一致性
- 尺寸适配:使用 Dimensions API 获取屏幕尺寸,实现响应式布局,适应不同设备屏幕
- 状态管理:采用 React Hooks 进行状态管理,保持跨平台代码一致性
- 类型安全:TypeScript 类型定义确保了数据结构在不同平台间的一致性
- 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 组件提供操作反馈和提示信息
- 响应式设计:根据屏幕尺寸动态调整布局,确保在不同设备上的良好显示效果
- 跨端架构:基于 React Native 构建,实现了一次编码多平台运行的目标,特别关注了鸿蒙平台的适配
- 类型安全:全面使用 TypeScript 类型定义,提高代码质量和可维护性,确保健康数据的准确性
- 小组互动机制:通过减重小组功能,实现了用户间的互动和监督,提高减重的持久性和成功率
- 多维度数据记录:同时记录饮食和运动数据,提供全面的健康管理视角
- 专业资源整合:集成了营养师资源,为用户提供专业指导
- 实时数据反馈:通过即时的 Alert 反馈,增强用户操作体验
- 模块化设计:通过清晰的类型定义和函数划分,实现了代码的模块化,提高了可维护性
在实际应用中,还可以考虑以下性能优化策略:
- 状态管理优化:对于大型应用,可以考虑使用 Redux 或 Context API 进行全局状态管理,提高状态更新的效率
- 组件拆分:将大型组件拆分为更小的可复用组件,提高渲染性能和代码可维护性
- 数据缓存:对用户数据和记录信息进行本地缓存,减少重复计算和网络请求
- 动画性能:使用 React Native 的 Animated API 实现流畅的过渡动画,提升用户体验
- 内存管理:确保及时清理不再使用的状态和事件监听器,避免内存泄漏
- 网络优化:对于实际应用中的远程数据同步,实现合理的网络请求策略,如批量上传、增量同步等
- 计算优化:对于卡路里计算等频繁操作,可以考虑使用 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的设计原则,使得饮食记录、运动数据等核心功能在鸿蒙设备上能无缝运行。这种"一次开发、多端部署"的实现方案为健康管理类应用的跨平台开发提供了典型范例。
更多推荐

所有评论(0)