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


在医疗健康领域,药物冲突检测系统正成为保障用药安全的重要工具。今天我们将深入分析一个基于 React Native 开发的药物冲突检测应用,探讨其技术实现细节以及在鸿蒙系统上的跨端适配策略。

Hooks 进行状态管理

该应用采用了 React Native 作为核心框架,结合 TypeScript 提供类型安全保障。从代码结构来看,项目遵循了 React 函数式组件的设计范式,充分利用了 Hooks 进行状态管理。

const DrugConflictCheckerApp: React.FC = () => {
  const [medicines, setMedicines] = useState<Medicine[]>([...]);
  const [drugConflicts, setDrugConflicts] = useState<DrugConflict[]>([...]);
  const [newMedicine, setNewMedicine] = useState({
    name: '',
    dosage: '',
    time: '',
    frequency: ''
  });
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [modalContent, setModalContent] = useState('');
  // ...
};

这种函数式组件的写法不仅代码更简洁,而且在性能上也有优势。通过多个 useState 钩子,开发者可以独立管理不同的状态,如药物列表、药物冲突列表、新药物信息以及模态框状态等。这种状态管理方式对于药物冲突检测应用来说非常合适,因为各个状态之间存在明确的业务逻辑关系,通过局部状态管理即可满足需求。

药物列表、药物冲突列表、新药物信息以及模态框状态

项目使用 TypeScript 定义了清晰的数据结构,例如 MedicineDrugConflict 类型:

type Medicine = {
  id: string;
  name: string;
  dosage: string;
  time: string;
  frequency: string;
};

type DrugConflict = {
  id: string;
  medicine1: string;
  medicine2: string;
  conflictInfo: string;
};

这种类型定义为代码提供了良好的可维护性和类型检查,避免了运行时错误。在跨端开发中,类型系统的重要性更加凸显,因为不同平台的类型处理可能存在差异,TypeScript 可以帮助开发者在编译时就发现潜在问题。

布局

应用采用了 Flexbox 布局系统,结合 Dimensions API 实现响应式设计:

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

这种方式确保了应用在不同屏幕尺寸的设备上都能良好显示。特别值得注意的是,代码中使用了 ScrollView 组件来处理内容滚动,对于药物列表和药物冲突列表等可能超出屏幕高度的内容,这种处理方式非常合适。同时,应用使用了 inputRow 样式来实现输入框的并排布局,提升了空间利用率。

从代码中可以看出,开发者采用了一套通用的组件和 API,这些都是 React Native 提供的跨平台解决方案:

  • 使用 SafeAreaView 确保内容不会被设备刘海或底部安全区域遮挡
  • 使用 TouchableOpacity 实现跨平台的点击效果
  • 使用 Alert 实现跨平台的弹窗提示
  • 使用 Modal 实现跨平台的模态框
  • 使用 TextInput 实现跨平台的文本输入

这些组件在 iOS、Android 和鸿蒙系统上都能正常工作,体现了 React Native 的 “Write Once, Run Anywhere” 理念。


药物管理

应用实现了药物的添加和删除功能:

const handleAddMedicine = () => {
  if (newMedicine.name && newMedicine.dosage && newMedicine.time && newMedicine.frequency) {
    const newMedicineEntry: Medicine = {
      id: (medicines.length + 1).toString(),
      name: newMedicine.name,
      dosage: newMedicine.dosage,
      time: newMedicine.time,
      frequency: newMedicine.frequency
    };
    setMedicines([...medicines, newMedicineEntry]);

    // 检测药物冲突
    const conflicts = checkDrugConflicts(newMedicineEntry);
    if (conflicts.length > 0) {
      setDrugConflicts([...drugConflicts, ...conflicts]);
      Alert.alert('药物冲突警告', '检测到潜在的药物冲突,请查看详细信息');
    } else {
      Alert.alert('添加成功', '药物已添加到用药计划中');
    }

    setNewMedicine({ name: '', dosage: '', time: '', frequency: '' });
  } else {
    Alert.alert('提示', '请填写完整的药物信息');
  }
};

const handleDeleteMedicine = (medicineId: string) => {
  setMedicines(medicines.filter(medicine => medicine.id !== medicineId));
  setDrugConflicts(drugConflicts.filter(conflict => conflict.medicine1 !== medicines.find(m => m.id === medicineId)?.name && conflict.medicine2 !== medicines.find(m => m.id === medicineId)?.name));
  Alert.alert('删除成功', '药物已从用药计划中移除');
};

通过这种方式,用户可以方便地管理药物列表,提升了用户体验。添加药物时,应用会进行非空验证,确保用户不会添加不完整的药物信息。添加成功后,应用会清空输入框并提供反馈,确保用户了解操作结果。删除药物时,应用会同时移除相关的药物冲突记录,保持数据的一致性。

药物冲突检测

应用实现了药物冲突的检测功能:

const checkDrugConflicts = (newMedicine: Medicine): DrugConflict[] => {
  const conflicts: DrugConflict[] = [];
  medicines.forEach(medicine => {
    if (medicine.name === '阿司匹林' && newMedicine.name === '华法林') {
      conflicts.push({
        id: (drugConflicts.length + conflicts.length + 1).toString(),
        medicine1: medicine.name,
        medicine2: newMedicine.name,
        conflictInfo: '同时使用可能增加出血风险'
      });
    }
    // 可以继续添加其他药物冲突规则
  });
  return conflicts;
};

这种设计使得应用可以自动检测药物之间的潜在冲突,提升了用户体验。药物冲突检测功能在添加新药物时自动触发,确保用户及时了解潜在的用药风险。

药物冲突查看

应用实现了药物冲突的查看功能:

const handleViewConflict = (conflictId: string) => {
  const conflict = drugConflicts.find(c => c.id === conflictId);
  if (conflict) {
    setModalContent(`药物1: ${conflict.medicine1}\n药物2: ${conflict.medicine2}\n冲突信息: ${conflict.conflictInfo}`);
    setIsModalVisible(true);
  }
};

通过这种方式,用户可以点击药物冲突记录查看详细信息,提升了用户体验。详细信息通过模态框展示,避免了页面跳转,保持了操作的连续性。

模态框

应用使用 Modal 组件实现了模态框功能:

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

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

这种方式可以在不离开当前页面的情况下展示额外信息,提升了用户体验。

应用通过样式和图标为用户提供了清晰的视觉反馈:

  • 使用不同的图标来区分药物和药物冲突
  • 使用 Alert 弹窗来确认用户的操作
  • 使用模态框来展示详细信息

这种设计使得用户可以直观地了解应用的状态和操作结果,提升了用户体验。


在鸿蒙系统上,React Native 应用需要考虑组件的兼容性。从代码来看,开发者使用的都是 React Native 核心组件,这些组件在鸿蒙系统上都有对应的实现。例如:

  • View 对应鸿蒙的 Component
  • Text 对应鸿蒙的 Text
  • ScrollView 对应鸿蒙的 ListContainer
  • Modal 对应鸿蒙的 Dialog
  • TouchableOpacity 对应鸿蒙的 Button
  • TextInput 对应鸿蒙的 TextInput

鸿蒙系统对应用性能有较高要求,特别是在内存使用和启动速度方面。该应用的实现方式有利于在鸿蒙系统上获得良好的性能:

  • 使用函数式组件减少了内存占用
  • 使用多个 useState 钩子进行细粒度的状态管理
  • 避免了复杂的计算和不必要的渲染
  • 使用 TouchableOpacity 替代 Button,减少了视图层级

虽然代码中没有直接调用原生能力,但在实际的鸿蒙适配中,可能需要通过 React Native 的 Native Modules 机制来调用鸿蒙的特有 API。例如,当需要实现药物数据库的本地存储或与医疗系统的集成时,可能需要调用鸿蒙的文件系统 API 或网络 API。

资源加载

代码中使用了 Base64 编码的图标:

const ICONS_BASE64 = {
  medicine: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  warning: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  conflict: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

这种方式的优点是减少了网络请求,提高了应用的加载速度。对于小型图标来说,Base64 编码是一种非常有效的优化策略,特别是在跨端开发中,可以避免不同平台对图片资源的处理差异。


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

  • 状态管理与 UI 渲染分离
  • 数据结构与业务逻辑分离
  • 样式与组件分离

这种设计使得代码结构清晰,易于维护和扩展。例如,当需要添加新的药物冲突规则时,只需要修改 checkDrugConflicts 函数即可,不需要修改 UI 渲染逻辑。

TypeScript 的使用为代码提供了类型安全保障,减少了运行时错误的可能性。特别是在跨端开发中,类型系统可以帮助开发者发现不同平台之间的潜在差异。

应用使用了 StyleSheet.create 来管理样式,这种方式的优点是:

  • 提高了样式的复用性
  • 减少了运行时的样式计算
  • 使代码更加清晰易读

应用的代码组织合理,逻辑清晰:

  • 首先定义了必要的类型和数据
  • 然后实现了组件的状态管理
  • 接着实现了事件处理函数
  • 最后实现了 UI 渲染

这种组织方式使得代码易于阅读和理解,便于后续的维护和扩展。


通过对这个药物冲突检测应用的技术分析,我们可以看到 React Native 在跨端开发中的优势:

  • 开发效率高:使用 JavaScript/TypeScript 开发,代码量少,开发周期短
  • 跨平台兼容性好:一套代码可以在 iOS、Android 和鸿蒙系统上运行
  • 性能表现优秀:通过原生组件渲染,性能接近原生应用
  • 生态系统成熟:拥有丰富的第三方库和工具
  • 用户体验良好:可以构建出与原生应用相媲美的界面和交互
  • 功能强大:通过 Hooks 等特性,可以实现复杂的业务逻辑

在鸿蒙系统逐渐普及的背景下,React Native 作为一种跨端解决方案,具有广阔的应用前景。开发者可以通过学习和掌握 React Native,快速构建支持多平台的医疗应用,提高开发效率和代码复用率。

同时,我们也可以看到,React Native 在构建具有业务逻辑的应用时表现出色,例如这个药物冲突检测应用。通过简单的状态管理和组件组合,开发者可以实现完整的药物管理和冲突检测流程,为用户提供便捷的用药安全服务。


药物冲突检测是智慧医疗中用药安全的核心保障环节,聚焦“药物录入-冲突检测-风险提示”的全流程用药管理逻辑,既要保证药物信息录入的准确性与完整性,又需实现多端一致的冲突规则检测与风险提示交互。本文基于这套 React Native 药物冲突检测应用代码,从架构设计、核心业务逻辑、鸿蒙跨端适配三个维度,系统解读用药安全场景的跨端开发逻辑与技术要点,重点剖析 React Native 与鸿蒙系统的适配底层逻辑和落地实践方案,尤其针对药物添加、冲突检测、冲突详情展示等核心交互的跨端实现进行深度拆解。

一、避免因平台差异导致的冲突漏检或误检

该药物冲突检测应用基于 React Native 函数式组件 + TypeScript 强类型架构构建,核心依赖 React Native 原生基础组件(SafeAreaView、ScrollView、TouchableOpacity、TextInput、Modal 等)与 useState 状态管理,未引入第三方 UI 框架或复杂状态管理库。这种极简架构是药物冲突检测这类“强规则计算、轻设备交互”场景实现鸿蒙跨端的核心优势——轻量意味着适配成本更低,且能最大程度保证多端药物冲突检测规则的一致性,尤其适合药物信息录入、冲突规则校验、风险提示展示等核心逻辑的跨端复用。

从跨端技术底层逻辑来看,React Native 以“JS 桥接层(JS Bridge)”为核心实现跨端能力:前端编写的 JSX 组件与药物冲突检测逻辑,通过桥接层映射为不同平台的原生组件,iOS 端映射为 UIKit 体系、Android 端映射为 View 体系,而鸿蒙(HarmonyOS)端则通过 React Native for HarmonyOS 适配层,完成 React Native 组件/API 与鸿蒙 ArkUI 组件/API 的双向映射。该应用的代码结构完全遵循跨端开发规范:无平台专属硬编码、状态管理基于 React 原生 Hooks、样式采用跨端通用的 Flex 布局,从根源上消除了鸿蒙适配的技术壁垒,同时保证药物添加、冲突检测、冲突列表展示等核心用药安全流程逻辑在多端的一致性。

值得注意的是,应用核心的冲突检测逻辑(checkDrugConflicts)为纯 JS 函数实现,无任何平台相关依赖,这是跨端复用的关键——鸿蒙端可通过 JS 引擎直接执行该逻辑,无需适配任何原生能力,保证冲突检测规则在多端的完全一致,避免因平台差异导致的冲突漏检或误检。


1. 用药

应用通过 TypeScript 接口定义了 Medicine(药物)、DrugConflict(药物冲突)两类核心数据类型,字段设计精准匹配药物冲突检测全流程数据需求:

  • Medicine 涵盖药物ID、名称、剂量、服药时间、服用频率,完整覆盖临床用药的基础信息维度,字段均采用通用字符串格式,在鸿蒙端适配层可直接映射为 ArkTS 的 string 类型,避免多端数据类型解析差异导致的药物信息展示错误;
  • DrugConflict 包含冲突ID、两种冲突药物名称、冲突信息描述,完整覆盖药物冲突的展示与管理需求,冲突信息字段采用自然语言字符串格式,保证跨端展示的一致性,尤其在“阿司匹林与华法林增加出血风险”这类关键用药风险信息的传递上,保证了跨端的数据准确性。

这种强类型+场景化的数据模型设计,在跨端场景下保证了数据结构的一致性——鸿蒙端适配层可直接解析 TypeScript 类型定义,与 ArkTS 中的数据模型形成精准映射,避免多端数据格式不一致导致的冲突检测规则失效、冲突信息展示异常等核心问题,是用药安全场景跨端落地的基础保障。

2. Hooks 状态管理

应用采用 useState 实现多维度状态管理,核心状态均具备跨端复用的特性:

  • 核心业务状态(medicines/drugConflicts)中,medicines 支持动态增删(setMedicines),该更新方法在鸿蒙端会被适配层转换为 ArkTS 的 @State 状态更新逻辑,新增/删除药物后列表可实时渲染,保证多端药物列表展示的一致性;drugConflicts 支持冲突信息的动态添加与删除,其更新逻辑为纯 JS 数组操作,鸿蒙端直接执行,冲突列表的实时刷新跨端一致;
  • 表单状态(newMedicine)维护新增药物的录入信息,采用对象型状态设计,其更新逻辑(setNewMedicine({ ...newMedicine, name: text }))使用对象扩展运算符,为 ES6 标准语法,鸿蒙端直接执行,表单输入的双向绑定逻辑跨端统一,保证药物信息录入的交互体验一致;
  • 交互状态(isModalVisible/modalContent)维护冲突详情弹窗的显隐与内容,其更新逻辑为基础状态操作,鸿蒙端适配层会将 Modal 的显示状态映射为 ArkUI 弹窗的显隐状态,弹窗展示冲突详情的逻辑跨端统一。

1. 跨端兼容的样式

应用在基础样式之上新增表单输入、药物卡片、冲突卡片专属样式,核心样式设计仍遵循跨端兼容原则,适配鸿蒙系统无明显改造成本,且针对药物冲突检测场景的样式特性做了深度优化:

  • Flex 布局的跨端统一:从表单输入的“两行两列”横向分布(inputRowflexDirection: 'row' + justifyContent: 'space-between')、药物卡片的“图标+信息+删除按钮”横向布局,到冲突卡片的“图标+冲突信息”横向布局,全量采用 Flex 布局体系。Flex 作为 W3C 标准布局方案,在鸿蒙端可被适配层直接解析为 ArkUI 的 Flex 布局,无需重构任何布局逻辑,仅需保证样式属性命名与 React Native 规范一致,尤其在表单输入区域、药物列表、冲突列表等核心交互区域的布局上,Flex 布局的跨端一致性表现突出;
  • 用药安全专属样式的跨端适配
    • 表单输入框样式(inputbackgroundColor: '#f0f9ff' + borderRadius: 8 + padding)为通用样式属性,鸿蒙端适配层会将尺寸单位转换为 vp,输入框的大小、内边距、圆角跨端一致,保证药物信息录入的输入体验统一;
    • 药物卡片样式与此前设备/健康数据卡片保持一致,鸿蒙端可直接复用样式解析逻辑,保证列表展示的视觉统一性;
    • 冲突卡片的背景色(#fee2e2)为通用颜色属性,鸿蒙端适配层转换为 ArkUI 的 Color 类型,浅红色背景的视觉警示效果跨端统一,用户可快速识别药物冲突风险;
    • 删除按钮的样式(deleteButton:背景色 #ef4444、圆角 8)为通用样式属性,鸿蒙端直接解析,按钮的点击区域与视觉效果跨端一致,符合用户对“删除”操作的视觉认知;
  • 屏幕适配与层级兼容Dimensions.get('window') 获取设备宽高的 API 在鸿蒙端已完成原生映射,为不同尺寸鸿蒙设备(手机、平板)的自适应布局预留基础;shadow + elevation 的双层阴影设计,鸿蒙系统对 elevation 属性的支持与 Android 端完全兼容,保证药物卡片、冲突卡片等组件的视觉层级跨端统一;
  • 安全区域适配SafeAreaView 组件在鸿蒙端已适配为 ArkUI 的 SafeArea 组件,保证头部药物冲突检测标题区域在不同鸿蒙设备上的展示完整性,避免标题被刘海屏、全面屏遮挡。

(1)药物信息录入组件

药物信息录入组件是用药安全的核心输入环节,核心适配逻辑如下:

  • 表单输入行(inputRow)的 Flex 横向布局(flexDirection: 'row' + justifyContent: 'space-between')在鸿蒙端会被适配层转换为 ArkUI 的 flexDirection: FlexDirection.Row + justifyContent: FlexAlign.SpaceBetween,布局结构无需重构,保证“药物名称+剂量”“服药时间+频率”的两行两列排列在鸿蒙端与其他端完全一致;
  • 输入框(TextInput)的核心属性(placeholder/value/onChangeText)在鸿蒙端会被适配层转换为 ArkUI 的 TextInput 对应属性,占位符提示、值绑定、输入监听的逻辑跨端统一,且输入框的样式(背景色、圆角、内边距)为通用属性,鸿蒙端直接解析,保证药物信息录入的交互体验一致;
  • 添加按钮的点击逻辑(handleAddMedicine)包含表单校验、药物添加、冲突检测、弹窗反馈,该逻辑为纯 JS 实现,鸿蒙端直接执行:
    • 表单校验(newMedicine.name && newMedicine.dosage && ...)为纯 JS 布尔判断,鸿蒙端直接执行,校验规则跨端一致,保证只有完整的药物信息才会被添加;
    • 药物添加(setMedicines([...medicines, newMedicineEntry]))采用数组扩展运算符,鸿蒙端直接执行,药物列表的实时刷新跨端一致;
    • 冲突检测(checkDrugConflicts)为纯 JS 函数调用,鸿蒙端直接执行,检测规则跨端一致;
    • 反馈提示(Alert.alert)会被适配层转换为鸿蒙的 AlertDialog 组件,提示内容的拼接逻辑跨端一致。
(2)药物列表管理组件

药物列表管理组件是用药安全的核心展示环节,核心适配逻辑如下:

  • 药物列表的渲染采用 map 方法遍历 medicines 数组,该逻辑为纯 JS 数组操作,鸿蒙端通过 JS 引擎直接执行,列表渲染的顺序与数据展示的完整性跨端一致,保证录入的药物信息按添加顺序展示的核心逻辑不被破坏;
  • 药物卡片的删除按钮点击逻辑(handleDeleteMedicine)包含药物删除、冲突清理、弹窗反馈,该逻辑为纯 JS 实现:
    • 药物删除(medicines.filter(medicine => medicine.id !== medicineId))为纯 JS 数组操作,鸿蒙端直接执行,删除规则跨端一致;
    • 冲突清理(drugConflicts.filter(conflict => ...))为纯 JS 数组操作,保证删除药物后关联的冲突信息也被清理,清理规则跨端一致;
    • 反馈提示(Alert.alert)会被适配层转换为鸿蒙的 AlertDialog 组件,提示内容跨端一致。
(3)药物冲突展示组件

药物冲突展示组件是用药安全的核心风险提示环节,核心适配逻辑如下:

  • 冲突列表的渲染采用 map 方法遍历 drugConflicts 数组,该逻辑为纯 JS 数组操作,鸿蒙端直接执行,冲突列表的渲染顺序与展示内容跨端一致,保证检测出的药物冲突按生成顺序展示的核心逻辑不被破坏;
  • 冲突卡片的点击逻辑(handleViewConflict)包含冲突查找、弹窗内容拼接、弹窗显隐状态更新,冲突查找(drugConflicts.find(c => c.id === conflictId))为 JS 原生数组方法,弹窗内容拼接包含两种冲突药物名称与冲突信息,该逻辑在鸿蒙端直接执行,冲突详情的展示格式跨端统一;
  • 冲突详情弹窗(Modal)的核心属性(animationType/transparent/visible)在鸿蒙端会被适配层转换为 ArkUI 的 Dialog 组件对应属性,弹窗的滑动动画、透明背景、显隐逻辑跨端一致,且弹窗内的冲突信息文本展示格式统一,保证用户可清晰查看药物冲突的详细风险提示。

1. 药物添加

handleAddMedicine + checkDrugConflicts 是药物冲突检测的核心业务能力,实现了“表单校验-药物添加-冲突检测-风险提示”的全流程自动化,核心适配逻辑如下:

  • 表单校验逻辑(newMedicine.name && newMedicine.dosage && newMedicine.time && newMedicine.frequency)为纯 JS 布尔判断,鸿蒙端直接执行,校验规则仅依赖表单状态的字段值,跨端无差异,保证只有完整的药物信息才会被添加,避免因信息不全导致的冲突检测失效;
  • 药物添加逻辑中,新药物对象生成(newMedicineEntry)采用对象字面量语法,ID 生成((medicines.length + 1).toString())为纯 JS 数值操作,鸿蒙端直接执行,药物 ID 生成规则跨端一致;列表更新(setMedicines([...medicines, newMedicineEntry]))采用数组扩展运算符,该操作符为 ES6 标准语法,鸿蒙端直接执行,药物列表的实时刷新跨端一致;
  • 冲突检测逻辑(checkDrugConflicts)为纯 JS 函数实现,核心规则(medicine.name === '阿司匹林' && newMedicine.name === '华法林')为字符串相等判断,鸿蒙端直接执行,检测规则跨端完全一致,保证“阿司匹林与华法林增加出血风险”这类核心冲突规则在多端的精准检测;
  • 冲突列表更新逻辑(setDrugConflicts([...drugConflicts, ...conflicts]))采用数组扩展运算符合并新冲突,鸿蒙端直接执行,冲突列表的实时刷新跨端一致;
  • 风险提示逻辑(Alert.alert)使用跨端兼容的弹窗 API,鸿蒙端适配层转换为 AlertDialog 组件,提示内容(“药物冲突警告”/“添加成功”)为固定字符串,跨端展示与交互逻辑完全一致,保证用户能及时收到冲突风险提示或添加成功反馈。

2. 药物删除

handleDeleteMedicine 函数是药物列表管理的核心交互逻辑,核心适配逻辑如下:

  • 药物删除逻辑(medicines.filter(medicine => medicine.id !== medicineId))为纯 JS 数组操作,鸿蒙端直接执行,删除规则仅依赖药物 ID,跨端一致,保证删除指定药物的准确性;
  • 冲突清理逻辑(drugConflicts.filter(conflict => conflict.medicine1 !== ... && conflict.medicine2 !== ...))为纯 JS 数组过滤操作,鸿蒙端直接执行,清理规则仅依赖药物名称,跨端一致,保证删除药物后关联的冲突信息也被同步清理,避免无效冲突信息残留;
  • 反馈提示逻辑(Alert.alert)使用跨端兼容的弹窗 API,鸿蒙端转换为 AlertDialog 组件,提示内容(“删除成功”)为固定字符串,跨端展示与交互逻辑完全一致。

3. 冲突详情查看

handleViewConflict 函数是药物冲突风险提示的核心交互逻辑,核心适配逻辑如下:

  • 冲突查找逻辑(drugConflicts.find(c => c.id === conflictId))为纯 JS 数组方法调用,鸿蒙端直接执行,基于 ID 精准查找冲突的规则跨端一致,保证能定位到用户点击的具体冲突信息;
  • 弹窗内容拼接逻辑(setModalContent)采用字符串换行拼接,该逻辑为通用 JS 字符串操作,鸿蒙端直接执行,拼接后的冲突详情格式(药物1、药物2、冲突信息)跨端统一,保证用户可清晰查看药物冲突的详细风险提示;
  • 弹窗显隐状态更新(setIsModalVisible(true))为基础状态操作,鸿蒙端适配层会将 Modal 组件的 visible 属性映射为 ArkUI 弹窗的显隐状态,弹窗的触发时机与展示逻辑跨端统一。

4. 跨端 API

应用使用的核心 API 均为 React Native 跨端兼容 API,在鸿蒙端可无缝适配:

  • 数组 API:map/find/filter/扩展运算符等数组方法为 JS 原生 API,鸿蒙端通过 JS 引擎直接执行,无需适配,保证药物列表、冲突列表的渲染、筛选、更新等核心逻辑的跨端一致性;
  • 字符串 API:字符串拼接/换行符(\n)等操作为 JS 原生 API,鸿蒙端直接执行,保证弹窗内容拼接等核心逻辑的跨端一致性;
  • 表单 API:TextInputonChangeText/value/placeholder 等属性在鸿蒙端已完成适配,输入监听、值绑定、占位符提示的逻辑跨端统一,保证药物信息录入的交互体验一致;
  • 弹窗 API:Alert.alert/Modal 已被适配层封装为鸿蒙的 AlertDialog/Dialog 组件,交互逻辑完全复用,在冲突风险提示、药物添加/删除反馈、冲突详情展示等核心场景中,弹窗的展示与操作逻辑多端统一;
  • 样式 API:StyleSheet.create 封装的样式规则,适配层转换为 ArkUI 的样式对象,新增的表单输入、冲突卡片样式属性(justifyContent/backgroundColor)均为通用属性,无需修改即可适配鸿蒙。

该药物冲突检测应用作为智慧医疗用药安全核心模块,适配鸿蒙系统的成本极低,核心适配思路与技术要点如下:

1. 纯 JS 业务逻辑的跨端复用

应用核心的冲突检测逻辑(checkDrugConflicts)为纯 JS 函数实现,无任何平台相关依赖,这是跨端复用的最大优势——鸿蒙端可通过 JS 引擎直接执行该逻辑,无需适配任何原生能力。在生产环境中扩展冲突检测规则(如添加更多药物组合、基于剂量/频率的复杂规则)时,新增规则仍为纯 JS 逻辑,鸿蒙端可直接复用,仅需保证规则逻辑的通用性,无需考虑平台差异,这也是用药安全场景跨端开发的核心优势。

2. 性能

该应用当前的列表渲染采用基础 map 方法,在生产环境中若药物/冲突数据量较大(如超过50条药物记录、超过20条冲突信息),可替换为 React Native 的 FlatList 高性能列表组件——FlatList 在鸿蒙端已完成深度适配,支持虚拟化列表渲染,其核心属性(data/renderItem/keyExtractor)与 React Native 端完全一致,仅需少量调整即可适配鸿蒙端的性能优化策略,保证列表的滚动性能,尤其适合长期用药管理产生的海量药物数据与冲突信息展示场景。

3. 用药安全样式

鸿蒙系统有自身的设计规范,在适配时可通过条件编译实现差异化样式,既保证遵循鸿蒙设计规范,又能最大程度复用现有代码:

// 鸿蒙端药物冲突样式差异化适配示例
import { Platform } from 'react-native';
const isHarmonyOS = Platform.OS === 'harmony';

const adaptiveStyles = {
  input: {
    ...styles.input,
    backgroundColor: isHarmonyOS ? '#e0f7fa' : '#f0f9ff',
    borderRadius: isHarmonyOS ? 10 : 8,
  },
  conflictCard: {
    ...styles.conflictCard,
    backgroundColor: isHarmonyOS ? '#fecdd3' : '#fee2e2',
    borderRadius: isHarmonyOS ? 14 : 12,
  },
  deleteButton: {
    ...styles.deleteButton,
    backgroundColor: isHarmonyOS ? '#dc2626' : '#ef4444',
    borderRadius: isHarmonyOS ? 10 : 8,
  }
};

这种轻量级的差异化适配,既能保证符合鸿蒙的设计规范,又能保留现有代码的完整性,尤其在表单输入框、冲突卡片、删除按钮等核心用药安全交互组件的样式适配中,效果显著。

该 React Native 药物冲突检测应用实现了药物信息录入、冲突自动检测、冲突列表管理、冲突详情展示等核心智慧医疗用药安全功能,代码结构符合跨端开发规范,可低成本适配鸿蒙系统。针对生产环境落地,可做以下优化:

  1. 冲突检测规则扩展:扩展 checkDrugConflicts 函数中的检测规则,添加更多药物组合的冲突规则(如布洛芬与对乙酰氨基酚、降压药与降糖药等),规则逻辑为纯 JS 实现,鸿蒙端可直接执行,提升冲突检测的覆盖范围与准确性;
  2. 药物信息标准化:对接药品数据库 API,实现药物名称的自动补全与标准化校验,避免因药物名称输入不规范导致的冲突检测失效,鸿蒙端适配时仅需封装 API 调用逻辑,核心冲突检测逻辑无需重构;
  3. 冲突风险等级划分:为 DrugConflict 模型新增风险等级字段(高/中/低),并在 UI 层添加对应的视觉标识,风险等级判断逻辑为纯 JS 实现,鸿蒙端直接执行,提升风险提示的精准度;
  4. 数据持久化与同步:引入 AsyncStorage(或鸿蒙原生 Preferences)实现药物信息与冲突记录的本地加密存储,同时对接医疗云平台接口实现数据云端同步,鸿蒙端兼容主流存储方案,可直接复用数据处理逻辑,保证用药信息的跨设备同步;
  5. 鸿蒙原生能力融合:通过 React Native 原生模块封装鸿蒙的健康服务能力,将药物冲突信息接入鸿蒙的健康应用,实现用药风险的统一管理,核心冲突检测逻辑可完全复用现有代码,仅需对接鸿蒙的健康服务接口。

真实演示案例代码:






// 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 = {
  medicine: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  warning: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  conflict: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// 药物类型
type Medicine = {
  id: string;
  name: string;
  dosage: string;
  time: string;
  frequency: string;
};

// 药物冲突类型
type DrugConflict = {
  id: string;
  medicine1: string;
  medicine2: string;
  conflictInfo: string;
};

// 药物冲突检测应用组件
const DrugConflictCheckerApp: React.FC = () => {
  const [medicines, setMedicines] = useState<Medicine[]>([
    {
      id: '1',
      name: '阿司匹林',
      dosage: '100mg',
      time: '08:00',
      frequency: '每日一次'
    },
    {
      id: '2',
      name: '华法林',
      dosage: '5mg',
      time: '12:00',
      frequency: '每日一次'
    }
  ]);

  const [drugConflicts, setDrugConflicts] = useState<DrugConflict[]>([
    {
      id: '1',
      medicine1: '阿司匹林',
      medicine2: '华法林',
      conflictInfo: '同时使用可能增加出血风险'
    }
  ]);

  const [newMedicine, setNewMedicine] = useState({
    name: '',
    dosage: '',
    time: '',
    frequency: ''
  });

  const [isModalVisible, setIsModalVisible] = useState(false);
  const [modalContent, setModalContent] = useState('');

  const handleAddMedicine = () => {
    if (newMedicine.name && newMedicine.dosage && newMedicine.time && newMedicine.frequency) {
      const newMedicineEntry: Medicine = {
        id: (medicines.length + 1).toString(),
        name: newMedicine.name,
        dosage: newMedicine.dosage,
        time: newMedicine.time,
        frequency: newMedicine.frequency
      };
      setMedicines([...medicines, newMedicineEntry]);

      // 检测药物冲突
      const conflicts = checkDrugConflicts(newMedicineEntry);
      if (conflicts.length > 0) {
        setDrugConflicts([...drugConflicts, ...conflicts]);
        Alert.alert('药物冲突警告', '检测到潜在的药物冲突,请查看详细信息');
      } else {
        Alert.alert('添加成功', '药物已添加到用药计划中');
      }

      setNewMedicine({ name: '', dosage: '', time: '', frequency: '' });
    } else {
      Alert.alert('提示', '请填写完整的药物信息');
    }
  };

  const checkDrugConflicts = (newMedicine: Medicine): DrugConflict[] => {
    const conflicts: DrugConflict[] = [];
    medicines.forEach(medicine => {
      if (medicine.name === '阿司匹林' && newMedicine.name === '华法林') {
        conflicts.push({
          id: (drugConflicts.length + conflicts.length + 1).toString(),
          medicine1: medicine.name,
          medicine2: newMedicine.name,
          conflictInfo: '同时使用可能增加出血风险'
        });
      }
      // 可以继续添加其他药物冲突规则
    });
    return conflicts;
  };

  const handleDeleteMedicine = (medicineId: string) => {
    setMedicines(medicines.filter(medicine => medicine.id !== medicineId));
    setDrugConflicts(drugConflicts.filter(conflict => conflict.medicine1 !== medicines.find(m => m.id === medicineId)?.name && conflict.medicine2 !== medicines.find(m => m.id === medicineId)?.name));
    Alert.alert('删除成功', '药物已从用药计划中移除');
  };

  const handleViewConflict = (conflictId: string) => {
    const conflict = drugConflicts.find(c => c.id === conflictId);
    if (conflict) {
      setModalContent(`药物1: ${conflict.medicine1}\n药物2: ${conflict.medicine2}\n冲突信息: ${conflict.conflictInfo}`);
      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}>输入多种药品后,系统提示潜在的药物冲突或禁忌</Text>
      </View>

      <ScrollView style={styles.content}>
        {/* 添加药物 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>添加药物</Text>
          <View style={styles.inputRow}>
            <TextInput
              style={styles.input}
              placeholder="药物名称"
              value={newMedicine.name}
              onChangeText={(text) => setNewMedicine({ ...newMedicine, name: text })}
            />
            <TextInput
              style={styles.input}
              placeholder="剂量"
              value={newMedicine.dosage}
              onChangeText={(text) => setNewMedicine({ ...newMedicine, dosage: text })}
            />
          </View>
          <View style={styles.inputRow}>
            <TextInput
              style={styles.input}
              placeholder="服药时间 (HH:MM)"
              value={newMedicine.time}
              onChangeText={(text) => setNewMedicine({ ...newMedicine, time: text })}
            />
            <TextInput
              style={styles.input}
              placeholder="频率"
              value={newMedicine.frequency}
              onChangeText={(text) => setNewMedicine({ ...newMedicine, frequency: text })}
            />
          </View>
          <TouchableOpacity 
            style={styles.addButton}
            onPress={handleAddMedicine}
          >
            <Text style={styles.addText}>添加药物</Text>
          </TouchableOpacity>
        </View>

        {/* 药物列表 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>药物列表</Text>
          {medicines.map(medicine => (
            <View key={medicine.id} style={styles.medicineCard}>
              <Text style={styles.icon}>💊</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>{medicine.name}</Text>
                <Text style={styles.cardDescription}>剂量: {medicine.dosage}</Text>
                <Text style={styles.cardDescription}>时间: {medicine.time}</Text>
                <Text style={styles.cardDescription}>频率: {medicine.frequency}</Text>
              </View>
              <TouchableOpacity 
                style={styles.deleteButton}
                onPress={() => handleDeleteMedicine(medicine.id)}
              >
                <Text style={styles.deleteText}>删除</Text>
              </TouchableOpacity>
            </View>
          ))}
        </View>

        {/* 药物冲突列表 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>药物冲突列表</Text>
          {drugConflicts.map(conflict => (
            <TouchableOpacity 
              key={conflict.id}
              style={styles.conflictCard}
              onPress={() => handleViewConflict(conflict.id)}
            >
              <Text style={styles.icon}>⚠️</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>{conflict.medicine1}{conflict.medicine2}</Text>
                <Text style={styles.cardDescription}>{conflict.conflictInfo}</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,
  },
  inputRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 12,
  },
  input: {
    flex: 1,
    backgroundColor: '#f0f9ff',
    borderRadius: 8,
    paddingHorizontal: 12,
    paddingVertical: 8,
    fontSize: 14,
    color: '#0c4a6e',
    marginRight: 8,
  },
  addButton: {
    backgroundColor: '#0284c7',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  addText: {
    color: '#ffffff',
    fontSize: 14,
    fontWeight: '500',
  },
  medicineCard: {
    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,
  },
  deleteButton: {
    backgroundColor: '#ef4444',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 8,
  },
  deleteText: {
    color: '#ffffff',
    fontSize: 12,
    fontWeight: '500',
  },
  conflictCard: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#fee2e2',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
  },
  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 DrugConflictCheckerApp;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文介绍了一个基于React Native开发的药物冲突检测应用,重点分析了其技术实现和跨平台适配策略。应用采用React函数式组件和Hooks进行状态管理,使用TypeScript确保类型安全,并通过Flexbox布局实现响应式设计。核心功能包括药物添加/删除、冲突检测和详细信息查看,均采用React Native跨平台组件实现。文章特别探讨了该应用在鸿蒙系统的适配方案,指出React Native核心组件在鸿蒙平台上均有对应实现,且函数式组件设计有助于满足鸿蒙对性能的高要求。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐