在移动应用开发中,健康管理类应用是一种重要的应用类型,需要考虑数据记录、状态展示、用户交互等多个方面。本文将深入分析一个功能完备的 React Native 健康记录应用实现,探讨其架构设计、状态管理、数据处理以及跨端兼容性策略。
架构设计
该实现采用了清晰的单组件架构,主要包含以下部分:
- 主应用组件 (
HealthRecordListApp) - 负责整体布局和状态管理 - 记录列表 - 展示健康记录列表,支持按类型过滤
- 记录卡片 - 详细展示单条健康记录的信息
- 状态展示 - 根据健康状态显示不同颜色的标记
- 操作按钮 - 提供编辑和查看详情的功能
这种架构设计使得代码结构清晰,易于维护。主应用组件负责管理全局状态和业务逻辑,而各个UI部分则负责具体的展示,实现了关注点分离。
状态管理
HealthRecordListApp 组件使用 useState 钩子管理两个关键状态:
typescript
const [records, setRecords] = useState<HealthRecord[]>([...]);
const [activeFilter, setActiveFilter] = useState<string>('all');
这种状态管理方式简洁高效,通过状态更新触发组件重新渲染,实现了健康记录的展示和过滤功能。使用 TypeScript 类型定义确保了数据结构的类型安全,提高了代码的可靠性。
健康记录
应用实现了完整的健康记录展示功能:
- 多类型记录 - 支持血压、体重、步数、睡眠、体温、用药等多种健康记录类型
- 详细信息 - 每条记录包含标题、数值、单位、日期、时间、备注和状态
- 状态标记 - 根据健康状态显示不同颜色的标记(正常-绿色、警告-橙色、危险-红色)
- 操作按钮 - 每条记录提供编辑和查看详情的操作按钮
这种实现方式确保了健康记录的全面展示,用户可以方便地查看和管理各种健康数据。
记录过滤
应用实现了按类型过滤健康记录的功能:
typescript
// 根据类型过滤记录
const filteredRecords = activeFilter === 'all'
? records
: records.filter(record => record.type === activeFilter);
这种实现方式支持按健康记录类型进行过滤,用户可以快速查看特定类型的健康记录。
状态管理
应用实现了健康状态的颜色管理:
typescript
// 获取状态颜色
const getStatusColor = (status: string): string => {
switch (status) {
case 'normal': return '#10b981'; // 绿色
case 'warning': return '#f59e0b'; // 橙色
case 'critical': return '#ef4444'; // 红色
default: return '#64748b'; // 灰色
}
};
这种实现方式根据健康状态返回不同的颜色,使得健康状态的展示更加直观。
类型定义
该实现使用 TypeScript 定义了核心数据类型:
typescript
// 健康记录类型
type HealthRecord = {
id: string;
type: 'bloodPressure' | 'weight' | 'steps' | 'sleep' | 'temperature' | 'medication';
title: string;
value: string;
unit: string;
date: string;
time: string;
notes: string;
status: 'normal' | 'warning' | 'critical';
icon: string;
};
这个类型定义包含了健康记录的完整信息,包括类型、标题、数值、单位、日期、时间、备注、状态和图标。使用 TypeScript 联合类型确保了 type 和 status 的取值只能是预定义的几个选项之一,提高了代码的类型安全性。
数据组织
应用数据按照功能模块进行组织:
- records - 健康记录列表
- activeFilter - 当前选中的过滤条件
- filters - 过滤选项列表,包含全部和各种健康记录类型
这种数据组织方式使得数据管理更加清晰,易于扩展和维护。
布局结构
应用界面采用了清晰的层次结构:
- 顶部 - 显示应用标题和操作按钮
- 过滤栏 - 显示过滤选项,允许用户按类型过滤记录
- 记录列表 - 显示当前过滤条件下的健康记录列表
- 记录卡片 - 详细展示单条健康记录的信息
这种布局结构符合用户的使用习惯,用户可以快速了解应用内容并进行操作。
当前实现使用 FlatList 渲染记录列表,这是一个好的做法,但可以进一步优化:
typescript
// 优化前
<FlatList
data={filteredRecords}
renderItem={renderRecordItem}
keyExtractor={item => item.id}
/>
// 优化后
<FlatList
data={filteredRecords}
renderItem={renderRecordItem}
keyExtractor={item => item.id}
initialNumToRender={5} // 初始渲染的项目数
maxToRenderPerBatch={10} // 每批渲染的最大项目数
windowSize={10} // 可见区域外渲染的项目数
removeClippedSubviews={true} // 移除不可见的子视图
updateCellsBatchingPeriod={100} // 单元格更新的批处理周期
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT, // 预计算的项目高度
offset: ITEM_HEIGHT * index,
index
})}
/>
2. 状态管理
当前实现使用 useState 钩子管理状态,可以考虑使用 useReducer 或状态管理库来管理复杂状态:
typescript
// 优化前
const [records, setRecords] = useState<HealthRecord[]>([...]);
const [activeFilter, setActiveFilter] = useState<string>('all');
// 优化后
type AppState = {
records: HealthRecord[];
activeFilter: string;
};
type AppAction =
| { type: 'SET_RECORDS'; payload: HealthRecord[] }
| { type: 'SET_ACTIVE_FILTER'; payload: string }
| { type: 'ADD_RECORD'; payload: HealthRecord }
| { type: 'UPDATE_RECORD'; payload: HealthRecord }
| { type: 'DELETE_RECORD'; payload: string };
const initialState: AppState = {
records: [...],
activeFilter: 'all',
};
const appReducer = (state: AppState, action: AppAction): AppState => {
switch (action.type) {
case 'SET_RECORDS':
return { ...state, records: action.payload };
case 'SET_ACTIVE_FILTER':
return { ...state, activeFilter: action.payload };
case 'ADD_RECORD':
return { ...state, records: [action.payload, ...state.records] };
case 'UPDATE_RECORD':
return {
...state,
records: state.records.map(record =>
record.id === action.payload.id ? action.payload : record
)
};
case 'DELETE_RECORD':
return {
...state,
records: state.records.filter(record => record.id !== action.payload)
};
default:
return state;
}
};
const [state, dispatch] = useReducer(appReducer, initialState);
3. 数据持久化
当前实现使用内存状态存储数据,可以考虑集成本地存储实现数据持久化:
typescript
import AsyncStorage from '@react-native-async-storage/async-storage';
const STORAGE_KEY = '@health_records';
const HealthRecordListApp = () => {
const [records, setRecords] = useState<HealthRecord[]>([]);
// 加载数据
useEffect(() => {
loadRecords();
}, []);
const loadRecords = async () => {
try {
const storedRecords = await AsyncStorage.getItem(STORAGE_KEY);
if (storedRecords) {
setRecords(JSON.parse(storedRecords));
}
} catch (error) {
console.error('加载数据失败:', error);
}
};
// 保存数据
const saveRecords = async (newRecords: HealthRecord[]) => {
try {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newRecords));
} catch (error) {
console.error('保存数据失败:', error);
}
};
// 添加记录时保存
const addRecord = (newRecord: HealthRecord) => {
const updatedRecords = [newRecord, ...records];
setRecords(updatedRecords);
saveRecords(updatedRecords);
};
// 其他代码...
};
4. 导航系统
可以集成 React Navigation 实现多页面导航:
typescript
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HealthRecordListApp}
options={{ title: '健康记录' }}
/>
<Stack.Screen
name="AddRecord"
component={AddRecordScreen}
options={{ title: '添加记录' }}
/>
<Stack.Screen
name="RecordDetail"
component={RecordDetailScreen}
options={({ route }) => ({ title: route.params?.recordTitle || '记录详情' })}
/>
<Stack.Screen
name="Statistics"
component={StatisticsScreen}
options={{ title: '统计分析' }}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
总结
本文深入分析了一个功能完备的 React Native 健康记录应用实现,从架构设计、状态管理、数据处理到跨端兼容性都进行了详细探讨。该实现不仅功能完整,而且代码结构清晰,具有良好的可扩展性和可维护性
多类型健康数据管理应用的 React Native 完整实现逻辑,并掌握其向鸿蒙(HarmonyOS)平台跨端适配的核心方案。该应用是典型的健康数据聚合管理类应用,涵盖了多类型数据分类、动态过滤、高性能列表渲染、水平滚动筛选器、快捷操作区等核心场景,是健康管理类应用跨端开发的典型范例。
1. 模型设计
该应用构建了通用化的健康记录数据模型,支持血压、体重、步数、睡眠等多种健康指标的统一管理:
typescript
// 通用健康记录数据模型
type HealthRecord = {
id: string; // 唯一标识
type: 'bloodPressure' | 'weight' | 'steps' | 'sleep' | 'temperature' | 'medication'; // 记录类型
title: string; // 显示标题
value: string; // 测量值(字符串类型适配不同格式)
unit: string; // 单位
date: string; // 日期
time: string; // 时间
notes: string; // 备注
status: 'normal' | 'warning' | 'critical'; // 健康状态
icon: string; // 可视化图标
};
模型设计亮点:
- 类型枚举化:使用联合类型限制记录类型,确保数据规范性,支持未来扩展新的健康指标;
- 值类型通用化:采用字符串类型存储测量值,适配不同格式的健康数据(如"120/80"、"65.5"、"8500"等);
- 状态分级化:三级健康状态(正常/警告/危险)符合医疗数据的风险分级标准;
- 可视化增强:内置 icon 字段支持 emoji 图标展示,提升用户体验;
- 字段标准化:统一的日期、时间、备注字段,保证不同类型健康记录的一致性;
- 扩展性良好:新增健康指标仅需扩展 type 类型,无需修改核心数据结构。
2. 核心状态管理
应用采用 React Hooks 实现了基础状态管理和动态数据过滤逻辑:
typescript
// 健康记录列表状态
const [records, setRecords] = useState<HealthRecord[]>([/* 初始数据 */]);
// 筛选器激活状态
const [activeFilter, setActiveFilter] = useState<string>('all');
// 动态过滤逻辑(核心)
const filteredRecords = activeFilter === 'all'
? records
: records.filter(record => record.type === activeFilter);
状态管理特点:
- 极简状态设计:仅维护核心数据和筛选状态,避免状态冗余;
- 声明式过滤:使用三元表达式实现简洁的过滤逻辑,可读性高;
- 无副作用过滤:过滤操作基于原始数据的纯函数计算,不修改源数据;
- 即时响应:筛选状态变化立即触发数据重新过滤,交互响应迅速;
- 性能优化:过滤结果直接用于渲染,避免中间状态存储。
(1)健康状态
实现了健康状态到视觉样式的映射,符合医疗数据的视觉编码规范:
typescript
// 健康状态颜色映射(医疗级色彩编码)
const getStatusColor = (status: string): string => {
switch (status) {
case 'normal': return '#10b981'; // 绿色 - 正常(符合医疗UI规范)
case 'warning': return '#f59e0b'; // 橙色 - 警告
case 'critical': return '#ef4444';// 红色 - 危险
default: return '#64748b'; // 灰色 - 未知
}
};
// 状态文本本地化映射
<Text style={styles.statusText}>
{item.status === 'normal' ? '正常' :
item.status === 'warning' ? '警告' : '危险'}
</Text>
实现亮点:
- 医疗色彩规范:采用符合医疗UI设计的绿/橙/红三色体系,符合用户对健康状态的认知习惯;
- 本地化显示:将英文状态转换为中文显示,提升用户体验;
- 样式动态绑定:通过样式数组实现状态与颜色的动态关联;
- 视觉层级清晰:使用圆角徽章样式突出显示健康状态,便于快速识别风险等级。
(2)高性能列表渲染
使用 FlatList 实现高性能的健康记录列表渲染,优化大数据量展示性能:
tsx
<FlatList
data={filteredRecords}
renderItem={renderRecordItem}
keyExtractor={item => item.id}
showsVerticalScrollIndicator={false}
/>
FlatList 优势:
- 视图复用:相比 map 渲染,FlatList 实现了列表项的视图复用,性能提升显著;
- 按需渲染:仅渲染可视区域内的列表项,降低内存占用;
- 滚动优化:内置滚动优化,支持平滑滚动和回弹效果;
- 配置灵活:通过 showsVerticalScrollIndicator 控制滚动条显示,优化视觉体验;
- 唯一标识:keyExtractor 确保列表项的唯一标识,避免重渲染问题。
(3)水平滚动
构建了支持横向滚动的多类型健康记录筛选器,适配移动端有限的屏幕空间:
tsx
// 筛选器配置
const filters = [
{ id: 'all', name: '全部', icon: '📋' },
{ id: 'bloodPressure', name: '血压', icon: '🩸' },
{ id: 'weight', name: '体重', icon: '⚖️' },
{ id: 'steps', name: '步数', icon: '👣' },
{ id: 'sleep', name: '睡眠', icon: '😴' },
{ id: 'temperature', name: '体温', icon: '🌡️' },
{ id: 'medication', name: '用药', icon: '💊' },
];
// 水平滚动筛选器UI
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.filtersContainer}
>
<View style={styles.filtersList}>
{filters.map(filter => (
<TouchableOpacity
key={filter.id}
style={[
styles.filterItem,
activeFilter === filter.id && styles.activeFilter
]}
onPress={() => setActiveFilter(filter.id)}
>
<Text style={styles.filterIcon}>{filter.icon}</Text>
<Text style={styles.filterText}>{filter.name}</Text>
</TouchableOpacity>
))}
</View>
</ScrollView>
筛选器设计亮点:
- 水平滚动适配:使用 ScrollView 的 horizontal 属性实现横向滚动,适配移动端屏幕;
- 视觉反馈明确:activeFilter 状态驱动的样式变化,清晰标识当前选中的筛选类型;
- 图标+文字组合:emoji 图标提升视觉识别度,文字保证准确性;
- 样式统一:统一的圆角、内边距、间距设计,视觉效果协调;
- 交互友好:showsHorizontalScrollIndicator={false} 隐藏横向滚动条,提升美观度。
(1)多类型健康记录卡片
tsx
const renderRecordItem = ({ item }: { item: HealthRecord }) => (
<View style={styles.recordCard}>
<View style={styles.headerContainer}>
{/* 图标区域 */}
<View style={styles.iconContainer}>
<Text style={styles.recordIcon}>{item.icon}</Text>
</View>
{/* 标题和时间区域 */}
<View style={styles.titleContainer}>
<Text style={styles.recordTitle}>{item.title}</Text>
<Text style={styles.recordDate}>{item.date} {item.time}</Text>
</View>
{/* 数值和单位区域 */}
<View style={styles.valueContainer}>
<Text style={styles.recordValue}>{item.value}</Text>
<Text style={styles.recordUnit}>{item.unit}</Text>
</View>
</View>
{/* 状态徽章 */}
<View style={styles.statusContainer}>
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(item.status) }]}>
<Text style={styles.statusText}>
{item.status === 'normal' ? '正常' :
item.status === 'warning' ? '警告' : '危险'}
</Text>
</View>
</View>
{/* 备注信息 */}
<Text style={styles.notesText}>{item.notes}</Text>
{/* 操作按钮 */}
<View style={styles.actionContainer}>
<TouchableOpacity
style={styles.actionButton}
onPress={() => Alert.alert('编辑', `编辑 ${item.title} 记录`)}
>
<Text style={styles.actionButtonText}>编辑</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionButton}
onPress={() => Alert.alert('详情', `查看 ${item.title} 详细信息`)}
>
<Text style={styles.actionButtonText}>详情</Text>
</TouchableOpacity>
</View>
</View>
);
卡片设计亮点:
- 信息层级清晰:图标→标题时间→数值→状态→备注→操作,符合信息阅读顺序;
- 布局合理:使用 flex 布局实现不同区域的合理分配,适配不同类型的健康数据;
- 视觉突出:数值使用大号粗体,状态使用色彩徽章,关键信息一目了然;
- 交互友好:操作按钮放置在卡片底部右侧,不干扰主要信息阅读;
- 样式统一:统一的卡片样式,保证不同类型健康记录的视觉一致性;
- 响应式适配:使用 flex 比例适配不同屏幕尺寸,布局稳定。
(2)快捷操作区
tsx
<View style={styles.quickActionsContainer}>
<Text style={styles.sectionTitle}>快捷操作</Text>
<View style={styles.quickActionsRow}>
<TouchableOpacity
style={styles.quickActionItem}
onPress={() => Alert.alert('添加血压记录', '记录血压数据')}
>
<Text style={styles.quickActionIcon}>🩸</Text>
<Text style={styles.quickActionText}>血压</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.quickActionItem}
onPress={() => Alert.alert('添加体重记录', '记录体重数据')}
>
<Text style={styles.quickActionIcon}>⚖️</Text>
<Text style={styles.quickActionText}>体重</Text>
</TouchableOpacity>
{/* 其他快捷操作项 */}
</View>
</View>
设计优势:
- 操作便捷性:常用健康记录的快捷入口,减少用户操作步骤;
- 视觉识别度高:emoji 图标+文字组合,直观易懂;
- 布局均衡:使用 flex 布局实现等宽分布,视觉效果协调;
- 交互反馈:TouchableOpacity 提供点击反馈,提升交互体验;
- 扩展性强:新增快捷操作仅需添加新的 TouchableOpacity 组件。
(3)健康数据
tsx
<View style={styles.statsContainer}>
<View style={styles.statItem}>
<Text style={styles.statValue}>120/80</Text>
<Text style={styles.statLabel}>血压</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statValue}>65.5 kg</Text>
<Text style={styles.statLabel}>体重</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statValue}>8,500</Text>
<Text style={styles.statLabel}>步数</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statValue}>7.5h</Text>
<Text style={styles.statLabel}>睡眠</Text>
</View>
</View>
统计设计亮点:
- 核心指标聚合:展示关键健康指标的最新值,便于用户快速了解健康状况;
- 视觉层次分明:数值使用大号蓝色粗体,标签使用小号灰色,对比鲜明;
- 布局均衡:四列等宽布局,视觉效果稳定;
- 单位整合:将单位直接整合到数值中(如"65.5 kg"),信息更紧凑;
- 医疗专业性:数值格式符合医疗数据展示规范。
5. 样式系统
该应用的样式系统体现了健康管理类应用的专业设计原则:
typescript
const styles = StyleSheet.create({
// 基础容器样式
container: {
flex: 1,
backgroundColor: '#f8fafc', // 浅灰背景,降低视觉疲劳
},
// 卡片样式(核心)
recordCard: {
backgroundColor: '#ffffff', // 白色卡片
borderRadius: 12, // 大圆角设计,提升友好性
padding: 16,
marginBottom: 12,
// 跨平台阴影
elevation: 1, // Android阴影
shadowColor: '#000', // iOS阴影
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
// 筛选器样式
filterItem: {
backgroundColor: '#f1f5f9',
borderRadius: 20, // 胶囊形设计
paddingVertical: 8,
paddingHorizontal: 16,
marginRight: 12,
alignItems: 'center',
},
activeFilter: {
backgroundColor: '#3b82f6', // 激活状态蓝色背景
},
// 状态徽章样式
statusBadge: {
paddingHorizontal: 12,
paddingVertical: 4,
borderRadius: 12, // 胶囊形徽章
},
// 其他样式...
});
样式设计原则:
- 健康UI风格:简洁、专业、低饱和度的色彩体系,符合健康管理应用设计规范;
- 视觉层次清晰:通过阴影、圆角、间距构建卡片层级,提升可读性;
- 交互反馈明确:筛选器激活状态使用蓝色背景,视觉反馈清晰;
- 响应式布局:使用 flex 布局适配不同屏幕尺寸;
- 平台兼容:同时设置 elevation 和 shadow 实现跨平台一致的阴影效果;
- 文本层级:通过字体大小、粗细、颜色区分不同重要程度的信息。
将该 React Native 多类型健康记录应用适配到鸿蒙平台,核心是将 React 的多类型数据管理、水平滚动筛选、FlatList 高性能渲染、动态样式映射等核心能力映射到鸿蒙 ArkTS + ArkUI 生态,以下是完整的适配方案。
1. 核心技术栈映射
| React Native 核心能力 | 鸿蒙 ArkTS 对应实现 | 适配关键说明 |
|---|---|---|
useState 状态管理 |
@State/@Link/@DerivedState |
基础状态+派生状态组合使用 |
FlatList 高性能列表 |
List + ForEach + ListItem |
高性能列表渲染适配 |
| 水平滚动 ScrollView | Scroll + scrollDirection: Axis.Horizontal |
横向滚动适配 |
| 动态样式数组 | 条件样式 + @Styles 装饰器 |
状态驱动的样式适配 |
Alert.alert 弹窗 |
AlertDialog 组件 |
交互弹窗替换 |
TouchableOpacity |
Button + stateEffect(true) |
可点击组件替换 |
StyleSheet.create |
@Styles/@Extend + 链式样式 |
样式体系重构 |
| 数据过滤逻辑 | 完全复用(TypeScript 逻辑) | 业务逻辑零修改 |
showsVerticalScrollIndicator |
scrollBar(BarState.Off) |
滚动条控制适配 |
| 水平滚动筛选器 | Scroll(Axis.Horizontal) + Row |
横向筛选器适配 |
2. 鸿蒙端
tsx
// index.ets - 鸿蒙端多类型健康记录应用完整实现
import { BusinessError } from '@ohos.base';
// 类型定义(与RN端完全一致)
type HealthRecord = {
id: string;
type: 'bloodPressure' | 'weight' | 'steps' | 'sleep' | 'temperature' | 'medication';
title: string;
value: string;
unit: string;
date: string;
time: string;
notes: string;
status: 'normal' | 'warning' | 'critical';
icon: string;
};
@Entry
@Component
struct HealthRecordListApp {
// 基础状态(对应RN的useState)
@State records: HealthRecord[] = [
{
id: '1',
type: 'bloodPressure',
title: '血压',
value: '120/80',
unit: 'mmHg',
date: '2023-05-15',
time: '08:30',
notes: '早晨测量,感觉良好',
status: 'normal',
icon: '🩸'
},
{
id: '2',
type: 'weight',
title: '体重',
value: '65.5',
unit: 'kg',
date: '2023-05-14',
time: '07:15',
notes: '晨起空腹称重',
status: 'normal',
icon: '⚖️'
},
{
id: '3',
type: 'steps',
title: '步数',
value: '8500',
unit: '步',
date: '2023-05-14',
time: '23:59',
notes: '步行运动',
status: 'normal',
icon: '👣'
},
{
id: '4',
type: 'sleep',
title: '睡眠',
value: '7.5',
unit: '小时',
date: '2023-05-14',
time: '07:00',
notes: '深度睡眠 3.2 小时',
status: 'normal',
icon: '😴'
},
{
id: '5',
type: 'temperature',
title: '体温',
value: '36.8',
unit: '°C',
date: '2023-05-13',
time: '20:00',
notes: '正常体温',
status: 'normal',
icon: '🌡️'
},
{
id: '6',
type: 'medication',
title: '服药',
value: '降压药',
unit: '次',
date: '2023-05-13',
time: '09:00',
notes: '按时服用',
status: 'normal',
icon: '💊'
}
];
@State activeFilter: string = 'all';
// 通用样式封装 - 卡片容器样式
@Styles
cardStyle() {
.backgroundColor('#ffffff')
.borderRadius(12)
.shadow({ radius: 2, color: '#000', opacity: 0.1, offsetX: 0, offsetY: 1 });
}
// 通用样式封装 - 筛选器项样式
@Styles
filterItemStyle(isActive: boolean) {
.backgroundColor(isActive ? '#3b82f6' : '#f1f5f9')
.borderRadius(20)
.paddingVertical(8)
.paddingHorizontal(16)
.marginRight(12)
.alignItems(ItemAlign.Center);
}
// 获取状态颜色(完全复用RN端逻辑)
private getStatusColor(status: string): string {
switch (status) {
case 'normal': return '#10b981';
case 'warning': return '#f59e0b';
case 'critical': return '#ef4444';
default: return '#64748b';
}
}
// 派生状态 - 过滤后的记录(对应RN的filteredRecords)
@DerivedState
get filteredRecords(): HealthRecord[] {
return this.activeFilter === 'all'
? this.records
: this.records.filter(record => record.type === this.activeFilter);
}
// 筛选器配置(与RN端完全一致)
private filters = [
{ id: 'all', name: '全部', icon: '📋' },
{ id: 'bloodPressure', name: '血压', icon: '🩸' },
{ id: 'weight', name: '体重', icon: '⚖️' },
{ id: 'steps', name: '步数', icon: '👣' },
{ id: 'sleep', name: '睡眠', icon: '😴' },
{ id: 'temperature', name: '体温', icon: '🌡️' },
{ id: 'medication', name: '用药', icon: '💊' },
];
// 渲染健康记录项
@Builder
renderRecordItem(item: HealthRecord) {
Column({ space: 12 }) {
// 头部区域(图标+标题+数值)
Row({ space: 12, alignItems: ItemAlign.Center }) {
// 图标
Text(item.icon)
.fontSize(24);
// 标题和时间
Column({ space: 4, flex: 1 }) {
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
Text(`${item.date} ${item.time}`)
.fontSize(12)
.fontColor('#64748b');
}
// 数值和单位
Column({ space: 0, alignItems: ItemAlign.End }) {
Text(item.value)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
Text(item.unit)
.fontSize(12)
.fontColor('#64748b');
}
}
.width('100%');
// 状态徽章
Row({ space: 0 }) {
Text(item.status === 'normal' ? '正常' :
item.status === 'warning' ? '警告' : '危险')
.fontSize(12)
.fontColor('#ffffff')
.fontWeight(FontWeight.Medium)
.backgroundColor(this.getStatusColor(item.status))
.paddingHorizontal(12)
.paddingVertical(4)
.borderRadius(12);
}
.width('100%');
// 备注信息
Text(item.notes)
.fontSize(14)
.fontColor('#64748b')
.width('100%');
// 操作按钮
Row({ space: 8, justifyContent: FlexAlign.End }) {
Button('编辑')
.backgroundColor('#f1f5f9')
.paddingHorizontal(12)
.paddingVertical(6)
.borderRadius(6)
.fontSize(12)
.fontColor('#3b82f6')
.fontWeight(FontWeight.Medium)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '编辑',
message: `编辑 ${item.title} 记录`,
confirm: { value: '确定' }
});
});
Button('详情')
.backgroundColor('#f1f5f9')
.paddingHorizontal(12)
.paddingVertical(6)
.borderRadius(6)
.fontSize(12)
.fontColor('#3b82f6')
.fontWeight(FontWeight.Medium)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '详情',
message: `查看 ${item.title} 详细信息`,
confirm: { value: '确定' }
});
});
}
.width('100%');
}
.cardStyle()
.padding(16)
.marginBottom(12)
.width('100%');
}
// 渲染水平筛选器
@Builder
renderFilterBar() {
Column({ space: 8 }) {
Text('记录类型')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.width('100%');
// 水平滚动筛选器
Scroll({ scrollDirection: Axis.Horizontal, scrollBar: BarState.Off }) {
Row({ space: 0 }) {
ForEach(this.filters, (filter) => {
Button()
.filterItemStyle(this.activeFilter === filter.id)
.stateEffect(true)
.onClick(() => this.activeFilter = filter.id) {
Column({ space: 4 }) {
Text(filter.icon)
.fontSize(16);
Text(filter.name)
.fontSize(12)
.fontColor(this.activeFilter === filter.id ? '#ffffff' : '#64748b');
}
};
});
}
}
.width('100%');
}
.marginBottom(16)
.width('100%');
}
// 渲染快捷操作区
@Builder
renderQuickActions() {
Column({ space: 8 }) {
Text('快捷操作')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.width('100%');
Row({ space: 8 }) {
// 血压快捷操作
Button()
.flex(1)
.cardStyle()
.padding(12)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '添加血压记录',
message: '记录血压数据',
confirm: { value: '确定' }
});
}) {
Column({ space: 4, alignItems: ItemAlign.Center }) {
Text('🩸')
.fontSize(24);
Text('血压')
.fontSize(12)
.fontColor('#1e293b');
}
};
// 体重快捷操作
Button()
.flex(1)
.cardStyle()
.padding(12)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '添加体重记录',
message: '记录体重数据',
confirm: { value: '确定' }
});
}) {
Column({ space: 4, alignItems: ItemAlign.Center }) {
Text('⚖️')
.fontSize(24);
Text('体重')
.fontSize(12)
.fontColor('#1e293b');
}
};
// 步数快捷操作
Button()
.flex(1)
.cardStyle()
.padding(12)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '添加步数记录',
message: '记录步数数据',
confirm: { value: '确定' }
});
}) {
Column({ space: 4, alignItems: ItemAlign.Center }) {
Text('👣')
.fontSize(24);
Text('步数')
.fontSize(12)
.fontColor('#1e293b');
}
};
// 睡眠快捷操作
Button()
.flex(1)
.cardStyle()
.padding(12)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '添加睡眠记录',
message: '记录睡眠数据',
confirm: { value: '确定' }
});
}) {
Column({ space: 4, alignItems: ItemAlign.Center }) {
Text('😴')
.fontSize(24);
Text('睡眠')
.fontSize(12)
.fontColor('#1e293b');
}
};
}
.width('100%');
}
.marginBottom(16)
.width('100%');
}
// 渲染统计摘要
@Builder
renderStatsSummary() {
Row({ space: 0 }) {
// 血压统计
Column({ space: 4, flex: 1 }) {
Text('120/80')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#3b82f6');
Text('血压')
.fontSize(12)
.fontColor('#64748b');
}
.alignItems(ItemAlign.Center);
// 体重统计
Column({ space: 4, flex: 1 }) {
Text('65.5 kg')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#3b82f6');
Text('体重')
.fontSize(12)
.fontColor('#64748b');
}
.alignItems(ItemAlign.Center);
// 步数统计
Column({ space: 4, flex: 1 }) {
Text('8,500')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#3b82f6');
Text('步数')
.fontSize(12)
.fontColor('#64748b');
}
.alignItems(ItemAlign.Center);
// 睡眠统计
Column({ space: 4, flex: 1 }) {
Text('7.5h')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#3b82f6');
Text('睡眠')
.fontSize(12)
.fontColor('#64748b');
}
.alignItems(ItemAlign.Center);
}
.cardStyle()
.padding(16)
.marginBottom(16)
.width('100%');
}
// 渲染使用说明
@Builder
renderInfoSection() {
Column({ space: 8 }) {
Text('使用说明')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.marginBottom(4);
Text('• 定期记录您的健康数据')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 异常数据会显示警告状态')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 点击编辑按钮修改记录')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 建议每天定时记录关键指标')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
}
.cardStyle()
.padding(16)
.width('100%');
}
build() {
Column({ space: 0 }) {
// 头部导航栏
this.Header();
// 内容区域(滚动容器)
Scroll() {
Column({ space: 16 }) {
// 搜索栏
this.SearchBar();
// 水平筛选器
this.renderFilterBar();
// 记录列表标题
Row({ space: 0 }) {
Text('健康记录')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
Text(`${this.filteredRecords.length} 条记录`)
.fontSize(14)
.fontColor('#64748b')
.marginLeft('auto');
}
.width('100%')
.marginBottom(12);
// 健康记录列表(替代RN的FlatList)
List({ space: 0, scrollBar: BarState.Off }) {
ForEach(this.filteredRecords, (item) => {
ListItem() {
this.renderRecordItem(item);
}
}, (item) => item.id);
}
.width('100%')
.shrink(0)
.edgeEffect(EdgeEffect.None);
// 快捷操作区
this.renderQuickActions();
// 统计摘要
this.renderStatsSummary();
// 使用说明
this.renderInfoSection();
}
.padding(16)
.width('100%');
}
.flex(1)
.width('100%');
// 底部导航
this.BottomNav();
}
.width('100%')
.height('100%')
.backgroundColor('#f8fafc')
.safeArea(true);
}
// 头部导航栏 - Builder函数封装
@Builder
Header() {
Row({ space: 0 }) {
Text('健康记录')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
// 添加按钮
Button('+')
.width(36)
.height(36)
.borderRadius(18)
.backgroundColor('#3b82f6')
.fontSize(20)
.fontColor('#ffffff')
.fontWeight(FontWeight.Bold)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '添加记录',
message: '选择要添加的健康记录类型',
confirm: { value: '确定' }
});
})
.marginLeft('auto');
}
.padding(20)
.backgroundColor('#ffffff')
.borderBottom({ width: 1, color: '#e2e8f0' })
.width('100%');
}
// 搜索栏 - Builder函数封装
@Builder
SearchBar() {
Row({ space: 12 }) {
Text('🔍')
.fontSize(18)
.fontColor('#64748b');
Text('搜索健康记录')
.fontSize(14)
.fontColor('#94a3b8')
.flex(1);
}
.cardStyle()
.paddingVertical(12)
.paddingHorizontal(16)
.borderRadius(20)
.width('100%');
}
// 底部导航 - Builder函数封装
@Builder
BottomNav() {
Row({ space: 0 }) {
// 首页
Button()
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({ title: '首页', message: '首页功能' });
}) {
Column({ space: 4 }) {
Text('🏠')
.fontSize(20)
.fontColor('#94a3b8');
Text('首页')
.fontSize(12)
.fontColor('#94a3b8');
}
};
// 记录
Button()
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({ title: '记录', message: '记录管理' });
}) {
Column({ space: 4 }) {
Text('📋')
.fontSize(20)
.fontColor('#94a3b8');
Text('记录')
.fontSize(12)
.fontColor('#94a3b8');
}
};
// 图表
Button()
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({ title: '图表', message: '数据统计' });
}) {
Column({ space: 4 }) {
Text('📊')
.fontSize(20)
.fontColor('#94a3b8');
Text('图表')
.fontSize(12)
.fontColor('#94a3b8');
}
};
// 我的
Button()
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({ title: '我的', message: '个人中心' });
}) {
Column({ space: 4 }) {
Text('👤')
.fontSize(20)
.fontColor('#94a3b8');
Text('我的')
.fontSize(12)
.fontColor('#94a3b8');
}
};
}
.backgroundColor('#ffffff')
.borderTop({ width: 1, color: '#e2e8f0' })
.paddingVertical(12)
.width('100%');
}
}
3. 关键适配点详解
(1)FlatList 高性能列表适配
React Native 的 FlatList 适配为鸿蒙的 List + ForEach 组合,保持高性能渲染特性:
tsx
// React Native - FlatList 实现
<FlatList
data={filteredRecords}
renderItem={renderRecordItem}
keyExtractor={item => item.id}
showsVerticalScrollIndicator={false}
/>
// 鸿蒙 - List + ForEach 实现
List({ space: 0, scrollBar: BarState.Off }) {
ForEach(this.filteredRecords, (item) => {
ListItem() {
this.renderRecordItem(item);
}
}, (item) => item.id); // 唯一标识,对应keyExtractor
}
.width('100%')
.shrink(0)
.edgeEffect(EdgeEffect.None);
(2)水平滚动筛选器
React Native 的水平滚动 ScrollView 适配为鸿蒙的 Scroll(Axis.Horizontal),保持筛选器交互体验:
tsx
// React Native - 水平滚动筛选器
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.filtersContainer}
>
<View style={styles.filtersList}>
{filters.map(filter => (
<TouchableOpacity
key={filter.id}
style={[
styles.filterItem,
activeFilter === filter.id && styles.activeFilter
]}
onPress={() => setActiveFilter(filter.id)}
>
<Text style={styles.filterIcon}>{filter.icon}</Text>
<Text style={styles.filterText}>{filter.name}</Text>
</TouchableOpacity>
))}
</View>
</ScrollView>
// 鸿蒙 - 水平滚动筛选器
Scroll({ scrollDirection: Axis.Horizontal, scrollBar: BarState.Off }) {
Row({ space: 0 }) {
ForEach(this.filters, (filter) => {
Button()
.filterItemStyle(this.activeFilter === filter.id) // 条件样式
.stateEffect(true)
.onClick(() => this.activeFilter = filter.id) {
Column({ space: 4 }) {
Text(filter.icon)
.fontSize(16);
Text(filter.name)
.fontSize(12)
.fontColor(this.activeFilter === filter.id ? '#ffffff' : '#64748b');
}
};
});
}
}
.width('100%');
适配技巧:
- 滚动方向配置 :
scrollDirection: Axis.Horizontal对应horizontal={true}; - 滚动条控制 :
scrollBar: BarState.Off对应showsHorizontalScrollIndicator={false}; - 条件样式简化 :使用
@Styles装饰器封装条件样式,代码更简洁; - 交互反馈增强 :
stateEffect(true)提供点击反馈,体验优于 TouchableOpacity; - 布局结构优化:使用 Row 替代 View + flexDirection: row,代码更简洁;
- 样式内聚:将激活状态的文字颜色变化内聚到 Button 内部,逻辑更清晰。
(3)派生状态实现数据过滤
React Native 的声明式过滤逻辑适配为鸿蒙的 @DerivedState,实现响应式数据过滤:
tsx
// React Native - 声明式过滤
const filteredRecords = activeFilter === 'all'
? records
: records.filter(record => record.type === activeFilter);
// 鸿蒙 - 派生状态过滤
@DerivedState
get filteredRecords(): HealthRecord[] {
return this.activeFilter === 'all'
? this.records
: this.records.filter(record => record.type === this.activeFilter);
}
适配优势:
- 响应式更新:@DerivedState 自动响应依赖状态变化,无需手动触发;
- 类型安全:明确的返回值类型定义,避免类型错误;
- 代码内聚:过滤逻辑封装在组件内部,代码更内聚;
- 性能优化:派生状态仅在依赖变化时重新计算,避免不必要的计算;
- 逻辑复用:过滤逻辑 100% 复用,仅修改状态访问方式。
(4)动态样式与状态可视化适配
React Native 的样式数组适配为鸿蒙的条件样式,保持健康状态的可视化效果:
tsx
// React Native - 动态状态样式
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(item.status) }]}>
<Text style={styles.statusText}>
{item.status === 'normal' ? '正常' : '危险'}
</Text>
</View>
// 鸿蒙 - 条件样式实现
Text(item.status === 'normal' ? '正常' : '危险')
.fontSize(12)
.fontColor('#ffffff')
.fontWeight(FontWeight.Medium)
.backgroundColor(this.getStatusColor(item.status)) // 条件样式
.paddingHorizontal(12)
.paddingVertical(4)
.borderRadius(12);
该多类型健康记录应用的跨端适配实践验证了健康管理类应用从 React Native 向鸿蒙迁移的高效性,核心的数据模型和业务逻辑可实现完全复用,仅需适配UI组件层和交互层。这种适配模式特别适合多类型数据管理的健康应用开发,能够在保证功能完整性的前提下,显著提升跨端开发效率,同时利用鸿蒙的原生能力提升应用性能和用户体验。
真实演示案例代码:
js
// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList } from 'react-native';
// Base64 图标库
const ICONS_BASE64 = {
home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
health: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
bloodPressure: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
weight: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
steps: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
sleep: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
notes: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
more: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};
const { width, height } = Dimensions.get('window');
// 健康记录类型
type HealthRecord = {
id: string;
type: 'bloodPressure' | 'weight' | 'steps' | 'sleep' | 'temperature' | 'medication';
title: string;
value: string;
unit: string;
date: string;
time: string;
notes: string;
status: 'normal' | 'warning' | 'critical';
icon: string;
};
const HealthRecordListApp: React.FC = () => {
const [records, setRecords] = useState<HealthRecord[]>([
{
id: '1',
type: 'bloodPressure',
title: '血压',
value: '120/80',
unit: 'mmHg',
date: '2023-05-15',
time: '08:30',
notes: '早晨测量,感觉良好',
status: 'normal',
icon: '🩸'
},
{
id: '2',
type: 'weight',
title: '体重',
value: '65.5',
unit: 'kg',
date: '2023-05-14',
time: '07:15',
notes: '晨起空腹称重',
status: 'normal',
icon: '⚖️'
},
{
id: '3',
type: 'steps',
title: '步数',
value: '8500',
unit: '步',
date: '2023-05-14',
time: '23:59',
notes: '步行运动',
status: 'normal',
icon: '👣'
},
{
id: '4',
type: 'sleep',
title: '睡眠',
value: '7.5',
unit: '小时',
date: '2023-05-14',
time: '07:00',
notes: '深度睡眠 3.2 小时',
status: 'normal',
icon: '😴'
},
{
id: '5',
type: 'temperature',
title: '体温',
value: '36.8',
unit: '°C',
date: '2023-05-13',
time: '20:00',
notes: '正常体温',
status: 'normal',
icon: '🌡️'
},
{
id: '6',
type: 'medication',
title: '服药',
value: '降压药',
unit: '次',
date: '2023-05-13',
time: '09:00',
notes: '按时服用',
status: 'normal',
icon: '💊'
}
]);
const [activeFilter, setActiveFilter] = useState<string>('all');
// 获取状态颜色
const getStatusColor = (status: string): string => {
switch (status) {
case 'normal': return '#10b981'; // 绿色
case 'warning': return '#f59e0b'; // 橙色
case 'critical': return '#ef4444'; // 红色
default: return '#64748b'; // 灰色
}
};
// 根据类型过滤记录
const filteredRecords = activeFilter === 'all'
? records
: records.filter(record => record.type === activeFilter);
// 渲染记录项
const renderRecordItem = ({ item }: { item: HealthRecord }) => (
<View style={styles.recordCard}>
<View style={styles.headerContainer}>
<View style={styles.iconContainer}>
<Text style={styles.recordIcon}>{item.icon}</Text>
</div>
<View style={styles.titleContainer}>
<Text style={styles.recordTitle}>{item.title}</Text>
<Text style={styles.recordDate}>{item.date} {item.time}</Text>
</div>
<View style={styles.valueContainer}>
<Text style={styles.recordValue}>{item.value}</Text>
<Text style={styles.recordUnit}>{item.unit}</Text>
</div>
</View>
<View style={styles.statusContainer}>
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(item.status) }]}>
<Text style={styles.statusText}>
{item.status === 'normal' ? '正常' :
item.status === 'warning' ? '警告' : '危险'}
</Text>
</div>
</div>
<Text style={styles.notesText}>{item.notes}</Text>
<View style={styles.actionContainer}>
<TouchableOpacity
style={styles.actionButton}
onPress={() => Alert.alert('编辑', `编辑 ${item.title} 记录`)}
>
<Text style={styles.actionButtonText}>编辑</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionButton}
onPress={() => Alert.alert('详情', `查看 ${item.title} 详细信息`)}
>
<Text style={styles.actionButtonText}>详情</Text>
</TouchableOpacity>
</div>
</View>
);
// 过滤器选项
const filters = [
{ id: 'all', name: '全部', icon: '📋' },
{ id: 'bloodPressure', name: '血压', icon: '🩸' },
{ id: 'weight', name: '体重', icon: '⚖️' },
{ id: 'steps', name: '步数', icon: '👣' },
{ id: 'sleep', name: '睡眠', icon: '😴' },
{ id: 'temperature', name: '体温', icon: '🌡️' },
{ id: 'medication', name: '用药', icon: '💊' },
];
return (
<SafeAreaView style={styles.container}>
{/* 头部 */}
<View style={styles.header}>
<Text style={styles.title}>健康记录</Text>
<TouchableOpacity
style={styles.addButton}
onPress={() => Alert.alert('添加记录', '选择要添加的健康记录类型')}
>
<Text style={styles.addButtonText}>+</Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.content}>
{/* 搜索栏 */}
<View style={styles.searchContainer}>
<Text style={styles.searchIcon}>🔍</Text>
<Text style={styles.searchPlaceholder}>搜索健康记录</Text>
</div>
{/* 过滤器 */}
<Text style={styles.sectionTitle}>记录类型</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.filtersContainer}
>
<View style={styles.filtersList}>
{filters.map(filter => (
<TouchableOpacity
key={filter.id}
style={[
styles.filterItem,
activeFilter === filter.id && styles.activeFilter
]}
onPress={() => setActiveFilter(filter.id)}
>
<Text style={styles.filterIcon}>{filter.icon}</Text>
<Text style={styles.filterText}>{filter.name}</Text>
</TouchableOpacity>
))}
</View>
</ScrollView>
{/* 记录列表标题 */}
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>健康记录</Text>
<Text style={styles.countText}>{filteredRecords.length} 条记录</Text>
</div>
{/* 记录列表 */}
<FlatList
data={filteredRecords}
renderItem={renderRecordItem}
keyExtractor={item => item.id}
showsVerticalScrollIndicator={false}
/>
{/* 快捷操作 */}
<View style={styles.quickActionsContainer}>
<Text style={styles.sectionTitle}>快捷操作</Text>
<View style={styles.quickActionsRow}>
<TouchableOpacity
style={styles.quickActionItem}
onPress={() => Alert.alert('添加血压记录', '记录血压数据')}
>
<Text style={styles.quickActionIcon}>🩸</Text>
<Text style={styles.quickActionText}>血压</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.quickActionItem}
onPress={() => Alert.alert('添加体重记录', '记录体重数据')}
>
<Text style={styles.quickActionIcon}>⚖️</Text>
<Text style={styles.quickActionText}>体重</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.quickActionItem}
onPress={() => Alert.alert('添加步数记录', '记录步数数据')}
>
<Text style={styles.quickActionIcon}>👣</Text>
<Text style={styles.quickActionText}>步数</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.quickActionItem}
onPress={() => Alert.alert('添加睡眠记录', '记录睡眠数据')}
>
<Text style={styles.quickActionIcon}>😴</Text>
<Text style={styles.quickActionText}>睡眠</Text>
</TouchableOpacity>
</View>
</div>
{/* 统计摘要 */}
<View style={styles.statsContainer}>
<View style={styles.statItem}>
<Text style={styles.statValue}>120/80</Text>
<Text style={styles.statLabel}>血压</Text>
</div>
<View style={styles.statItem}>
<Text style={styles.statValue}>65.5 kg</Text>
<Text style={styles.statLabel}>体重</Text>
</div>
<View style={styles.statItem}>
<Text style={styles.statValue}>8,500</Text>
<Text style={styles.statLabel}>步数</Text>
</div>
<View style={styles.statItem}>
<Text style={styles.statValue}>7.5h</Text>
<Text style={styles.statLabel}>睡眠</Text>
</div>
</div>
{/* 使用说明 */}
<View style={styles.infoCard}>
<Text style={styles.infoTitle}>使用说明</Text>
<Text style={styles.infoText}>• 定期记录您的健康数据</Text>
<Text style={styles.infoText}>• 异常数据会显示警告状态</Text>
<Text style={styles.infoText}>• 点击编辑按钮修改记录</Text>
<Text style={styles.infoText}>• 建议每天定时记录关键指标</Text>
</View>
</ScrollView>
{/* 底部导航 */}
<View style={styles.bottomNav}>
<TouchableOpacity
style={styles.navItem}
onPress={() => Alert.alert('首页')}
>
<Text style={styles.navIcon}>🏠</Text>
<Text style={styles.navText}>首页</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.navItem}
onPress={() => Alert.alert('记录')}
>
<Text style={styles.navIcon}>📋</Text>
<Text style={styles.navText}>记录</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.navItem}
onPress={() => Alert.alert('图表')}
>
<Text style={styles.navIcon}>📊</Text>
<Text style={styles.navText}>图表</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.navItem}
onPress={() => Alert.alert('我的')}
>
<Text style={styles.navIcon}>👤</Text>
<Text style={styles.navText}>我的</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 20,
backgroundColor: '#ffffff',
borderBottomWidth: 1,
borderBottomColor: '#e2e8f0',
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#1e293b',
},
addButton: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#3b82f6',
alignItems: 'center',
justifyContent: 'center',
},
addButtonText: {
fontSize: 20,
color: '#ffffff',
fontWeight: 'bold',
},
content: {
flex: 1,
padding: 16,
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#ffffff',
borderRadius: 20,
paddingVertical: 12,
paddingHorizontal: 16,
marginBottom: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
searchIcon: {
fontSize: 18,
color: '#64748b',
},
searchPlaceholder: {
fontSize: 14,
color: '#94a3b8',
marginLeft: 12,
flex: 1,
},
filtersContainer: {
marginBottom: 16,
},
filtersList: {
flexDirection: 'row',
},
filterItem: {
backgroundColor: '#f1f5f9',
borderRadius: 20,
paddingVertical: 8,
paddingHorizontal: 16,
marginRight: 12,
alignItems: 'center',
},
activeFilter: {
backgroundColor: '#3b82f6',
},
filterIcon: {
fontSize: 16,
marginBottom: 4,
},
filterText: {
fontSize: 12,
color: '#64748b',
},
activeFilterText: {
color: '#ffffff',
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#1e293b',
},
countText: {
fontSize: 14,
color: '#64748b',
},
recordCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginBottom: 12,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
headerContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
iconContainer: {
marginRight: 12,
},
recordIcon: {
fontSize: 24,
},
titleContainer: {
flex: 1,
},
recordTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 4,
},
recordDate: {
fontSize: 12,
color: '#64748b',
},
valueContainer: {
alignItems: 'flex-end',
},
recordValue: {
fontSize: 20,
fontWeight: 'bold',
color: '#1e293b',
},
recordUnit: {
fontSize: 12,
color: '#64748b',
},
statusContainer: {
alignItems: 'flex-start',
marginBottom: 12,
},
statusBadge: {
paddingHorizontal: 12,
paddingVertical: 4,
borderRadius: 12,
},
statusText: {
fontSize: 12,
color: '#ffffff',
fontWeight: '500',
},
notesText: {
fontSize: 14,
color: '#64748b',
marginBottom: 12,
},
actionContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
},
actionButton: {
backgroundColor: '#f1f5f9',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 6,
marginRight: 8,
},
actionButtonText: {
fontSize: 12,
color: '#3b82f6',
fontWeight: '500',
},
quickActionsContainer: {
marginBottom: 16,
},
quickActionsRow: {
flexDirection: 'row',
justifyContent: 'space-between',
},
quickActionItem: {
flex: 1,
alignItems: 'center',
padding: 12,
backgroundColor: '#ffffff',
borderRadius: 12,
marginHorizontal: 4,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
quickActionIcon: {
fontSize: 24,
marginBottom: 4,
},
quickActionText: {
fontSize: 12,
color: '#1e293b',
},
statsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginBottom: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
statItem: {
alignItems: 'center',
},
statValue: {
fontSize: 16,
fontWeight: 'bold',
color: '#3b82f6',
},
statLabel: {
fontSize: 12,
color: '#64748b',
marginTop: 4,
textAlign: 'center',
},
infoCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginTop: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
infoTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 12,
},
infoText: {
fontSize: 14,
color: '#64748b',
lineHeight: 22,
marginBottom: 8,
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
backgroundColor: '#ffffff',
borderTopWidth: 1,
borderTopColor: '#e2e8f0',
paddingVertical: 12,
},
navItem: {
alignItems: 'center',
flex: 1,
},
navIcon: {
fontSize: 20,
color: '#94a3b8',
marginBottom: 4,
},
navText: {
fontSize: 12,
color: '#94a3b8',
},
});
export default HealthRecordListApp;

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

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

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

本文分析了一个React Native健康记录应用的实现方案,重点探讨了其架构设计、状态管理和功能特性。应用采用清晰的单组件架构,使用useState管理健康记录和过滤状态,支持多种健康数据类型(血压、体重等)并实现按类型过滤功能。记录卡片展示详细信息并依据健康状态显示不同颜色标记。应用使用TypeScript确保类型安全,并通过FlatList优化列表性能。文章还提出了进一步优化建议,包括使用useReducer管理复杂状态、集成AsyncStorage实现数据持久化等,为开发类似健康管理应用提供了有价值的参考。