React Native鸿蒙跨平台使用useState管理健康记录和过滤状态,支持多种健康数据类型(血压、体重等)并实现按类型过滤功能

在移动应用开发中,健康管理类应用是一种重要的应用类型,需要考虑数据记录、状态展示、用户交互等多个方面。本文将深入分析一个功能完备的 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 联合类型确保了 typestatus 的取值只能是预定义的几个选项之一,提高了代码的类型安全性。

数据组织

应用数据按照功能模块进行组织:

  • 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实现数据持久化等,为开发类似健康管理应用提供了有价值的参考。

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

相关推荐
打小就很皮...2 小时前
dnd-kit 实现表格拖拽排序
前端·react.js·表格拖拽·dnd-kit
Ulyanov2 小时前
从静态到沉浸:打造惊艳的Web技术发展历程3D时间轴
前端·javascript·html5·gui开发
2501_921930832 小时前
高级进阶 React Native 鸿蒙跨平台开发:InteractionManager 交互优化
react native·harmonyos
打小就很皮...2 小时前
React 19 + Vite 6 + SWC 构建优化实践
前端·react.js·vite·swc
Highcharts.js2 小时前
使用Highcharts与React集成 官网文档使用说明
前端·react.js·前端框架·react·highcharts·官方文档
前端不太难2 小时前
HarmonyOS PC 文档模型完整范式
华为·状态模式·harmonyos
VT.馒头2 小时前
【力扣】2625. 扁平化嵌套数组
前端·javascript·算法·leetcode·职场和发展·typescript
毎天要喝八杯水3 小时前
搭建vue前端后端环境
前端·javascript·vue.js
雨季6664 小时前
Flutter 三端应用实战:OpenHarmony “极简手势轨迹球”——指尖与屏幕的诗意对话
开发语言·javascript·flutter