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


在末端物流数字化升级的进程中,驿站管理类应用需要同时满足多终端适配、轻量化操作、状态实时同步等核心诉求。React Native 凭借其“一次开发,多端部署”的技术特性,成为连接 iOS、Android 与鸿蒙(HarmonyOS)系统的最优技术路径。本文以驿站包裹管理应用为例,从数据模型设计、鸿蒙风格 UI 实现、业务逻辑跨端兼容等维度,完整解析 React Native 对接鸿蒙系统的技术内核与落地实践。

一、基础架构

本驿站管理应用聚焦包裹到站、通知、取件全流程管理,核心覆盖“待取件-已通知-已取件”三大状态流转,整体架构遵循 React Native 组件化开发范式,同时深度契合鸿蒙系统的设计语言与交互逻辑。从技术选型来看,应用基于 React 函数式组件 + TypeScript 构建,这种组合既保证了代码的类型安全,又能最大化跨端复用率,是 React Native 适配鸿蒙系统的理想技术底座。

1. 跨端数据一致性

在跨端应用开发中,统一且精准的数据模型是避免多端行为不一致的关键前提。代码中通过 TypeScript 严格定义了包裹的核心数据结构,覆盖驿站管理全场景的业务属性:

// 包裹核心数据模型
type Parcel = {
  id: string;
  trackingNumber: string;
  recipientName: string;
  recipientPhone: string;
  arrivalTime: string;
  pickupCode: string;
  status: '待取件' | '已通知' | '已取件';
};

这种强类型定义不仅在开发阶段提供语法校验和智能提示,更关键的是在鸿蒙系统适配时,能够与 ArkTS 的类型系统形成天然映射。相较于纯 JavaScript 开发,TypeScript 可有效规避因数据类型模糊导致的跨端兼容性问题——尤其是在鸿蒙这类面向全场景智慧终端的操作系统中,类型安全能大幅降低多设备适配的调试成本,确保包裹状态、取件码等核心数据在不同终端的一致性。

2. 轻量级状态管理:

应用采用 React 内置的 useState Hook 管理核心业务状态,结合不可变数据模式实现包裹状态的跨端更新:

const [parcels, setParcels] = useState<Parcel[]>([...]);
const [searchTerm, setSearchTerm] = useState('');

// 状态更新示例 - 标记包裹已取件
setParcels(parcels.map(p => 
  p.id === parcelId 
    ? { ...p, status: '已取件' } 
    : p
));

这种轻量级状态管理方案完全适配跨端开发场景,相较于 Redux 等重型状态库,useState 无需额外的中间件和适配层,能够直接在 React Native 支持的所有平台(包括鸿蒙)上稳定运行。从鸿蒙系统的视角来看,useState 的状态更新逻辑与 ArkUI 的 @State 装饰器在设计理念上高度契合,开发者无需切换思维模式即可完成跨端开发,大幅降低了鸿蒙适配的学习成本。


React Native 的核心价值在于通过统一的组件抽象层,屏蔽不同平台的 UI 实现差异。本应用在 UI 层的设计深度复刻了鸿蒙系统的视觉风格与交互规范,同时保证多端体验的一致性。

1. Flex 布局

应用基于 React Native 的 Flex 布局系统构建整体界面,通过 Dimensions.get('window') 获取设备宽高,实现对不同尺寸鸿蒙设备(手机、平板、桌面终端)的自适应:

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

相较于鸿蒙系统的 DirectionalLayoutStackLayout,React Native 的 Flex 布局具备更强的跨端兼容性,通过 flexDirectionjustifyContentflexWrap 等属性,能够精准还原鸿蒙系统的布局逻辑。例如包裹操作按钮区域的自适应布局:

<View style={styles.parcelActions}>
  {parcel.status === '待取件' && (
    <>
      <TouchableOpacity style={styles.editButton}>
        <Text style={styles.editButtonText}>编辑信息</Text>
      </TouchableOpacity>
      <TouchableOpacity style={styles.notifyButton}>
        <Text style={styles.notifyButtonText}>发送通知</Text>
      </TouchableOpacity>
    </>
  )}
  {/* 其他状态按钮 */}
</View>

这段代码通过 flexDirection: 'row'flexWrap: 'wrap' 实现了鸿蒙系统特有的流式布局效果,在不同尺寸的鸿蒙设备上都能保持按钮的合理排列,无需针对鸿蒙系统做额外的布局适配。

2. 视觉样式:

应用通过 StyleSheet.create 定义样式表,深度适配鸿蒙系统的设计规范,核心体现在以下几个维度:

  • 色彩体系:采用鸿蒙系统的暖黄色系主色调(#ca8a04#854d0e),搭配功能性色彩区分包裹状态(待取件-橙色、已通知-蓝色、已取件-绿色),符合鸿蒙系统“自然、舒适”的视觉设计理念
  • 圆角与阴影:使用 borderRadius: 12 实现鸿蒙风格的大圆角设计,通过 elevation(Android)和 shadow(iOS)属性适配鸿蒙系统的阴影效果,兼顾视觉层次感与跨端一致性
  • 状态徽章与取件码样式:通过动态样式绑定实现鸿蒙风格的状态标签,同时强化取件码的视觉层级:
<View style={[
  styles.statusBadge,
  { backgroundColor: getStatusColor(parcel.status) }
]}>
  <Text style={styles.statusText}>{parcel.status}</Text>
</View>

<Text style={styles.pickupCode}>{parcel.pickupCode}</Text>

这种样式设计方案完全基于 React Native 的标准 API 实现,在鸿蒙系统中能够通过 React Native 的渲染层自动转换为原生样式——backgroundColor 对应鸿蒙的 background-colorborderRadius 对应鸿蒙的 border-radius,无需编写平台特定代码。

3. 交互组件:

应用中所有交互组件均基于 React Native 基础组件封装,同时适配鸿蒙系统的交互规范,新增的 TextInput 组件更是实现了鸿蒙风格的搜索与表单交互:

  • SafeAreaView:对应鸿蒙系统的 SafeArea 组件,适配刘海屏、挖孔屏等异形屏,保证界面在鸿蒙不同终端设备上的完整性
  • TextInput:替代鸿蒙的 Input 组件,实现搜索框与表单输入功能,通过 placeholderonChangeText 等属性适配鸿蒙的输入交互规范
  • Alert.prompt:对应鸿蒙的 TextInputDialog 组件,实现包裹添加、收件人信息编辑等带输入的弹窗交互,保持与鸿蒙原生应用一致的交互体验
  • TouchableOpacity:替代鸿蒙的 Button 组件,实现点击反馈效果,符合鸿蒙的交互规范

以添加包裹的交互流程为例,代码通过 Alert.prompt 实现鸿蒙风格的分步输入弹窗:

Alert.prompt(
  '添加到站包裹',
  '请输入快递单号:',
  [
    { text: '取消', style: 'cancel' },
    {
      text: '确定',
      onPress: (trackingNumber) => {
        if (trackingNumber) {
          // 包裹添加逻辑
          Alert.alert('成功', `包裹已添加,取件码: ${newParcel.pickupCode}`);
        }
      }
    }
  ],
  'plain-text'
);

这种交互逻辑完全基于 React Native 的跨端 API 实现,在鸿蒙系统中能够保持与原生应用一致的交互体验,无需针对鸿蒙系统做特殊处理。


除了 UI 层的适配,业务逻辑的跨端兼容性是 React Native 开发的核心。本应用的核心业务逻辑包括取件码生成、包裹状态流转、搜索筛选、多步表单交互等,这些逻辑完全基于 JavaScript/TypeScript 实现,天然具备跨端运行能力。

1. 取件码生成与包裹状态流转

应用实现了完整的取件码自动生成逻辑,通过纯函数生成符合驿站规范的字母+数字组合取件码:

const generatePickupCode = () => {
  const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  const numbers = '0123456789';
  let code = '';
  
  // 生成1个字母 + 3个数字的取件码
  code += letters.charAt(Math.floor(Math.random() * letters.length));
  for (let i = 0; i < 3; i++) {
    code += numbers.charAt(Math.floor(Math.random() * numbers.length));
  }
  
  return code;
};

该函数完全基于纯 JavaScript 实现,不依赖任何平台特定 API,在 React Native 支持的所有平台(包括鸿蒙)上都能稳定运行。同时,包裹状态流转逻辑(待取件 → 已通知 → 已取件)采用不可变数据模式更新,通过 Array.map 实现状态的跨端一致性更新,避免因平台运行时差异导致的状态错乱。

2. 多维度搜索筛选

应用实现了基于快递单号、收件人姓名、取件码的多维度搜索筛选:

const filteredParcels = parcels.filter(parcel => 
  parcel.trackingNumber.includes(searchTerm) || 
  parcel.recipientName.includes(searchTerm) ||
  parcel.pickupCode.includes(searchTerm)
);

这种无副作用的纯函数筛选方式,不仅符合 React 的设计理念,更重要的是在跨端场景下,能够避免因不同平台的运行时差异导致的筛选结果不一致。对于鸿蒙系统而言,这类纯逻辑代码无需任何适配即可直接运行,是跨端开发的最优实践。

3. 多步表单

应用针对收件人信息编辑场景,实现了鸿蒙风格的多步表单交互:

Alert.prompt(
  '编辑收件人信息',
  '请输入收件人姓名:',
  [
    { text: '取消', style: 'cancel' },
    {
      text: '下一步',
      onPress: (name) => {
        if (name) {
          Alert.prompt(
            '编辑收件人信息',
            '请输入收件人电话:',
            [/* 确认逻辑 */],
            'plain-text',
            parcel.recipientPhone
          );
        }
      }
    }
  ],
  'plain-text',
  parcel.recipientName
);

该逻辑基于 React Native 的 Alert.prompt 嵌套实现,完全兼容鸿蒙系统的弹窗交互规范,在不同平台上都能保持一致的交互流程,体现了 React Native “一次编写,多端复用”的核心价值。


从本应用的实现来看,React Native 对接鸿蒙系统的核心在于“抽象层适配”,而非“重写适配”,具体体现在以下几个维度:

  1. JS 引擎层兼容:鸿蒙系统内置了符合 ECMAScript 标准的 JavaScript 引擎,能够直接执行 React Native 的 JS 代码,这是跨端运行的底层基础。本应用中所有的业务逻辑代码(取件码生成、状态更新、搜索筛选)均运行在 JS 引擎层,无需任何修改即可在鸿蒙系统中执行,保证了业务逻辑的跨端一致性。

  2. 组件映射层适配:React Native 通过自定义渲染器,将 React 组件(View、Text、TextInput、TouchableOpacity 等)映射为鸿蒙系统的原生组件。例如 TextInput 组件会被转换为鸿蒙的 Input 组件,Alert.prompt 会被转换为鸿蒙的 TextInputDialog 组件,这种映射关系由 React Native 的鸿蒙适配层自动完成,开发者无需关注底层实现细节。

  3. 样式转换层适配:React Native 的 StyleSheet 样式会被自动转换为鸿蒙系统的原生样式,本应用中定义的所有鸿蒙风格样式(暖黄色系、大圆角、状态色彩)都能通过这一层完成自动转换,保证了 UI 风格在鸿蒙系统中的一致性。

  4. API 适配层兼容:React Native 提供的基础 API(如 Alert、Dimensions、TextInput)都有对应的鸿蒙实现,本应用中使用的 Alert.promptTextInput 等 API,在鸿蒙系统中会被替换为对应的原生 API 调用,保证了交互行为的跨端一致性。

本驿站管理应用的实现完整展现了 React Native 在鸿蒙跨端开发领域的技术优势,核心要点可总结为:

  1. 强类型设计保障跨端一致性:TypeScript 类型定义不仅提升代码质量,更能与鸿蒙 ArkTS 形成类型映射,降低跨端适配成本,是物流驿站类应用跨端开发的基础保障
  2. 纯逻辑开发最大化复用率:取件码生成、状态流转、搜索筛选等核心逻辑采用纯 JavaScript/TypeScript 实现,无需针对鸿蒙系统做特殊修改,大幅提升开发效率
  3. 标准 API 适配鸿蒙生态:基于 React Native 标准组件和 API 开发,通过底层适配层自动对接鸿蒙原生能力,兼顾开发效率与原生体验

对于希望基于 React Native 开发鸿蒙应用的开发者而言,本案例提供了清晰的实践路径:聚焦业务逻辑的跨端复用,依托 React Native 的抽象层完成与鸿蒙系统的对接,既能享受跨端开发的效率优势,又能保证鸿蒙系统的原生体验,是驿站管理类应用跨端开发的最优解。


本项目采用React Native函数式组件架构,以ParcelStationApp为核心组件,实现了驿站包裹管理的完整功能流程。架构设计遵循模块化原则,将数据结构、状态管理和业务逻辑清晰分离,便于维护和扩展。

核心技术栈

  • React Native:跨平台移动应用开发框架,支持iOS、Android和鸿蒙系统
  • TypeScript:提供类型安全,增强代码可维护性和开发体验
  • Hooks API:使用useState进行状态管理,简化组件逻辑
  • Flexbox:实现响应式布局,适配不同屏幕尺寸
  • Base64图标:内置图标资源,减少网络请求,提升加载速度
  • Dimensions API:获取屏幕尺寸,实现精细化布局控制

包裹类型(Parcel)

type Parcel = {
  id: string;
  trackingNumber: string;
  recipientName: string;
  recipientPhone: string;
  arrivalTime: string;
  pickupCode: string;
  status: '待取件' | '已通知' | '已取件';
};

该类型设计全面,包含了包裹管理的核心信息:

  • id:唯一标识符,确保数据唯一性
  • trackingNumber:快递单号,便于包裹追踪
  • recipientNamerecipientPhone:收件人信息,便于联系
  • arrivalTime:到站时间,用于包裹管理
  • pickupCode:取件码,确保包裹安全领取
  • status:包裹状态,使用联合类型确保类型安全,清晰定义业务流程

核心状态

应用使用useState钩子管理两个核心状态:

  • parcels:包裹列表,支持动态更新
  • searchTerm:搜索关键词,用于包裹查找

状态更新

  1. 包裹添加:通过handleAddParcel函数,生成新包裹并添加到列表
  2. 状态更新:通过handleNotifyRecipient和handleMarkPickedUp函数,更新包裹状态
  3. 信息编辑:通过handleEditRecipient函数,修改收件人信息
  4. 搜索功能:通过searchTerm状态,实现包裹查找

状态管理

  • 不可变数据模式:使用扩展运算符(...)创建新状态,避免直接修改原状态
  • 批量更新:在状态更新时使用map方法批量处理,确保数据一致性
  • 状态验证:在进行操作前,通过find方法验证包裹是否存在
  • 用户反馈:操作完成后,通过Alert组件提供明确的成功提示

核心业务

  1. 包裹管理:展示包裹列表,包含完整的包裹信息
  2. 包裹添加:输入快递单号,自动生成取件码,添加新包裹
  3. 通知收件人:发送取件通知,更新包裹状态为"已通知"
  4. 确认取件:验证取件信息,更新包裹状态为"已取件"
  5. 信息编辑:修改收件人信息,确保数据准确性

核心业务

const handleAddParcel = () => {
  Alert.prompt(
    '添加到站包裹',
    '请输入快递单号:',
    [
      { text: '取消', style: 'cancel' },
      {
        text: '确定',
        onPress: (trackingNumber) => {
          if (trackingNumber) {
            const newParcel: Parcel = {
              id: (parcels.length + 1).toString(),
              trackingNumber: trackingNumber,
              recipientName: '待填写',
              recipientPhone: '待填写',
              arrivalTime: new Date().toLocaleString('zh-CN'),
              pickupCode: generatePickupCode(),
              status: '待取件'
            };
            setParcels([...parcels, newParcel]);
            Alert.alert('成功', `包裹已添加,取件码: ${newParcel.pickupCode}`);
          }
        }
      }
    ],
    'plain-text'
  );
};

数据流

  • 单向数据流:状态 → 视图 → 用户操作 → 状态更新
  • 数据验证:在添加包裹前验证快递单号是否存在
  • 业务逻辑封装:将复杂操作逻辑封装在专用函数中,提高代码可读性
  • 用户交互反馈:使用Alert组件提供操作确认和信息提示,提升用户体验

组件

  • 核心组件:SafeAreaView、View、Text、ScrollView、TouchableOpacity、TextInput等在鸿蒙系统上有对应实现
  • API兼容性:Dimensions API在鸿蒙系统中可正常使用,确保布局适配
  • Alert组件:鸿蒙系统支持Alert组件的基本功能,但样式可能有所差异
  • Alert.prompt:需要注意鸿蒙系统对Alert.prompt的支持情况,可能需要自定义实现

资源

  • Base64图标:在鸿蒙系统中同样支持,可减少网络请求,提升性能
  • 内存管理:鸿蒙系统对内存使用更为严格,需注意资源释放
  • 计算性能:对于取件码生成等操作,鸿蒙系统的处理性能与其他平台相当

性能

  1. 渲染性能

    • 避免不必要的重渲染,合理使用React.memo
    • 对于长包裹列表,建议使用FlatList替代ScrollView
    • 优化组件结构,减少嵌套层级
  2. 数据处理

    • 缓存计算结果,避免重复计算
    • 优化包裹查找算法,特别是搜索功能
    • 考虑使用useMemo缓存计算结果,提高性能
  3. 内存管理

    • 及时释放不再使用的资源
    • 避免内存泄漏,特别是在处理多个包裹时
    • 合理使用缓存策略,平衡性能和内存占用

类型定义

  • 使用枚举类型:将包裹状态使用枚举类型替代字符串,提高代码可读性和可维护性
  • 类型扩展:考虑使用接口继承,增强类型系统的表达能力
  • 类型守卫:添加更多类型守卫,确保运行时类型安全
  • 国际化支持:考虑添加国际化支持,特别是包裹状态等术语

状态管理

  • 状态分离:对于复杂状态,考虑使用useReducer或状态管理库(如Redux、Zustand)
  • 状态持久化:实现状态持久化,使用AsyncStorage存储包裹数据
  • 计算属性:使用useMemo缓存计算结果,避免重复计算
  • 批量操作:支持批量管理包裹,提高驿站效率

组件化

  • 组件拆分:将大型组件拆分为更小的可复用组件,如ParcelCard、AddParcelForm、SearchBar等
  • 自定义Hook:提取重复的状态逻辑到自定义Hook,如useParcelManager、usePickupCodeGenerator等
  • 高阶组件:使用高阶组件处理横切关注点,如错误边界、加载状态等
  • 表单组件:封装表单输入组件,提高代码复用性

业务逻辑

  • 服务层分离:将业务逻辑分离到服务层,提高代码可测试性
  • 错误处理:增强错误处理机制,提供更友好的错误提示
  • 实时更新:考虑添加实时包裹状态更新,提升用户体验
  • 智能排序:实现包裹智能排序,如按到站时间、状态等排序

性能

  • 列表优化:使用FlatList的性能优化特性,如getItemLayout、initialNumToRender等
  • 网络优化:实现请求防抖和节流,减少网络请求频率
  • Bundle优化:使用代码分割和Tree Shaking,减少应用体积
  • 启动优化:优化应用启动速度,减少初始加载时间

本项目展示了如何使用React Native和TypeScript构建一个功能完整的驿站管理应用。通过合理的架构设计、类型定义和状态管理,实现了跨平台的一致性体验。

在鸿蒙跨端开发方面,需要注意平台特定API的兼容性处理,优化资源管理和渲染性能,确保在不同平台上都能提供良好的用户体验。同时,由于涉及包裹管理的特殊性,还需特别关注数据准确性和操作效率。

随着React Native和鸿蒙系统的不断发展,跨端开发将会变得更加成熟和高效。未来可以考虑:

  1. 使用React Native 0.70+版本:利用新的架构特性,如Fabric渲染器和Turbo Modules,提升应用性能
  2. 探索鸿蒙原生能力:充分利用鸿蒙系统的分布式能力,实现多设备协同和更丰富的功能
  3. 引入现代化状态管理:使用Redux Toolkit或Zustand等现代状态管理库,简化状态管理
  4. 实现PWA支持:扩展应用的使用场景,支持Web平台
  5. 集成AI能力:引入AI技术,如包裹自动识别、智能分拣等,提升应用智能化水平
  6. 实时监控系统:集成实时监控系统,实现对驿站运营状态的24小时监控
  7. 多语言支持:添加多语言支持,提升应用的全球适配能力

真实演示案例代码:




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

// Base64 图标库
const ICONS_BASE64 = {
  package: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  inbox: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  notification: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  code: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  phone: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  search: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  check: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// 包裹类型
type Parcel = {
  id: string;
  trackingNumber: string;
  recipientName: string;
  recipientPhone: string;
  arrivalTime: string;
  pickupCode: string;
  status: '待取件' | '已通知' | '已取件';
};

// 驿站管理应用组件
const ParcelStationApp: React.FC = () => {
  const [parcels, setParcels] = useState<Parcel[]>([
    {
      id: '1',
      trackingNumber: 'YT123456789CN',
      recipientName: '张三',
      recipientPhone: '138****1234',
      arrivalTime: '2023-12-01 10:30',
      pickupCode: 'A123',
      status: '待取件'
    },
    {
      id: '2',
      trackingNumber: 'SF987654321CN',
      recipientName: '李四',
      recipientPhone: '139****5678',
      arrivalTime: '2023-12-01 11:15',
      pickupCode: 'B456',
      status: '已通知'
    },
    {
      id: '3',
      trackingNumber: 'JD555666777CN',
      recipientName: '王五',
      recipientPhone: '137****9012',
      arrivalTime: '2023-12-01 09:45',
      pickupCode: 'C789',
      status: '已取件'
    }
  ]);

  const [searchTerm, setSearchTerm] = useState('');

  const getStatusColor = (status: string) => {
    switch (status) {
      case '待取件': return '#f59e0b';
      case '已通知': return '#3b82f6';
      case '已取件': return '#10b981';
      default: return '#6b7280';
    }
  };

  const generatePickupCode = () => {
    const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    const numbers = '0123456789';
    let code = '';
    
    // 生成1个字母 + 3个数字的取件码
    code += letters.charAt(Math.floor(Math.random() * letters.length));
    for (let i = 0; i < 3; i++) {
      code += numbers.charAt(Math.floor(Math.random() * numbers.length));
    }
    
    return code;
  };

  const handleAddParcel = () => {
    Alert.prompt(
      '添加到站包裹',
      '请输入快递单号:',
      [
        { text: '取消', style: 'cancel' },
        {
          text: '确定',
          onPress: (trackingNumber) => {
            if (trackingNumber) {
              const newParcel: Parcel = {
                id: (parcels.length + 1).toString(),
                trackingNumber: trackingNumber,
                recipientName: '待填写',
                recipientPhone: '待填写',
                arrivalTime: new Date().toLocaleString('zh-CN'),
                pickupCode: generatePickupCode(),
                status: '待取件'
              };
              setParcels([...parcels, newParcel]);
              Alert.alert('成功', `包裹已添加,取件码: ${newParcel.pickupCode}`);
            }
          }
        }
      ],
      'plain-text'
    );
  };

  const handleNotifyRecipient = (parcelId: string) => {
    const parcel = parcels.find(p => p.id === parcelId);
    if (parcel) {
      Alert.alert(
        '通知收件人',
        `快递单号: ${parcel.trackingNumber}\n` +
        `收件人: ${parcel.recipientName}\n` +
        `取件码: ${parcel.pickupCode}\n\n` +
        `确定发送取件通知吗?`,
        [
          { text: '取消', style: 'cancel' },
          {
            text: '发送通知',
            onPress: () => {
              setParcels(parcels.map(p => 
                p.id === parcelId 
                  ? { ...p, status: '已通知' } 
                  : p
              ));
              Alert.alert('成功', '取件通知已发送');
            }
          }
        ]
      );
    }
  };

  const handleMarkPickedUp = (parcelId: string) => {
    const parcel = parcels.find(p => p.id === parcelId);
    if (parcel) {
      Alert.alert(
        '确认取件',
        `快递单号: ${parcel.trackingNumber}\n` +
        `取件码: ${parcel.pickupCode}\n\n` +
        `确认包裹已被取走吗?`,
        [
          { text: '取消', style: 'cancel' },
          {
            text: '确认取件',
            onPress: () => {
              setParcels(parcels.map(p => 
                p.id === parcelId 
                  ? { ...p, status: '已取件' } 
                  : p
              ));
              Alert.alert('成功', '包裹状态已更新为已取件');
            }
          }
        ]
      );
    }
  };

  const handleEditRecipient = (parcelId: string) => {
    const parcel = parcels.find(p => p.id === parcelId);
    if (parcel) {
      Alert.prompt(
        '编辑收件人信息',
        '请输入收件人姓名:',
        [
          { text: '取消', style: 'cancel' },
          {
            text: '下一步',
            onPress: (name) => {
              if (name) {
                Alert.prompt(
                  '编辑收件人信息',
                  '请输入收件人电话:',
                  [
                    { text: '取消', style: 'cancel' },
                    {
                      text: '确定',
                      onPress: (phone) => {
                        if (phone) {
                          setParcels(parcels.map(p => 
                            p.id === parcelId 
                              ? { ...p, recipientName: name, recipientPhone: phone } 
                              : p
                          ));
                          Alert.alert('成功', '收件人信息已更新');
                        }
                      }
                    }
                  ],
                  'plain-text',
                  parcel.recipientPhone
                );
              }
            }
          }
        ],
        'plain-text',
        parcel.recipientName
      );
    }
  };

  const filteredParcels = parcels.filter(parcel => 
    parcel.trackingNumber.includes(searchTerm) || 
    parcel.recipientName.includes(searchTerm) ||
    parcel.pickupCode.includes(searchTerm)
  );

  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.searchCard}>
          <View style={styles.searchContainer}>
            <Text style={styles.searchIcon}>🔍</Text>
            <TextInput
              style={styles.searchInput}
              placeholder="搜索单号、姓名或取件码"
              value={searchTerm}
              onChangeText={setSearchTerm}
            />
          </View>
          
          <TouchableOpacity 
            style={styles.addButton}
            onPress={handleAddParcel}
          >
            <Text style={styles.addButtonText}>+ 添加包裹</Text>
          </TouchableOpacity>
        </View>

        {/* 包裹统计 */}
        <View style={styles.statsCard}>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>
              {parcels.filter(p => p.status === '待取件').length}
            </Text>
            <Text style={styles.statLabel}>待取件</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>
              {parcels.filter(p => p.status === '已通知').length}
            </Text>
            <Text style={styles.statLabel}>已通知</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>
              {parcels.filter(p => p.status === '已取件').length}
            </Text>
            <Text style={styles.statLabel}>已取件</Text>
          </View>
        </View>

        {/* 包裹列表 */}
        <View style={styles.parcelsCard}>
          <Text style={styles.sectionTitle}>到站包裹</Text>
          
          {filteredParcels.map(parcel => (
            <View key={parcel.id} style={styles.parcelItem}>
              <View style={styles.parcelHeader}>
                <Text style={styles.trackingNumber}>{parcel.trackingNumber}</Text>
                <View style={[
                  styles.statusBadge,
                  { backgroundColor: getStatusColor(parcel.status) }
                ]}>
                  <Text style={styles.statusText}>{parcel.status}</Text>
                </View>
              </View>
              
              <View style={styles.parcelDetails}>
                <View style={styles.detailRow}>
                  <Text style={styles.detailLabel}>收件人:</Text>
                  <Text style={styles.detailValue}>{parcel.recipientName}</Text>
                </View>
                <View style={styles.detailRow}>
                  <Text style={styles.detailLabel}>电话:</Text>
                  <Text style={styles.detailValue}>{parcel.recipientPhone}</Text>
                </View>
                <View style={styles.detailRow}>
                  <Text style={styles.detailLabel}>到站时间:</Text>
                  <Text style={styles.detailValue}>{parcel.arrivalTime}</Text>
                </View>
                <View style={styles.detailRow}>
                  <Text style={styles.detailLabel}>取件码:</Text>
                  <Text style={styles.pickupCode}>{parcel.pickupCode}</Text>
                </View>
              </View>
              
              <View style={styles.parcelActions}>
                {parcel.status === '待取件' && (
                  <>
                    <TouchableOpacity 
                      style={styles.editButton}
                      onPress={() => handleEditRecipient(parcel.id)}
                    >
                      <Text style={styles.editButtonText}>编辑信息</Text>
                    </TouchableOpacity>
                    <TouchableOpacity 
                      style={styles.notifyButton}
                      onPress={() => handleNotifyRecipient(parcel.id)}
                    >
                      <Text style={styles.notifyButtonText}>发送通知</Text>
                    </TouchableOpacity>
                  </>
                )}
                {parcel.status === '已通知' && (
                  <TouchableOpacity 
                    style={styles.pickupButton}
                    onPress={() => handleMarkPickedUp(parcel.id)}
                  >
                    <Text style={styles.pickupButtonText}>标记取件</Text>
                  </TouchableOpacity>
                )}
              </View>
            </View>
          ))}
        </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>
      </ScrollView>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>📦</Text>
          <Text style={styles.navText}>包裹</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>🔔</Text>
          <Text style={styles.navText}>通知</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>📊</Text>
          <Text style={styles.navText}>统计</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.navItem, styles.activeNavItem]}>
          <Text style={styles.navIcon}>👤</Text>
          <Text style={styles.navText}>我的</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fefce8',
  },
  header: {
    flexDirection: 'column',
    padding: 16,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#fef08a',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#854d0e',
    marginBottom: 4,
  },
  subtitle: {
    fontSize: 14,
    color: '#ca8a04',
  },
  content: {
    flex: 1,
    marginTop: 12,
  },
  searchCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  searchContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#fefce8',
    borderRadius: 8,
    paddingHorizontal: 12,
    marginBottom: 12,
  },
  searchIcon: {
    fontSize: 16,
    color: '#a16207',
    marginRight: 8,
  },
  searchInput: {
    flex: 1,
    paddingVertical: 12,
    fontSize: 14,
    color: '#854d0e',
  },
  addButton: {
    backgroundColor: '#ca8a04',
    paddingVertical: 14,
    borderRadius: 8,
    alignItems: 'center',
  },
  addButtonText: {
    color: '#ffffff',
    fontSize: 16,
    fontWeight: '600',
  },
  statsCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    flexDirection: 'row',
    justifyContent: 'space-around',
  },
  statItem: {
    alignItems: 'center',
  },
  statNumber: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#ca8a04',
  },
  statLabel: {
    fontSize: 12,
    color: '#64748b',
    marginTop: 4,
  },
  parcelsCard: {
    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: '#854d0e',
    marginBottom: 12,
  },
  parcelItem: {
    padding: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#fef08a',
  },
  parcelHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
  },
  trackingNumber: {
    fontSize: 16,
    fontWeight: '600',
    color: '#854d0e',
  },
  statusBadge: {
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 12,
  },
  statusText: {
    fontSize: 12,
    color: '#ffffff',
    fontWeight: '500',
  },
  parcelDetails: {
    marginBottom: 12,
  },
  detailRow: {
    flexDirection: 'row',
    marginBottom: 4,
  },
  detailLabel: {
    fontSize: 12,
    color: '#64748b',
    width: 70,
  },
  detailValue: {
    fontSize: 12,
    color: '#854d0e',
    flex: 1,
  },
  pickupCode: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#ca8a04',
  },
  parcelActions: {
    flexDirection: 'row',
    justifyContent: 'flex-end',
    flexWrap: 'wrap',
  },
  editButton: {
    backgroundColor: '#fde047',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 16,
    marginRight: 8,
    marginBottom: 4,
  },
  editButtonText: {
    color: '#854d0e',
    fontSize: 12,
  },
  notifyButton: {
    backgroundColor: '#3b82f6',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 16,
    marginRight: 8,
    marginBottom: 4,
  },
  notifyButtonText: {
    color: '#ffffff',
    fontSize: 12,
  },
  pickupButton: {
    backgroundColor: '#10b981',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 16,
    marginBottom: 4,
  },
  pickupButtonText: {
    color: '#ffffff',
    fontSize: 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,
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#fef08a',
    paddingVertical: 12,
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  activeNavItem: {
    paddingTop: 4,
    borderTopWidth: 2,
    borderTopColor: '#ca8a04',
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  activeNavIcon: {
    color: '#ca8a04',
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  activeNavText: {
    color: '#ca8a04',
    fontWeight: '500',
  },
});

export default ParcelStationApp;


请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文介绍了基于React Native开发的驿站包裹管理应用如何适配鸿蒙系统。应用采用React函数式组件+TypeScript技术栈,通过强类型数据模型确保跨端一致性,使用useState轻量级状态管理实现多端状态同步。UI层深度复刻鸿蒙设计规范,采用Flex布局适配不同设备尺寸,并实现鸿蒙风格的视觉样式和交互组件。核心业务逻辑如取件码生成、状态流转和搜索筛选均采用纯JavaScript实现,天然具备跨端能力。这种技术方案为React Native应用对接鸿蒙系统提供了实践参考,有效降低了跨平台开发成本。

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

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐