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


术后康复是医疗过程中至关重要的环节,需要专业的指导和持续的关注。今天我们来深入分析一个基于 React Native 开发的术后康复咨询应用,该应用集成了患者管理、医生管理、问题咨询和康复指导等功能,为术后患者提供了便捷的康复咨询平台。

使用 useState 管理应用状态,包括患者数据、医生信息、问题列表和康复指导等

该应用采用了 React Native 现代开发技术栈,主要包括:

  • React Hooks:使用 useState 管理应用状态,包括患者数据、医生信息、问题列表和康复指导等
  • TypeScript:通过类型定义确保数据结构的一致性和代码的可维护性
  • 跨平台组件:使用 SafeAreaViewTouchableOpacityScrollViewModal 等实现跨平台兼容的用户界面
  • 响应式布局:利用 Dimensions API 获取屏幕尺寸,确保在不同设备上的良好显示效果
  • 基础组件:使用 ViewTextTextInput 等构建用户界面

表示医生信息,包含 ID、姓名、专业、经验、评分和可用性

应用通过 TypeScript 定义了四个核心数据类型,构建了完整的术后康复咨询数据模型:

  • Patient:表示患者信息,包含 ID、姓名、手术类型和康复阶段
  • Doctor:表示医生信息,包含 ID、姓名、专业、经验、评分和可用性
  • Question:表示患者问题,包含 ID、患者 ID、医生 ID、内容、日期和回答状态
  • Guidance:表示康复指导,包含 ID、医生 ID、患者 ID、标题、内容、视频链接和日期

这种强类型定义不仅提高了代码的可读性和可维护性,也为鸿蒙跨端适配提供了清晰的数据结构映射基础。

通过 useState 管理,并支持添加新数据

应用采用了基于 useState 的轻量级状态管理方案,为不同功能模块分别管理状态:

  • 静态数据(患者列表、医生列表)通过初始化的 useState 直接存储
  • 动态数据(问题列表、康复指导)通过 useState 管理,并支持添加新数据
  • 交互状态(选中的患者、选中的医生、新问题内容、模态框可见性)通过独立的 useState 管理

这种模式在中小型应用中非常高效,既避免了过度设计,又保证了状态管理的清晰性。


应用支持患者信息的展示和选择,通过列表展示患者的基本信息,包括姓名、手术类型和康复阶段。用户可以点击选择患者进行后续的问题咨询操作。

应用内置了医生信息列表,包含医生的姓名、专业、经验、评分和可用性状态。用户可以点击选择医生进行咨询,为患者提供专业的医疗支持。

问题咨询是应用的核心功能,主要包括:

  • 患者向医生提交问题
  • 查看问题列表和状态
  • 医生回复患者问题

当患者提交问题时,应用会记录问题内容、提交日期和状态,并将其添加到问题列表中。医生可以查看这些问题并提供相应的康复指导。

康复指导功能为患者提供专业的术后康复建议:

  • 医生可以针对患者的问题发送康复指导
  • 康复指导包含文字内容和视频链接
  • 患者可以查看详细的康复指导信息

应用使用了示例视频链接(https://example.com/recovery-video 和 https://example.com/guidance-video)来模拟康复指导视频的展示。

应用通过卡片式布局展示患者信息、医生信息、问题列表和康复指导,使用 TouchableOpacity 组件实现交互功能,点击卡片后可以查看详细信息或执行相关操作。此外,应用使用 Modal 组件展示详细信息,提升了用户体验。


组件映射

在鸿蒙 OS 适配过程中,需要注意以下组件映射:

  • SafeAreaView:在鸿蒙上需要使用 SafeArea 组件或自定义适配
  • TouchableOpacity:对应鸿蒙的 ButtonText 组件配合点击事件
  • ScrollView:对应鸿蒙的 ListContainerScroll 组件
  • Modal:对应鸿蒙的 Dialog 组件
  • Alert:对应鸿蒙的 ToastDialogAlertDialog 组件
  • TextInput:对应鸿蒙的 TextField 组件

鸿蒙 OS 对 Flexbox 布局的支持与 React Native 基本一致,但在细节上仍需注意:

  • 确保样式命名符合鸿蒙规范
  • 调整间距和尺寸以适应鸿蒙设备的显示特性
  • 注意字体大小和行高的适配
  • 对于表单输入控件,需要适配鸿蒙的输入框样式和交互方式

在鸿蒙跨端开发中,性能优化是一个重要考虑因素:

  • 使用 memo 优化组件渲染,特别是对于患者列表、医生列表等重复渲染的场景
  • 合理使用 useCallbackuseMemo 减少不必要的计算
  • 优化图片资源,考虑使用鸿蒙的资源加载机制
  • 对于列表数据的渲染,考虑使用虚拟化技术减少内存占用
  • 对于视频链接的处理,考虑使用鸿蒙的视频播放组件进行适配

应用构建了一个医患双向沟通平台:

  • 患者可以主动向医生提问,表达康复过程中的困惑
  • 医生可以针对患者的问题提供专业的康复指导
  • 支持文字和视频多种形式的沟通方式

应用实现了完整的康复管理流程:

  • 患者信息管理:记录患者的手术类型和康复阶段
  • 问题提交:患者向医生咨询康复问题
  • 康复指导:医生提供专业的康复建议和视频指导
  • 状态跟踪:记录问题的回答状态,确保患者的问题得到及时回应

应用采用了清晰的模块化设计:

  • 功能按模块划分(患者管理、医生管理、问题咨询、康复指导)
  • 组件职责单一,便于维护和扩展
  • 状态管理逻辑与 UI 渲染分离

Base64 图标库

应用使用 Base64 编码的图标,这种方式有几个优点:

  • 减少网络请求,提高加载速度
  • 避免图标资源的跨平台适配问题
  • 减小应用包体积

虽然示例中使用的是占位 Base64 编码,但实际应用中可以使用真实的图标编码。


文件结构

示例代码集中在 App.tsx 文件中,适合小型应用。对于大型应用,建议按功能模块拆分文件:

  • /components:存放可复用组件,如患者卡片、医生卡片、问题卡片等

  • /types:存放类型定义,如 Patient、Doctor、Question、Guidance 等

  • /hooks:存放自定义 hooks,如问题处理逻辑、康复指导逻辑等

  • /services:存放 API 调用和业务逻辑,如数据存储、视频播放服务等

  • /utils:存放工具函数,如日期处理、数据格式化等

  • 命名规范:变量和函数命名清晰,符合语义化要求

  • 类型安全:使用 TypeScript 确保类型安全

  • 错误处理:通过条件判断处理可能的异常情况,如表单验证

  • 注释:代码结构清晰,关键部分有适当注释

  • 性能考虑:合理使用 React Hooks,避免不必要的渲染和计算

  • 可以轻松添加新的患者和医生信息

  • 可以集成真实的后端 API,实现数据的持久化存储

  • 可以扩展支持更多的术后康复管理功能,如康复进度跟踪、用药提醒等

  • 可以集成视频播放功能,实现康复指导视频的直接播放

  • 可以添加数据可视化功能,如康复进度图表等


这个术后康复咨询应用展示了如何使用 React Native 构建功能完备的医疗康复管理工具,特别是在医患双向沟通和康复指导方面的实践具有重要参考价值。通过跨端开发技术,可以在不同平台为患者和医生提供一致的服务体验。

随着人们对术后康复重视程度的提高,这类应用的需求将不断增长。未来可以考虑:

  • 集成更多术后康复管理功能,如康复训练计划、伤口护理指导等
  • 与医院的电子病历系统对接,实现患者信息的自动同步
  • 利用 AI 技术根据患者的手术类型和康复阶段提供个性化的康复建议
  • 支持远程视频会诊,实现医生与患者的实时沟通
  • 开发配套的家属端应用,让家人也能参与到患者的康复过程中来

React Native 鸿蒙跨端开发代表了移动应用开发的未来趋势,通过一套代码库覆盖多个平台,不仅可以降低开发成本,还可以确保用户体验的一致性。在医疗康复领域,这种开发模式尤为重要,因为它可以让开发者更专注于核心功能的实现,而不是平台差异的处理。


术后康复咨询是智慧医疗在外科康复场景的核心落地形态,聚焦“患者信息管理-医生对接-术后问题提交-康复指导下发”全流程的康复服务逻辑,既要保证手术类型、恢复阶段等核心医疗数据的准确性,又需兼顾术后患者的交互易用性与康复指导下发的实时性,同时实现多端服务体验的一致性。本文基于这套 React Native 术后康复咨询应用代码,从架构设计、核心业务逻辑、鸿蒙跨端适配三个维度,系统解读术后康复咨询场景的跨端开发逻辑与技术要点,重点剖析 React Native 与鸿蒙系统的适配底层逻辑和落地实践方案,尤其针对问题提交、康复指导下发、医患状态关联等核心交互的跨端实现进行深度拆解。

一、医患选择(handleSelectPatient/handleSelectDoctor)逻辑

该术后康复咨询应用基于 React Native 函数式组件 + TypeScript 强类型架构构建,核心依赖 React Native 原生基础组件(SafeAreaView、ScrollView、TouchableOpacity、TextInput、Modal 等)与 useState 核心 Hook,未引入第三方 UI 框架或复杂状态管理库。这种极简架构是术后康复咨询这类“强医患关联、轻实时计算”场景实现鸿蒙跨端的核心优势——轻量意味着适配成本更低,且能最大程度保证多端术后康复服务流程逻辑的一致性,尤其适合患者信息管理、问题提交、康复指导下发等核心逻辑的跨端复用。

从跨端技术底层逻辑来看,React Native 以“JS 桥接层(JS Bridge)”为核心实现跨端能力:前端编写的 JSX 组件与术后康复咨询业务逻辑,通过桥接层映射为不同平台的原生组件,iOS 端映射为 UIKit 体系、Android 端映射为 View 体系,而鸿蒙(HarmonyOS)端则通过 React Native for HarmonyOS 适配层,完成 React Native 组件/API 与鸿蒙 ArkUI 组件/API 的双向映射。该应用的代码结构完全遵循跨端开发规范:无平台专属硬编码、状态管理基于 React 原生 Hooks、样式采用跨端通用的 Flex 布局,从根源上消除了鸿蒙适配的技术壁垒,同时保证问题提交、康复指导下发、医患状态关联等核心术后康复管理流程逻辑在多端的一致性。

值得注意的是,应用核心的问题提交(handleAskQuestion)、康复指导下发(handleSendGuidance)、医患选择(handleSelectPatient/handleSelectDoctor)逻辑均为纯 JS 状态操作与数组关联查询实现,无任何平台相关依赖,这是跨端复用的关键——鸿蒙端可通过 JS 引擎直接执行该逻辑,无需适配任何原生能力,保证术后康复管理规则在多端的完全一致,避免因平台差异导致的问题提交失败、康复指导下发错误等核心问题。


1. 患者ID、姓名、手术类型、恢复阶段,手术类型

应用通过 TypeScript 接口定义了 Patient(患者)、Doctor(医生)、Question(问题)、Guidance(康复指导)四类核心数据类型,字段设计精准匹配术后康复咨询全流程数据需求,且所有字段均为 JS 基础数据类型(string/number/boolean),为跨端适配奠定基础:

  • Patient 涵盖患者ID、姓名、手术类型、恢复阶段,手术类型(surgeryType)与恢复阶段(recoveryStage)为标准化字符串类型,在鸿蒙端适配层可直接映射为 ArkTS 的 string 类型,避免多端数据类型解析差异导致的患者信息展示错误,尤其在“李先生 膝关节置换 初期恢复”这类核心患者信息的传递上,保证了跨端的数据准确性;
  • Doctor 新增可用性字段(available: boolean)与评分字段(rating: number),布尔值与数值类型均为 JS/ArkTS 通用基础类型,医生在线/离线状态的展示逻辑跨端统一,评分的数值展示规则无平台差异;
  • Question 作为核心交互数据模型,关联患者ID/医生ID(patientId/doctorId)、问题内容、日期、回答状态(answered: boolean),关联ID为标准化字符串,回答状态为布尔值,鸿蒙端适配层可直接解析,且问题状态更新逻辑(answered 字段修改)为纯 JS 布尔赋值,保证问题状态的跨端一致性;
  • Guidance 关联患者ID/医生ID,新增标题、内容、视频链接(videoUrl)字段,所有字段均为标准化字符串,视频链接的展示与跳转逻辑为纯 JS 字符串处理,鸿蒙端适配层可直接解析,保证康复指导内容的跨端一致性。

这种强类型+场景化的数据模型设计,在跨端场景下保证了数据结构的一致性——鸿蒙端适配层可直接解析 TypeScript 类型定义,与 ArkTS 中的数据模型形成精准映射,避免多端数据格式不一致导致的患者信息展示异常、康复指导下发错误等核心问题,是术后康复咨询场景跨端落地的基础保障。

2. Hooks 状态管理

应用采用 useState 实现多维度状态管理,核心状态均具备跨端复用的特性,且针对术后康复咨询场景做了适配性设计:

  • 核心基础数据状态(patients/doctors)为只读设计,适配层自动映射为鸿蒙的 @State 响应式状态,患者列表、医生列表的展示逻辑跨端统一,保证基础医疗服务信息的一致性;
  • 动态业务数据状态(questions/guidances)支持新增/更新操作(setQuestions/setGuidances),数组扩展运算符([...questions, newQuestionEntry])、数组映射更新(questions.map(q => q.id === questionId ? { ...q, answered: true } : q))均为 ES6+ 标准语法,鸿蒙端直接执行,数据新增/状态更新的规则跨端一致,且关联查询逻辑绑定在状态更新之后,保证多端数据关联的准确性;
  • 交互状态(selectedPatient/selectedDoctor/newQuestion)维护选中医患与新增问题信息,newQuestion 的状态更新为基础字符串赋值,selectedPatient/selectedDoctor 为字符串/空值赋值,均为纯 JS 操作,鸿蒙端直接执行,医患选择与问题提交的交互逻辑跨端统一;
  • 弹窗状态(isModalVisible/modalContent)维护弹窗显隐与内容,其更新逻辑为基础状态操作,鸿蒙端适配层会将 Modal 的显示状态映射为 ArkUI 弹窗的显隐状态,弹窗展示问题/康复指导详情的逻辑跨端统一。

1. 术后康复咨询专属样式的跨端适配

应用在基础样式之上新增患者卡片、医生卡片、问题卡片、康复指导卡片专属样式,核心样式设计既遵循跨端兼容原则,又针对术后患者的操作习惯做了特殊优化,适配鸿蒙系统无明显改造成本:

  • Flex 布局的跨端统一:从医患卡片的“图标+信息+状态”、问题卡片的“图标+信息+发送指导按钮”,到康复指导卡片的“图标+信息”、弹窗的“垂直居中+内容区”布局,全量采用 Flex 布局体系——问题卡片使用 flexDirection: 'row' + alignItems: 'center' 横向布局,“发送指导”按钮独立排布,保证医生下发康复指导的操作便捷性;医患卡片使用 flexDirection: 'row' + alignItems: 'center' 横向布局,医生可用性状态标识(🟢/🔴)独立排布,保证医生在线状态的视觉辨识度。Flex 作为 W3C 标准布局方案,在鸿蒙端可被适配层直接解析为 ArkUI 的 Flex 布局,无需重构任何布局逻辑,仅需保证样式属性命名与 React Native 规范一致,尤其在问题提交、康复指导下发等核心术后康复咨询交互区域的布局上,Flex 布局的跨端一致性表现突出;
  • 术后康复咨询专属样式的跨端适配
    • 选中医患卡片样式(selectedCardborderWidth: 2 + borderColor: '#0284c7')为通用样式属性,鸿蒙端适配层会将边框属性转换为 ArkUI 的 border 相关属性,选中医患的高亮边框视觉效果跨端统一,帮助用户快速识别选择状态;
    • 问题输入框样式(questionInput)采用浅蓝背景(#f0f9ff)、圆角(12)、内边距(12)、最小高度(80)设计,且 multilinetextAlignVertical: 'top' 属性在鸿蒙端已完成适配,输入框的多行输入、文本居顶展示的视觉与交互效果跨端统一,符合术后患者输入长文本问题的操作习惯;
    • 各类术后康复咨询卡片样式(card/questionCard/guidanceCard)采用统一的浅蓝背景(#f0f9ff)、圆角(12)、内边距(16)设计,为通用样式属性,鸿蒙端适配层会将这些属性转换为 ArkUI 的对应样式,卡片的视觉质感跨端统一,符合术后康复咨询场景清晰、易识别的视觉需求;
    • “发送指导”按钮样式(sendButton)采用绿色背景(#10b981)、小尺寸内边距设计,为通用样式属性,鸿蒙端适配层直接解析,按钮的视觉辨识度与点击区域跨端统一,方便医生快速下发康复指导;
  • 屏幕适配与层级兼容Dimensions.get('window') 获取设备宽高的 API 在鸿蒙端已完成原生映射,为不同尺寸鸿蒙设备(手机、平板)的自适应布局预留基础,尤其适配平板设备的术后康复数据大屏展示场景;shadow + elevation 的双层阴影设计,鸿蒙系统对 elevation 属性的支持与 Android 端完全兼容,保证各类卡片、信息卡片的视觉层级跨端统一,同时阴影透明度(0.1)的低饱和度设计,避免视觉干扰,符合术后患者查看康复信息的视觉习惯;
  • 安全区域适配SafeAreaView 组件在鸿蒙端已适配为 ArkUI 的 SafeArea 组件,保证头部术后康复咨询标题区域在不同鸿蒙设备上的展示完整性,避免标题被刘海屏、全面屏遮挡,尤其适配术后患者的视觉聚焦习惯。

(1)医患信息管理组件

医患信息管理是术后康复咨询的基础功能,核心适配逻辑如下:

  • 患者/医生列表渲染采用 map 方法遍历 patients/doctors 数组,该逻辑为纯 JS 数组操作,鸿蒙端通过 JS 引擎直接执行,列表渲染的顺序与医患信息展示的完整性跨端一致,保证医患信息的准确展示;
  • 医患选择逻辑(handleSelectPatient/handleSelectDoctor)包含状态更新(setSelectedPatient/setSelectedDoctor)与弹窗反馈(Alert.alert),状态更新为基础字符串赋值,弹窗反馈转换为鸿蒙 AlertDialog 组件,选择操作的反馈逻辑跨端一致;
  • 医生可用性状态展示({doctor.available ? '🟢 在线' : '🔴 离线'})为纯 JS 三元表达式,鸿蒙端直接执行,状态展示的逻辑跨端统一,帮助用户快速识别医生在线状态。
(2)问题提交

问题提交与康复指导下发是术后康复咨询的核心交互功能,核心适配逻辑如下:

  • 问题提交采用 TextInput 组件实现多行输入交互,multiline/textAlignVertical/placeholder 属性在鸿蒙端已适配,输入框的视觉与交互效果跨端统一;
  • 问题提交逻辑(handleAskQuestion)包含完整的表单校验与数据新增:
    1. 表单校验(newQuestion.trim() && selectedPatient && selectedDoctor)为纯 JS 布尔运算,鸿蒙端直接执行,校验规则跨端一致,保证问题提交的完整性;
    2. 数据新增(setQuestions([...questions, newQuestionEntry]))使用数组扩展运算符,鸿蒙端直接执行,数据新增规则跨端一致;
    3. 状态重置(setNewQuestion(''))为纯 JS 字符串赋值,鸿蒙端直接执行,输入框重置逻辑跨端一致;
  • 康复指导下发逻辑(handleSendGuidance)包含问题查找、康复指导新增、问题状态更新:
    1. 问题查找(questions.find(q => q.id === questionId))为 JS 原生数组方法,鸿蒙端直接执行,查找规则跨端一致;
    2. 康复指导新增(setGuidances([...guidances, newGuidance]))使用数组扩展运算符,鸿蒙端直接执行,数据新增规则跨端一致;
    3. 问题状态更新(questions.map(q => q.id === questionId ? { ...q, answered: true } : q))为 JS 原生数组方法,鸿蒙端直接执行,状态更新规则跨端一致,保证问题“已回答”状态的跨端同步;
  • “提交问题”/“发送指导”按钮的点击回调为纯 JS 函数调用,鸿蒙端适配层将 TouchableOpacityonPress 事件映射为 ArkUI 的点击事件,按钮点击的交互逻辑跨端统一。
(3)问题/康复指导

问题/康复指导详情展示是术后康复咨询的重要交互环节,核心适配逻辑如下:

  • 详情查找逻辑(handleViewQuestion/handleViewGuidance)包含问题/康复指导查找、关联医患信息查找,均为 JS 原生 find 方法,鸿蒙端直接执行,查找规则跨端一致;
  • 弹窗内容拼接(模板字符串)为纯 JS 字符串操作,鸿蒙端直接执行,内容展示的格式跨端统一;
  • Modal 组件的 animationType="slide"/transparent={true}/visible={isModalVisible} 属性在鸿蒙端已完成适配,弹窗的滑动动画、透明背景、显隐状态的展示逻辑跨端统一,保证详情查看的交互体验一致。

1. 问题提交

handleAskQuestion 是术后康复咨询的核心提交机制,实现了“表单校验-数据构造-状态更新-反馈提示”的全流程问题处理,核心适配逻辑如下:

  • 表单校验逻辑(newQuestion.trim() && selectedPatient && selectedDoctor)为纯 JS 布尔运算,无任何平台依赖,鸿蒙端直接执行,校验规则跨端一致,保证问题提交的完整性;
  • 数据构造逻辑(newQuestionEntry)采用当前日期格式化(new Date().toISOString().split('T')[0]),为纯 JS 日期处理操作,鸿蒙端直接执行,日期格式的生成规则跨端一致;
  • 状态更新逻辑(setQuestions/setNewQuestion)为基础数组/字符串赋值,鸿蒙端直接执行,状态更新规则跨端一致;
  • 反馈提示逻辑(Alert.alert)转换为鸿蒙 AlertDialog 组件,提示文案的动态生成(模板字符串)为纯 JS 操作,鸿蒙端直接执行,提示展示的逻辑跨端一致。

2. 康复指导

handleSendGuidance 是术后康复咨询的核心服务机制,实现了“问题查找-康复指导构造-状态双更新-反馈提示”的全流程指导下发处理,核心适配逻辑如下:

  • 问题查找逻辑(questions.find(q => q.id === questionId))为 JS 原生数组方法,鸿蒙端直接执行,查找规则跨端一致;
  • 康复指导构造逻辑(newGuidance)采用当前日期格式化,为纯 JS 日期处理操作,鸿蒙端直接执行,日期格式的生成规则跨端一致;
  • 状态双更新逻辑(setGuidances 新增康复指导、setQuestions 更新问题状态)为基础数组操作,数组扩展与映射更新均为 ES6+ 标准语法,鸿蒙端直接执行,状态更新规则跨端一致;
  • 反馈提示逻辑(Alert.alert)转换为鸿蒙 AlertDialog 组件,提示文案为纯 JS 字符串,鸿蒙端直接执行,提示展示的逻辑跨端一致。

应用使用的核心 API 均为 React Native 跨端兼容 API,在鸿蒙端可无缝适配,且针对术后康复咨询场景做了适配性验证:

  • 数组 API:map/find/扩展运算符等数组方法为 JS 原生 API,鸿蒙端通过 JS 引擎直接执行,无需适配,保证患者/医生/问题/康复指导列表的渲染、查找、新增、状态更新等核心逻辑的跨端一致性;
  • 字符串 API:字符串分割(split)、拼接、模板字符串、trim 等操作为 JS 原生 API,鸿蒙端直接执行,保证日期格式处理、弹窗内容拼接、问题内容校验等核心逻辑的跨端一致性;
  • 日期 API:new Date().toISOString() 等日期处理 API 为 JS 原生 API,鸿蒙端直接执行,保证问题/康复指导日期生成的跨端一致性;
  • 交互 API:TouchableOpacityonPress 回调、TextInputonChangeText/multiline/textAlignVertical 属性、Alert.alert 弹窗、Modal 组件的核心属性,在鸿蒙端均已完成适配,点击交互、输入交互、弹窗反馈、模态框展示的逻辑跨端一致,符合术后患者/医生的操作习惯;
  • 样式 API:StyleSheet.create 封装的样式规则,适配层转换为 ArkUI 的样式对象,尤其选中医患卡片的条件样式绑定逻辑,鸿蒙端可直接解析,保证医患选择状态的视觉适配跨端统一。

该术后康复咨询应用作为智慧医疗外科康复场景核心模块,适配鸿蒙系统的成本极低,核心适配思路与技术要点如下:

应用核心的问题提交、康复指导下发、医患选择、详情展示等逻辑均为纯 JS 实现,无任何平台相关依赖,这是跨端复用的最大优势——鸿蒙端可通过 JS 引擎直接执行该逻辑,无需适配任何原生能力。在生产环境中扩展视频播放、康复进度跟踪、医生排班等逻辑时,新增规则仍为纯 JS 逻辑(视频播放可通过原生模块封装鸿蒙媒体 API,核心业务逻辑复用),鸿蒙端可直接复用,仅需保证规则逻辑的通用性,无需考虑平台差异,这也是术后康复咨询场景跨端开发的核心优势。

该应用当前的列表渲染采用基础 map 方法,在生产环境中若问题/康复指导/医患数据量较大(如超过100条问题、50条康复指导、20位医生),可替换为 React Native 的 FlatList 高性能列表组件——FlatList 在鸿蒙端已完成深度适配,支持虚拟化列表渲染,其核心属性(data/renderItem/keyExtractor)与 React Native 端完全一致,且 FlatListrenderItem 中可直接复用现有卡片样式与选中医患条件样式绑定逻辑,仅需少量调整即可适配鸿蒙端的性能优化策略,保证列表的滚动性能,尤其适合长期术后康复管理产生的海量数据展示场景。

鸿蒙系统有自身的术后患者友好型设计规范,在适配时可通过条件编译实现差异化样式,既保证遵循鸿蒙设计规范,又能保留现有代码的完整性与患者友好特性:

// 鸿蒙端术后康复咨询样式差异化适配示例
import { Platform } from 'react-native';
const isHarmonyOS = Platform.OS === 'harmony';

const adaptiveStyles = {
  card: {
    ...styles.card,
    backgroundColor: isHarmonyOS ? '#e0f7fa' : '#f0f9ff',
    borderRadius: isHarmonyOS ? 14 : 12,
    padding: isHarmonyOS ? 20 : 16, // 鸿蒙端增大内边距,更适合术后患者查看
  },
  selectedCard: {
    ...styles.selectedCard,
    borderWidth: isHarmonyOS ? 3 : 2, // 鸿蒙端增大选中边框宽度
    borderColor: isHarmonyOS ? '#0369a1' : '#0284c7',
  },
  questionInput: {
    ...styles.questionInput,
    fontSize: isHarmonyOS ? 16 : 14, // 鸿蒙端增大输入框字体
    padding: isHarmonyOS ? 16 : 12,
    minHeight: isHarmonyOS ? 90 : 80, // 鸿蒙端增大输入框高度
  },
  sendButton: {
    ...styles.sendButton,
    paddingHorizontal: isHarmonyOS ? 16 : 12, // 鸿蒙端增大按钮点击区域
    paddingVertical: isHarmonyOS ? 8 : 6,
  }
};

这种轻量级的差异化适配,既能保证符合鸿蒙的术后患者友好设计规范,又能保留现有代码的完整性,尤其在医患信息卡片、问题输入框、发送指导按钮等核心术后康复咨询交互组件的样式适配中,效果显著,同时维持了术后康复咨询场景清晰、易识别的视觉调性。

该 React Native 术后康复咨询应用实现了患者信息管理、医生对接、问题提交、康复指导下发等核心智慧医疗服务功能,代码结构符合跨端开发规范,可低成本适配鸿蒙系统。针对生产环境落地,可做以下优化:

  1. 康复视频播放对接:通过 React Native 原生模块封装鸿蒙的媒体播放 API,实现康复指导视频的在线播放,核心康复指导列表渲染逻辑可完全复用现有代码,仅需对接鸿蒙的媒体原生 API,提升术后康复指导的直观性;
  2. 康复进度跟踪:新增康复进度数据模型与状态管理,基于患者恢复阶段自动生成康复计划,该逻辑为纯 JS 条件判断,鸿蒙端直接执行,无需适配原生能力,提升术后康复管理的精细化程度;
  3. 医生排班与预约:新增医生排班数据模型与预约逻辑,该逻辑为纯 JS 数组操作与日期比较,鸿蒙端直接执行,实现术后患者与医生的精准预约,提升服务效率;
  4. 术后康复数据云端同步:对接智慧医疗云平台接口,实现患者信息、问题、康复指导的云端存储与多端同步,数据同步逻辑为纯 JS 数组操作,鸿蒙端直接执行,保证术后康复数据的跨设备一致性;
  5. 问题分类与智能回复:新增问题分类标签与智能回复规则,该逻辑为纯 JS 字符串匹配与条件判断,鸿蒙端直接执行,提升问题处理效率,减轻医生工作负担。

真实演示案例代码:






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

// Base64 图标库
const ICONS_BASE64 = {
  question: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  guidance: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  doctor: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  patient: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// 患者类型
type Patient = {
  id: string;
  name: string;
  surgeryType: string;
  recoveryStage: string;
};

// 医生类型
type Doctor = {
  id: string;
  name: string;
  specialty: string;
  experience: string;
  rating: number;
  available: boolean;
};

// 问题类型
type Question = {
  id: string;
  patientId: string;
  doctorId: string;
  content: string;
  date: string;
  answered: boolean;
};

// 康复指导类型
type Guidance = {
  id: string;
  doctorId: string;
  patientId: string;
  title: string;
  content: string;
  videoUrl: string;
  date: string;
};

// 术后康复咨询应用组件
const PostSurgeryRecoveryApp: React.FC = () => {
  const [patients] = useState<Patient[]>([
    {
      id: '1',
      name: '李先生',
      surgeryType: '膝关节置换',
      recoveryStage: '初期恢复'
    },
    {
      id: '2',
      name: '王女士',
      surgeryType: '心脏搭桥',
      recoveryStage: '中期恢复'
    }
  ]);

  const [doctors] = useState<Doctor[]>([
    {
      id: '1',
      name: '张医生',
      specialty: '骨科',
      experience: '10年经验',
      rating: 4.8,
      available: true
    },
    {
      id: '2',
      name: '李医生',
      specialty: '心胸外科',
      experience: '12年经验',
      rating: 4.9,
      available: false
    }
  ]);

  const [questions, setQuestions] = useState<Question[]>([
    {
      id: '1',
      patientId: '1',
      doctorId: '1',
      content: '术后膝盖疼痛应该如何缓解?',
      date: '2023-12-01',
      answered: false
    }
  ]);

  const [guidances, setGuidances] = useState<Guidance[]>([
    {
      id: '1',
      doctorId: '1',
      patientId: '1',
      title: '膝关节置换术后康复指导',
      content: '请按照以下步骤进行康复训练...',
      videoUrl: 'https://example.com/recovery-video',
      date: '2023-12-01'
    }
  ]);

  const [selectedPatient, setSelectedPatient] = useState<string | null>(null);
  const [selectedDoctor, setSelectedDoctor] = useState<string | null>(null);
  const [newQuestion, setNewQuestion] = useState('');
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [modalContent, setModalContent] = useState('');

  const handleSelectPatient = (patientId: string) => {
    setSelectedPatient(patientId);
    Alert.alert('选择患者', '您已选择该患者进行咨询');
  };

  const handleSelectDoctor = (doctorId: string) => {
    setSelectedDoctor(doctorId);
    Alert.alert('选择医生', '您已选择该医生进行咨询');
  };

  const handleAskQuestion = () => {
    if (newQuestion.trim() && selectedPatient && selectedDoctor) {
      const newQuestionEntry: Question = {
        id: (questions.length + 1).toString(),
        patientId: selectedPatient,
        doctorId: selectedDoctor,
        content: newQuestion,
        date: new Date().toISOString().split('T')[0],
        answered: false
      };
      setQuestions([...questions, newQuestionEntry]);
      setNewQuestion('');
      Alert.alert('问题提交', '您的问题已提交给医生');
    } else {
      Alert.alert('提示', '请填写问题内容并选择患者和医生');
    }
  };

  const handleSendGuidance = (questionId: string) => {
    const question = questions.find(q => q.id === questionId);
    if (question) {
      const newGuidance: Guidance = {
        id: (guidances.length + 1).toString(),
        doctorId: question.doctorId,
        patientId: question.patientId,
        title: '康复指导',
        content: '请按照以下建议进行康复...',
        videoUrl: 'https://example.com/guidance-video',
        date: new Date().toISOString().split('T')[0]
      };
      setGuidances([...guidances, newGuidance]);
      setQuestions(questions.map(q => q.id === questionId ? { ...q, answered: true } : q));
      Alert.alert('指导发送', '康复指导已发送给患者');
    }
  };

  const handleViewQuestion = (questionId: string) => {
    const question = questions.find(q => q.id === questionId);
    if (question) {
      const patient = patients.find(p => p.id === question.patientId);
      const doctor = doctors.find(d => d.id === question.doctorId);
      setModalContent(`患者: ${patient?.name}\n医生: ${doctor?.name}\n问题: ${question.content}\n日期: ${question.date}\n状态: ${question.answered ? '已回答' : '未回答'}`);
      setIsModalVisible(true);
    }
  };

  const handleViewGuidance = (guidanceId: string) => {
    const guidance = guidances.find(g => g.id === guidanceId);
    if (guidance) {
      const patient = patients.find(p => p.id === guidance.patientId);
      const doctor = doctors.find(d => d.id === guidance.doctorId);
      setModalContent(`患者: ${patient?.name}\n医生: ${doctor?.name}\n标题: ${guidance.title}\n内容: ${guidance.content}\n视频链接: ${guidance.videoUrl}\n日期: ${guidance.date}`);
      setIsModalVisible(true);
    }
  };

  const openModal = (content: string) => {
    setModalContent(content);
    setIsModalVisible(true);
  };

  const closeModal = () => {
    setIsModalVisible(false);
  };

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>术后康复咨询</Text>
        <Text style={styles.subtitle}>术后患者可通过App向医生提问,医生可发送康复指导视频</Text>
      </View>

      <ScrollView style={styles.content}>
        {/* 患者列表 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>患者列表</Text>
          {patients.map(patient => (
            <TouchableOpacity 
              key={patient.id}
              style={[
                styles.card,
                selectedPatient === patient.id && styles.selectedCard
              ]}
              onPress={() => handleSelectPatient(patient.id)}
            >
              <Text style={styles.icon}>👤</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>{patient.name}</Text>
                <Text style={styles.cardDescription}>手术类型: {patient.surgeryType}</Text>
                <Text style={styles.cardDescription}>恢复阶段: {patient.recoveryStage}</Text>
              </View>
            </TouchableOpacity>
          ))}
        </View>

        {/* 医生列表 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>医生列表</Text>
          {doctors.map(doctor => (
            <TouchableOpacity 
              key={doctor.id}
              style={[
                styles.card,
                selectedDoctor === doctor.id && styles.selectedCard
              ]}
              onPress={() => handleSelectDoctor(doctor.id)}
            >
              <Text style={styles.icon}>👨‍⚕️</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>{doctor.name}</Text>
                <Text style={styles.cardDescription}>专科: {doctor.specialty}</Text>
                <Text style={styles.cardDescription}>经验: {doctor.experience}</Text>
                <Text style={styles.cardDescription}>评分: {doctor.rating}</Text>
              </View>
              <Text style={styles.availability}>
                {doctor.available ? '🟢 在线' : '🔴 离线'}
              </Text>
            </TouchableOpacity>
          ))}
        </View>

        {/* 提问区域 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>提问</Text>
          <TextInput
            style={styles.questionInput}
            placeholder="请输入您的问题..."
            multiline
            value={newQuestion}
            onChangeText={setNewQuestion}
          />
          <TouchableOpacity 
            style={styles.askButton}
            onPress={handleAskQuestion}
          >
            <Text style={styles.askText}>提交问题</Text>
          </TouchableOpacity>
        </View>

        {/* 问题列表 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>问题列表</Text>
          {questions.map(question => (
            <TouchableOpacity 
              key={question.id}
              style={styles.questionCard}
              onPress={() => handleViewQuestion(question.id)}
            >
              <Text style={styles.icon}></Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>问题ID: {question.id}</Text>
                <Text style={styles.cardDescription}>内容: {question.content}</Text>
                <Text style={styles.cardDescription}>日期: {question.date}</Text>
                <Text style={styles.cardDescription}>状态: {question.answered ? '已回答' : '未回答'}</Text>
              </View>
              {!question.answered && selectedDoctor && (
                <TouchableOpacity 
                  style={styles.sendButton}
                  onPress={() => handleSendGuidance(question.id)}
                >
                  <Text style={styles.sendText}>发送指导</Text>
                </TouchableOpacity>
              )}
            </TouchableOpacity>
          ))}
        </View>

        {/* 康复指导列表 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>康复指导</Text>
          {guidances.map(guidance => (
            <TouchableOpacity 
              key={guidance.id}
              style={styles.guidanceCard}
              onPress={() => handleViewGuidance(guidance.id)}
            >
              <Text style={styles.icon}>🎥</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>指导ID: {guidance.id}</Text>
                <Text style={styles.cardDescription}>标题: {guidance.title}</Text>
                <Text style={styles.cardDescription}>内容: {guidance.content}</Text>
                <Text style={styles.cardDescription}>日期: {guidance.date}</Text>
              </View>
            </TouchableOpacity>
          ))}
        </View>

        {/* 使用说明 */}
        <View style={styles.infoCard}>
          <Text style={styles.sectionTitle}>📘 使用说明</Text>
          <Text style={styles.infoText}>• 选择患者和医生进行咨询</Text>
          <Text style={styles.infoText}>• 提交术后相关问题</Text>
          <Text style={styles.infoText}>• 医生可发送康复指导视频</Text>
          <Text style={styles.infoText}>• 患者可查看康复指导内容</Text>
        </View>

        {/* 弹框内容 */}
        <Modal
          animationType="slide"
          transparent={true}
          visible={isModalVisible}
          onRequestClose={closeModal}
        >
          <View style={styles.modalContainer}>
            <View style={styles.modalContent}>
              <Text style={styles.modalTitle}>详细信息</Text>
              <Text style={styles.modalText}>{modalContent}</Text>
              <TouchableOpacity
                style={styles.closeButton}
                onPress={closeModal}
              >
                <Text style={styles.closeButtonText}>关闭</Text>
              </TouchableOpacity>
            </View>
          </View>
        </Modal>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f0f9ff',
  },
  header: {
    flexDirection: 'column',
    padding: 16,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#bae6fd',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#0c4a6e',
    marginBottom: 4,
  },
  subtitle: {
    fontSize: 14,
    color: '#0284c7',
  },
  content: {
    flex: 1,
    marginTop: 12,
  },
  section: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#0c4a6e',
    marginBottom: 12,
  },
  card: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f0f9ff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
  },
  selectedCard: {
    borderWidth: 2,
    borderColor: '#0284c7',
  },
  questionCard: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f0f9ff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
  },
  guidanceCard: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f0f9ff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
  },
  icon: {
    fontSize: 28,
    marginRight: 12,
  },
  cardInfo: {
    flex: 1,
  },
  cardTitle: {
    fontSize: 16,
    fontWeight: '500',
    color: '#0c4a6e',
    marginBottom: 4,
  },
  cardDescription: {
    fontSize: 14,
    color: '#0284c7',
    marginBottom: 2,
  },
  availability: {
    fontSize: 14,
    color: '#0c4a6e',
    fontWeight: '500',
  },
  questionInput: {
    backgroundColor: '#f0f9ff',
    borderRadius: 12,
    padding: 12,
    fontSize: 14,
    color: '#0c4a6e',
    minHeight: 80,
    textAlignVertical: 'top',
    marginBottom: 12,
  },
  askButton: {
    backgroundColor: '#0284c7',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  askText: {
    color: '#ffffff',
    fontSize: 14,
    fontWeight: '500',
  },
  sendButton: {
    backgroundColor: '#10b981',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 8,
  },
  sendText: {
    color: '#ffffff',
    fontSize: 12,
    fontWeight: '500',
  },
  infoCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 80,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  infoText: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 20,
    marginBottom: 4,
  },
  modalContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
  },
  modalContent: {
    width: '80%',
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 20,
    elevation: 5,
  },
  modalTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#0c4a6e',
    marginBottom: 12,
    textAlign: 'center',
  },
  modalText: {
    fontSize: 14,
    color: '#0c4a6e',
    lineHeight: 20,
    marginBottom: 20,
  },
  closeButton: {
    backgroundColor: '#0284c7',
    padding: 10,
    borderRadius: 8,
    alignItems: 'center',
  },
  closeButtonText: {
    color: '#ffffff',
    fontSize: 14,
    fontWeight: '500',
  },
});

export default PostSurgeryRecoveryApp;


请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文介绍了一个基于React Native开发的术后康复咨询应用,采用TypeScript和React Hooks构建,实现了患者管理、医生咨询、问题提交和康复指导等功能。文章重点分析了该应用的跨平台开发技术,包括组件映射、状态管理和性能优化策略,并探讨了如何适配鸿蒙系统。应用采用模块化设计,通过清晰的类型定义和轻量级状态管理,为术后患者提供了便捷的康复咨询平台。文章还展望了未来发展方向,如AI个性化建议和远程视频会诊等,展示了React Native在医疗康复领域的跨平台开发潜力。

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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐