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

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

相关推荐
lerhxx2 小时前
AI 应用如何高效优雅地恢复中断?—— "连接解耦 + 状态持久化"
前端·javascript
梦想不只是梦与想2 小时前
HarmonyOS应用分层架构设计
harmonyos·分层架构·一次开发,多端部署
mantou1323 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
默_笙3 小时前
🍕 一个主编、三个工种、两本手册:搭一支 AI 调研队
前端·javascript
CappuccinoRose4 小时前
FormData数据处理
开发语言·前端·javascript·表单数据
MardaWang4 小时前
当滚动逃离了框架 ——HarmonyOS Web 与原生混排滚动的冲突本质与解法
harmonyos·arkts·鸿蒙·deveco studio
飘尘5 小时前
SVG和Canvas,前端里的两支“画笔”,用的时候怎么选择?
前端·javascript·面试
tsqtsqtsq03096 小时前
DevEco Studio 介绍
harmonyos
传奇开心果编程8 小时前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack