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


物流追踪是电商履约和快递服务的核心场景,高效的包裹追踪系统需要兼顾实时性、可视化、跨端适配三大核心特性。本文将深度拆解基于 React Native 开发的包裹追踪应用,剖析其物流轨迹可视化、状态管理、交互设计的技术实现思路,并提供完整的鸿蒙(HarmonyOS)ArkTS 跨端适配方案,为物流类应用的跨端开发提供可落地的技术参考。

1. 贴合物流场景

包裹追踪场景的核心诉求是物流节点管理、状态可视化、轨迹展示,代码通过 TypeScript 类型系统构建了精准贴合物流追踪业务的领域模型:

// 物流节点类型
type LogisticsNode = {
  id: string;
  status: string;
  time: string;
  location: string;
  description: string;
};

模型设计的物流场景适配性分析

  • 物流节点全维度覆盖:包含状态、时间、位置、描述四大核心要素,完整还原快递从揽收到签收的全生命周期节点信息;
  • 唯一标识设计:每个物流节点配备唯一 id,为轨迹渲染和状态管理提供精准标识;
  • 状态开放性设计status 字段未做严格枚举限制(实际业务中可扩展为联合类型),兼容不同快递公司的状态体系;
  • 时间格式标准化:统一的时间字符串格式,保证物流轨迹的时间轴展示一致性;
  • 位置信息结构化:单独的 location 字段存储节点位置,便于后续地图可视化扩展;
  • 描述信息补充description 字段提供节点的详细说明,提升物流信息的可读性。

同时,包裹基础信息采用对象字面量形式管理,包含运单号、收发件地址、当前状态、预计送达时间等核心属性,形成"基础信息 + 轨迹节点"的双层数据结构,完美匹配物流追踪的业务形态。

2. 物流追踪

包裹追踪系统的状态架构围绕查询单号、包裹基础信息、物流节点列表三层核心状态构建,通过 React 的 useState 实现从单号输入到轨迹展示的全流程状态管控:

// 查询单号状态
const [trackingNumber, setTrackingNumber] = useState('');

// 包裹基础信息状态
const [packageInfo, setPackageInfo] = useState({/* 包裹基础信息 */});

// 物流节点列表状态
const [logisticsNodes, setLogisticsNodes] = useState<LogisticsNode[]>([/* 初始物流节点 */]);

状态管理的物流特性适配

  • 查询驱动更新:以 trackingNumber 为核心驱动源,触发包裹信息和物流节点的联动更新,符合用户"输入单号-查询轨迹"的操作习惯;
  • 数据懒加载逻辑:仅在用户输入单号并点击查询后才更新完整的物流数据,减少初始渲染开销;
  • 状态联动设计:查询操作同时更新包裹基础信息和物流节点列表,保证数据的一致性;
  • 模拟异步请求:通过 setTimeout 模拟真实的物流 API 调用,保留异步请求的状态处理逻辑,便于后续对接真实接口;
  • 空状态处理:包裹信息和物流节点的展示依赖 trackingNumber 的非空判断,避免空数据渲染导致的界面异常;
  • 不可变更新策略:状态更新均采用全新对象/数组替换,保证 React 渲染机制的正常触发,确保物流信息的实时更新。

3. 物流追踪的全流程

包裹追踪系统的核心价值在于单号校验-异步查询-轨迹渲染的闭环能力,代码通过轻量化但专业的业务逻辑实现了物流追踪的核心交互:

(1)单号查询

单号查询是物流追踪的入口,代码实现了严谨的输入校验和异步数据更新:

const handleTrackPackage = () => {
  if (!trackingNumber.trim()) {
    Alert.alert('提示', '请输入快递单号');
    return;
  }

  // 模拟查询结果
  setTimeout(() => {
    // 更新包裹基础信息
    setPackageInfo({/* 新包裹信息 */});
    
    // 更新物流节点列表
    setLogisticsNodes([/* 新物流节点 */]);
    
    Alert.alert('成功', '包裹信息查询成功');
  }, 1000);
};

查询逻辑的物流适配性

  • 输入校验前置:查询前校验单号非空,避免无效的 API 调用,符合物流查询的基本交互规范;
  • 异步模拟真实:1秒延迟模拟网络请求,让用户感知查询过程,符合真实物流查询的交互体验;
  • 数据批量更新:一次查询同时更新包裹基础信息和物流轨迹,保证数据的完整性和一致性;
  • 操作反馈明确:查询成功后给出弹窗提示,让用户确认操作结果;
  • 数据增量扩展:模拟数据中物流节点从3个扩展到5个,模拟真实物流查询的完整轨迹展示;
  • 单号关联绑定:查询结果中的包裹信息自动关联输入的单号,保证数据的准确性。
(2)状态

物流状态的可视化是提升用户体验的关键,代码通过状态-色彩映射实现了直观的视觉区分:

const getStatusColor = (status: string) => {
  switch (status) {
    case '已揽收': return '#3b82f6';
    case '运输中': return '#f59e0b';
    case '派送中': return '#8b5cf6';
    case '已签收': return '#10b981';
    default: return '#6b7280';
  }
};

色彩映射的物流语义化设计

  • 已揽收(蓝色):代表物流流程的开始,蓝色传递稳定、可靠的视觉感受;
  • 运输中(黄色):代表物流在途状态,黄色传递提醒、注意的视觉感受;
  • 派送中(紫色):代表即将完成的状态,紫色传递特殊、重要的视觉感受;
  • 已签收(绿色):代表物流流程的完成,绿色传递成功、完成的视觉感受;
  • 默认色(灰色):兼容异常状态,保证界面的鲁棒性;
  • 色彩体系统一:状态色彩在包裹信息卡片和物流轨迹中保持一致,强化用户认知。

4.物流追踪

包裹追踪系统的视觉设计围绕时间轴可视化、状态语义化、操作轻量化三个核心维度展开,贴合物流追踪的用户习惯:

(1)物流轨迹时间

时间轴是物流追踪的核心视觉元素,代码通过精准的布局和样式设计实现了专业的轨迹展示:

<View style={styles.timeline}>
  {logisticsNodes.map((node, index) => (
    <View key={node.id} style={styles.timelineItem}>
      <View style={styles.timelineDotContainer}>
        <View style={[
          styles.timelineDot,
          index === logisticsNodes.length - 1 && styles.currentDot
        ]} />
        {index < logisticsNodes.length - 1 && (
          <View style={styles.timelineLine} />
        )}
      </View>
      
      <View style={styles.timelineContent}>
        {/* 物流节点内容 */}
      </View>
    </View>
  ))}
</View>

时间轴设计的物流适配性

  • 节点可视化:圆点 + 连线的组合方式,直观展示物流节点的先后顺序;
  • 当前节点突出:最新节点使用更大的蓝色圆点和加粗文字,突出当前物流状态;
  • 连线动态渲染:仅在非最后一个节点显示连线,避免视觉冗余;
  • 信息层级清晰:状态(重要)→ 时间(次要)→ 位置/描述(辅助)的信息层级,符合物流信息的阅读习惯;
  • 响应式布局:时间轴容器适配不同屏幕尺寸,保证小屏设备的轨迹展示效果;
  • 间距标准化:统一的节点间距和内边距,保证时间轴的视觉一致性。
(2)物流场景化
  • 主色调适配:采用蓝色系(#2563eb)为主色调,契合物流行业的专业、可靠的品牌属性;
  • 卡片式布局:所有功能模块采用卡片式设计,区分查询区、包裹信息区、轨迹区,提升界面的层次感;
  • 状态徽章设计:包裹当前状态采用彩色徽章展示,强化视觉识别;
  • 输入体验优化:搜索框采用浅蓝背景,与主色调形成呼应,提升输入体验;
  • 底部导航适配:按追踪、物流、扫码、我的分类,贴合物流追踪的核心功能场景;
  • 空状态处理:仅在输入单号并查询后展示包裹信息和轨迹,避免初始界面的信息过载;
  • 阴影效果:卡片添加轻微阴影,提升界面的立体感和层次感;
  • 圆角设计:统一的12px圆角,符合移动端物流应用的视觉设计趋势;
  • 字体层级:标题(16px)→ 正文(14px)→ 辅助文字(12px)的字体层级,保证信息的可读性。
(3)交互
  • 输入即时响应:输入框实时绑定单号状态,保证查询时的数据准确性;
  • 查询按钮状态:按钮采用高对比度设计,提升可点击性;
  • 操作反馈及时:查询成功/失败均有弹窗提示,让用户明确操作结果;
  • 轨迹滚动优化:包裹轨迹区域支持滚动,适配长轨迹的展示需求;
  • 信息折叠合理:物流节点描述自动换行,保证信息完整性的同时不破坏布局;
  • 底部导航固定:导航栏固定在底部,保证核心功能的快速访问;
  • 安全区域适配:使用 SafeAreaView 适配异形屏,保证界面的完整性;
  • 键盘适配:输入单号时自动适配键盘弹出,避免界面被遮挡。

将 React Native 包裹追踪系统迁移至鸿蒙平台,核心是基于 ArkTS + ArkUI 实现类型系统、状态管理、业务逻辑、视觉交互的对等还原,同时适配鸿蒙的组件特性和布局范式,保证物流追踪体验的专业性和实时性。

1. 架构

鸿蒙端适配遵循类型复用、逻辑对等、体验统一的原则,物流追踪的核心业务逻辑和视觉规范 100% 复用,仅需适配平台特有 API 和组件语法:

@Entry
@Component
struct PackageTrackingApp {
  // 类型定义:对等实现 TypeScript 类型 → 接口定义
  interface LogisticsNode {
    id: string;
    status: string;
    time: string;
    location: string;
    description: string;
  }

  // 状态管理:对等实现 useState → @State
  @State trackingNumber: string = '';
  @State packageInfo: {
    trackingNumber: string;
    sender: string;
    recipient: string;
    estimatedDelivery: string;
    status: string;
    currentLocation: string;
  } = {/* 初始包裹信息 */};
  @State logisticsNodes: LogisticsNode[] = [/* 初始物流节点 */];

  // 业务逻辑:完全复用 RN 端实现
  getStatusColor(status: string): string {/* 状态色彩映射 */}
  handleTrackPackage(): void {/* 单号查询 */}

  // 页面构建:镜像 RN 端布局结构
  build() {
    Column() {
      // 头部区域
      // 查询输入框
      // 包裹基础信息
      // 物流轨迹
      // 服务说明
      // 底部导航
    }
  }
}

React Native 特性 鸿蒙 ArkUI 对应实现 物流追踪适配关键说明
TypeScript 类型定义 TypeScript 接口定义 物流节点类型完全复用,保证数据结构一致性
useState @State 装饰器 单号、包裹信息、轨迹节点的状态管理逻辑完全复用
TouchableOpacity Button/Column + onClick 查询按钮、导航项等可点击区域通过 onClick 事件实现
Alert.alert AlertDialog.show 查询提示、错误提示等交互逻辑对等,符合物流查询习惯
StyleSheet 链式样式 蓝色系主色调、状态色彩映射等视觉规范 100% 复用
Array.map ForEach 组件 物流轨迹渲染逻辑一致,保证时间轴展示效果
ScrollView Scroll 组件 滚动容器语法差异,功能一致,适配长轨迹展示
TextInput TextInput 组件 单号输入框属性基本一致,仅键盘类型适配
绝对定位 position: Position.Fixed 底部导航定位语法差异,效果一致
时间轴布局 Row + Column 组合 圆点+连线的时间轴布局逻辑一致,仅组件嵌套方式差异
状态徽章 Column + 背景色 状态色彩映射逻辑一致,保证视觉识别性
异步查询模拟 setTimeout 复用 1秒延迟模拟网络请求的逻辑完全复用
空状态判断 条件渲染 包裹信息和轨迹的展示条件完全复用

3. 鸿蒙代码

// 鸿蒙 ArkTS 完整实现
@Entry
@Component
struct PackageTrackingApp {
  // 物流节点类型定义
  interface LogisticsNode {
    id: string;
    status: string;
    time: string;
    location: string;
    description: string;
  }

  // 状态管理
  @State trackingNumber: string = '';
  @State packageInfo: {
    trackingNumber: string;
    sender: string;
    recipient: string;
    estimatedDelivery: string;
    status: string;
    currentLocation: string;
  } = {
    trackingNumber: '',
    sender: '北京仓库',
    recipient: '上海客户',
    estimatedDelivery: '2023-12-05 18:00',
    status: '运输中',
    currentLocation: '南京转运中心'
  };
  @State logisticsNodes: LogisticsNode[] = [
    {
      id: '1',
      status: '已揽收',
      time: '2023-12-01 09:30',
      location: '北京朝阳区网点',
      description: '快件已被揽收'
    },
    {
      id: '2',
      status: '运输中',
      time: '2023-12-01 15:20',
      location: '北京转运中心',
      description: '快件正在发往下一个转运中心'
    },
    {
      id: '3',
      status: '运输中',
      time: '2023-12-02 08:45',
      location: '南京转运中心',
      description: '快件正在南京转运中心处理'
    }
  ];

  // 获取状态颜色
  getStatusColor(status: string): string {
    switch (status) {
      case '已揽收': return '#3b82f6';
      case '运输中': return '#f59e0b';
      case '派送中': return '#8b5cf6';
      case '已签收': return '#10b981';
      default: return '#6b7280';
    }
  }

  // 处理包裹查询
  handleTrackPackage(): void {
    if (!this.trackingNumber.trim()) {
      AlertDialog.show({
        title: '提示',
        message: '请输入快递单号',
        confirm: { value: '确定' }
      });
      return;
    }

    // 模拟异步查询
    setTimeout(() => {
      // 更新包裹信息
      this.packageInfo = {
        trackingNumber: this.trackingNumber,
        sender: '北京仓库',
        recipient: '上海客户',
        estimatedDelivery: '2023-12-05 18:00',
        status: '运输中',
        currentLocation: '南京转运中心'
      };

      // 更新物流节点
      this.logisticsNodes = [
        {
          id: '1',
          status: '已揽收',
          time: '2023-12-01 09:30',
          location: '北京朝阳区网点',
          description: '快件已被揽收'
        },
        {
          id: '2',
          status: '运输中',
          time: '2023-12-01 15:20',
          location: '北京转运中心',
          description: '快件正在发往下一个转运中心'
        },
        {
          id: '3',
          status: '运输中',
          time: '2023-12-02 08:45',
          location: '南京转运中心',
          description: '快件正在南京转运中心处理'
        },
        {
          id: '4',
          status: '运输中',
          time: '2023-12-03 14:30',
          location: '上海转运中心',
          description: '快件已到达上海转运中心'
        },
        {
          id: '5',
          status: '派送中',
          time: '2023-12-04 09:15',
          location: '上海浦东新区网点',
          description: '快件正在派送中'
        }
      ];

      // 查询成功提示
      AlertDialog.show({
        title: '成功',
        message: '包裹信息查询成功',
        confirm: { value: '确定' }
      });
    }, 1000);
  }

  build() {
    Column()
      .flex(1)
      .backgroundColor('#eff6ff')
      .safeArea(true) {
      
      // 头部区域
      Column()
        .padding(16)
        .backgroundColor('#ffffff')
        .borderBottom({ width: 1, color: '#dbeafe' }) {
        Text('包裹追踪')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1e3a8a')
          .marginBottom(4);
        
        Text('实时查询物流信息')
          .fontSize(14)
          .fontColor('#2563eb');
      }

      // 滚动内容区
      Scroll()
        .flex(1)
        .marginTop(12) {
        Column() {
          // 查询输入框卡片
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }) {
            
            Text('输入快递单号')
              .fontSize(16)
              .fontWeight(FontWeight.SemiBold)
              .fontColor('#1e3a8a')
              .marginBottom(12);
            
            // 输入框容器
            Row()
              .alignItems(ItemAlign.Center)
              .backgroundColor('#eff6ff')
              .borderRadius(8)
              .paddingHorizontal(12)
              .marginBottom(12) {
              Text('🔍')
                .fontSize(16)
                .fontColor('#64748b')
                .marginRight(8);
              
              TextInput({
                placeholder: '请输入快递单号',
                text: this.trackingNumber
              })
                .onChange((value) => {
                  this.trackingNumber = value;
                })
                .flex(1)
                .paddingVertical(12)
                .fontSize(14)
                .fontColor('#1e3a8a');
            }
            
            // 查询按钮
            Button()
              .backgroundColor('#2563eb')
              .paddingVertical(14)
              .borderRadius(8)
              .width('100%')
              .onClick(() => this.handleTrackPackage()) {
              Text('查询包裹')
                .fontColor('#ffffff')
                .fontSize(16)
                .fontWeight(FontWeight.SemiBold);
            }
          }

          // 包裹基本信息卡片
          if (this.packageInfo.trackingNumber) {
            Column()
              .backgroundColor('#ffffff')
              .marginLeft(16)
              .marginRight(16)
              .marginBottom(12)
              .borderRadius(12)
              .padding(16)
              .shadow({ color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }) {
              
              Text('包裹信息')
                .fontSize(16)
                .fontWeight(FontWeight.SemiBold)
                .fontColor('#1e3a8a')
                .marginBottom(12);
              
              // 包裹信息行
              Column() {
                // 运单号
                Row()
                  .alignItems(ItemAlign.Center)
                  .marginBottom(8) {
                  Text('运单号:')
                    .fontSize(14)
                    .fontColor('#64748b')
                    .width(80);
                  
                  Text(this.packageInfo.trackingNumber)
                    .fontSize(14)
                    .fontColor('#1e3a8a')
                    .flex(1);
                }
                
                // 发件地
                Row()
                  .alignItems(ItemAlign.Center)
                  .marginBottom(8) {
                  Text('发件地:')
                    .fontSize(14)
                    .fontColor('#64748b')
                    .width(80);
                  
                  Text(this.packageInfo.sender)
                    .fontSize(14)
                    .fontColor('#1e3a8a')
                    .flex(1);
                }
                
                // 收件地
                Row()
                  .alignItems(ItemAlign.Center)
                  .marginBottom(8) {
                  Text('收件地:')
                    .fontSize(14)
                    .fontColor('#64748b')
                    .width(80);
                  
                  Text(this.packageInfo.recipient)
                    .fontSize(14)
                    .fontColor('#1e3a8a')
                    .flex(1);
                }
                
                // 当前状态
                Row()
                  .alignItems(ItemAlign.Center)
                  .marginBottom(8) {
                  Text('当前状态:')
                    .fontSize(14)
                    .fontColor('#64748b')
                    .width(80);
                  
                  Column()
                    .paddingHorizontal(8)
                    .paddingVertical(4)
                    .borderRadius(12)
                    .backgroundColor(this.getStatusColor(this.packageInfo.status)) {
                    Text(this.packageInfo.status)
                      .fontSize(12)
                      .fontColor('#ffffff')
                      .fontWeight(FontWeight.Medium);
                  }
                }
                
                // 当前位置
                Row()
                  .alignItems(ItemAlign.Center)
                  .marginBottom(8) {
                  Text('当前位置:')
                    .fontSize(14)
                    .fontColor('#64748b')
                    .width(80);
                  
                  Text(this.packageInfo.currentLocation)
                    .fontSize(14)
                    .fontColor('#1e3a8a')
                    .flex(1);
                }
                
                // 预计送达
                Row()
                  .alignItems(ItemAlign.Center)
                  .marginBottom(8) {
                  Text('预计送达:')
                    .fontSize(14)
                    .fontColor('#64748b')
                    .width(80);
                  
                  Text(this.packageInfo.estimatedDelivery)
                    .fontSize(14)
                    .fontColor('#1e3a8a')
                    .flex(1);
                }
              }
            }
          }

          // 物流轨迹卡片
          if (this.logisticsNodes.length > 0) {
            Column()
              .backgroundColor('#ffffff')
              .marginLeft(16)
              .marginRight(16)
              .marginBottom(12)
              .borderRadius(12)
              .padding(16)
              .shadow({ color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }) {
              
              Text('物流轨迹')
                .fontSize(16)
                .fontWeight(FontWeight.SemiBold)
                .fontColor('#1e3a8a')
                .marginBottom(12);
              
              // 时间轴容器
              Column() {
                ForEach(this.logisticsNodes, (node: LogisticsNode, index: number) => {
                  Row()
                    .marginBottom(16) {
                    
                    // 时间轴圆点和连线
                    Column()
                      .alignItems(ItemAlign.Center)
                      .marginRight(12) {
                      // 圆点
                      Column()
                        .width(index === this.logisticsNodes.length - 1 ? 16 : 12)
                        .height(index === this.logisticsNodes.length - 1 ? 16 : 12)
                        .borderRadius(index === this.logisticsNodes.length - 1 ? 8 : 6)
                        .backgroundColor(index === this.logisticsNodes.length - 1 ? '#2563eb' : '#d1d5db');
                      
                      // 连线(非最后一个节点显示)
                      if (index < this.logisticsNodes.length - 1) {
                        Column()
                          .width(2)
                          .height(40)
                          .backgroundColor('#d1d5db');
                      }
                    }
                    
                    // 时间轴内容
                    Column()
                      .flex(1) {
                      // 节点头部
                      Row()
                        .justifyContent(FlexAlign.SpaceBetween)
                        .alignItems(ItemAlign.Center)
                        .marginBottom(4) {
                        Text(node.status)
                          .fontSize(14)
                          .fontWeight(index === this.logisticsNodes.length - 1 ? FontWeight.SemiBold : FontWeight.Medium)
                          .fontColor(index === this.logisticsNodes.length - 1 ? '#2563eb' : '#64748b');
                        
                        Text(node.time)
                          .fontSize(12)
                          .fontColor('#94a3b8');
                      }
                      
                      // 位置
                      Text(node.location)
                        .fontSize(13)
                        .fontColor('#1e3a8a')
                        .marginBottom(2);
                      
                      // 描述
                      Text(node.description)
                        .fontSize(12)
                        .fontColor('#64748b');
                    }
                  }
                })
              }
            }
          }

          // 服务说明卡片
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(80)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }) {
            
            Text('服务说明')
              .fontSize(16)
              .fontWeight(FontWeight.SemiBold)
              .fontColor('#1e3a8a')
              .marginBottom(12);
            
            Text('• 支持国内外主要快递公司单号查询')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 实时更新包裹运输状态')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 提供预计送达时间')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 异常情况及时通知')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20);
          }
        }
      }

      // 底部导航
      Row()
        .justifyContent(FlexAlign.SpaceAround)
        .backgroundColor('#ffffff')
        .borderTop({ width: 1, color: '#dbeafe' })
        .paddingVertical(12)
        .position(Position.Fixed)
        .bottom(0)
        .width('100%') {
      
      // 追踪
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1) {
        Text('📦')
          .fontSize(20)
          .fontColor('#94a3b8')
          .marginBottom(4);
        
        Text('追踪')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
      
      // 物流
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1) {
        Text('🚚')
          .fontSize(20)
          .fontColor('#94a3b8')
          .marginBottom(4);
        
        Text('物流')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
      
      // 扫码
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1) {
        Text('📱')
          .fontSize(20)
          .fontColor('#94a3b8')
          .marginBottom(4);
        
        Text('扫码')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
      
      // 我的(激活状态)
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1)
        .paddingTop(4)
        .borderTop({ width: 2, color: '#2563eb' }) {
        Text('👤')
          .fontSize(20)
          .fontColor('#2563eb')
          .marginBottom(4);
        
        Text('我的')
          .fontSize(12)
          .fontColor('#2563eb')
          .fontWeight(FontWeight.Medium);
      }
    }
  }
}

1. 适配

  • 物流领域模型完全复用:物流节点的类型定义在两端保持一致,包含状态、时间、位置、描述等物流核心属性,保证业务逻辑的准确性;
  • 视觉规范100%对齐:蓝色系主色调、状态色彩映射、时间轴样式、卡片布局等视觉属性完全复用,符合物流追踪的用户视觉认知;
  • 物流业务流程统一:单号查询、轨迹展示、状态识别等核心业务流程保持一致的规则,符合物流追踪的行业惯例;
  • 时间轴渲染逻辑对等:圆点+连线的时间轴渲染算法在两端使用相同的规则,保证物流轨迹展示的一致性;
  • 交互体验一致:输入校验、查询反馈、轨迹滚动等交互逻辑保持相同的用户体验,符合物流查询的操作习惯;
  • 语义化设计统一:状态色彩、时间轴样式等语义化设计保持一致,提升物流信息的可读性;
  • 平台特性兼容:弹窗展示、输入框交互等平台特有 API 做适配处理,保证物流追踪功能的可用性;
  • 性能优化适配:鸿蒙端利用 ForEach 组件的复用机制优化轨迹渲染性能,RN 端利用数组映射实现高效渲染。

2. 包裹追踪系

  • 多快递公司适配:接入顺丰、中通、圆通、EMS 等主流快递公司的 API,实现自动识别快递公司;
  • 扫码查询功能:集成扫码组件,支持扫描快递面单二维码自动填充单号;
  • 地图可视化:结合地图组件,展示包裹的实时位置和运输路线;
  • 异常物流提醒:监控物流节点,对长时间未更新、滞留等异常状态进行提醒;
  • 多语言支持:适配中英文等多语言,支持国际快递查询;
  • 历史查询记录:保存用户的查询历史,支持快速重新查询;
  • 批量查询功能:支持同时查询多个快递单号,批量展示物流信息;
  • 物流预测分析:基于历史数据,优化预计送达时间的准确性;
  • 推送通知:支持物流状态变更的推送提醒;
  • 快递客服集成:对接快递公司客服,提供一键咨询功能;
  • 电子面单生成:支持在线生成电子面单,打印发货;
  • 物流费用计算:集成运费计算功能,支持不同快递公司的运费对比。

包裹追踪系统作为物流服务的核心工具,其跨端适配的关键在于领域模型的物流专业性、轨迹展示的可视化效果、交互体验的实时性。这份 React Native 实现的包裹追踪组件,通过强类型领域建模、精细化状态管理、物流场景化视觉设计,构建了高效的移动端物流追踪体验;而鸿蒙 ArkTS 端的适配实现,则验证了跨端开发中"逻辑复用、体验统一"的核心原则。

  1. 包裹追踪系统的类型设计需贴合物流场景,覆盖物流节点的状态、时间、位置、描述等核心属性;
  2. 时间轴可视化是物流轨迹展示的核心,需保证当前节点突出、连线逻辑准确、信息层级清晰;
  3. 跨端适配的核心是物流业务逻辑复用 + 平台特性适配,单号查询、状态映射等核心逻辑无需重写;
  4. 状态色彩的语义化设计能显著提升物流状态的识别效率,不同平台需保持相同的色彩映射规则;
  5. 异步查询的模拟实现保留了对接真实物流 API 的扩展能力,是跨端适配的重要设计考量;
  6. 输入校验和操作反馈是提升物流查询体验的关键技术手段,需在跨端适配中完整保留;
  7. 物流场景化的视觉设计(蓝色系主色调、卡片式布局)是提升追踪效率的核心体验要素。

Hooks应用

PackageTrackingApp组件充分利用了React Hooks的优势,使用useState钩子管理多个状态变量:

const [trackingNumber, setTrackingNumber] = useState('');
const [packageInfo, setPackageInfo] = useState({
  trackingNumber: '',
  sender: '北京仓库',
  recipient: '上海客户',
  estimatedDelivery: '2023-12-05 18:00',
  status: '运输中',
  currentLocation: '南京转运中心'
});

const [logisticsNodes, setLogisticsNodes] = useState<LogisticsNode[]>([
  // 物流节点数据
]);

这种状态管理方式相比传统的类组件更为简洁,代码可读性更高,同时也更符合React的函数式编程理念。通过TypeScript类型定义,确保了数据结构的一致性和类型安全。

TypeScript类型

PackageTrackingApp组件使用了TypeScript的类型定义功能,为物流节点定义了明确的类型:

// 物流节点类型
type LogisticsNode = {
  id: string;
  status: string;
  time: string;
  location: string;
  description: string;
};

这种类型定义方式提高了代码的可读性和可维护性,减少了运行时错误的可能性。

UI

应用使用了React Native的核心UI组件构建界面:

  • SafeAreaView:确保内容在刘海屏等异形屏设备上正常显示。

  • ScrollView:实现内容的垂直滚动,适应不同屏幕尺寸。

  • TouchableOpacity:实现可点击的交互元素,如查询包裹按钮。

  • TextInput:实现文本输入功能,用于输入快递单号。

  • View:作为布局容器,组织界面结构。

  • Text:显示文本内容,如标题、标签和提示信息。

布局方面,应用采用了Flexbox布局系统,通过样式定义实现响应式设计。例如,查询输入框使用了水平布局,包裹信息和物流节点使用了垂直布局。

事件处理

应用实现了多种用户交互功能:

  • 查询包裹:通过TouchableOpacity的onPress属性处理用户点击,调用handleTrackPackage函数执行包裹查询操作。

  • 输入快递单号:通过TextInput的onChangeText属性实时更新快递单号状态,实现表单数据的双向绑定。

  • 警告提示:使用Alert组件显示操作结果和错误提示,提供清晰的用户反馈。

数据模拟

应用实现了数据模拟和处理逻辑:

const handleTrackPackage = () => {
  if (!trackingNumber.trim()) {
    Alert.alert('提示', '请输入快递单号');
    return;
  }

  // 模拟查询结果
  setTimeout(() => {
    setPackageInfo({
      trackingNumber: trackingNumber,
      sender: '北京仓库',
      recipient: '上海客户',
      estimatedDelivery: '2023-12-05 18:00',
      status: '运输中',
      currentLocation: '南京转运中心'
    });

    setLogisticsNodes([
      // 物流节点数据
    ]);

    Alert.alert('成功', '包裹信息查询成功');
  }, 1000);
};

这种实现方式使用了setTimeout模拟网络请求的延迟,然后更新包裹信息和物流节点数据,确保了包裹查询过程的完整性和可靠性。


组件

在跨端开发中,React Native组件与鸿蒙平台的兼容性是关键考虑因素。PackageTrackingApp组件使用的核心UI组件在鸿蒙平台上都有对应的实现:

  • SafeAreaView:在鸿蒙平台上可以使用类似的安全区域组件。

  • ScrollView:鸿蒙平台提供了滚动视图组件。

  • TouchableOpacity:鸿蒙平台有对应的可点击组件。

  • TextInput:鸿蒙平台支持文本输入功能。

  • ViewText:鸿蒙平台提供了基础的布局和文本组件。

API差异

React Native与鸿蒙平台在API层面存在一些差异,需要注意以下几点:

  1. Dimensions API:应用使用Dimensions.get(‘window’)获取屏幕尺寸,在鸿蒙平台上需要使用相应的API获取屏幕信息。

  2. Alert API:React Native的Alert组件在鸿蒙平台上可能有不同的实现方式,需要进行适配。

  3. setTimeout API:应用使用setTimeout模拟网络请求的延迟,在鸿蒙平台上需要使用相应的API实现延迟操作。

  4. Base64图标:应用使用Base64编码的图标,这种方式在跨平台开发中是可行的,但需要注意性能影响。

样式

React Native的样式系统与鸿蒙平台的样式系统存在差异,需要注意以下几点:

  1. 样式写法:React Native使用驼峰命名法定义样式,而鸿蒙平台可能使用不同的样式定义方式。

  2. 布局系统:虽然两者都支持Flexbox布局,但在具体实现细节上可能存在差异。

  3. 响应式设计:需要确保应用在不同屏幕尺寸和方向下都能正常显示。


组件

PackageTrackingApp组件在性能优化方面采取了以下措施:

  1. 避免不必要的重渲染:使用useState钩子管理状态,确保只有状态变化时才会触发组件重渲染。

  2. 合理使用ScrollView:使用ScrollView包装内容,确保在小屏幕设备上的完整显示,同时避免一次性加载过多内容。

  3. 样式复用:通过StyleSheet.create()创建可复用的样式,减少运行时的样式计算。

  4. 条件渲染:使用条件渲染,只在有包裹信息时显示包裹详情,减少不必要的渲染。

用户体验

  1. 视觉层次感:通过卡片式设计、阴影效果和边框,创建清晰的视觉层次,提高界面的可读性。

  2. 色彩方案:为不同物流状态定义了不同的颜色,增强视觉识别度,提高用户体验。

  3. 交互反馈:所有可点击元素都应该有明确的视觉反馈,如TouchableOpacity的默认触摸效果。

  4. 加载状态:使用setTimeout模拟网络请求的延迟,实际应用中应该添加加载指示器,提供清晰的加载状态反馈。

  5. 错误处理:在用户未输入快递单号时,显示错误提示,提高用户体验。

  6. 状态反馈:通过颜色和文本,清晰展示包裹的当前状态和物流节点信息,帮助用户了解包裹情况。


数据结构

PackageTrackingApp组件使用了TypeScript的类型定义功能,为物流节点定义了明确的类型:

// 物流节点类型
type LogisticsNode = {
  id: string;
  status: string;
  time: string;
  location: string;
  description: string;
};

这种类型定义方式提高了代码的可读性和可维护性,减少了运行时错误的可能性。

状态管理

PackageTrackingApp组件使用useState钩子管理应用状态:

const [trackingNumber, setTrackingNumber] = useState('');
const [packageInfo, setPackageInfo] = useState({
  trackingNumber: '',
  sender: '北京仓库',
  recipient: '上海客户',
  estimatedDelivery: '2023-12-05 18:00',
  status: '运输中',
  currentLocation: '南京转运中心'
});

const [logisticsNodes, setLogisticsNodes] = useState<LogisticsNode[]>([
  // 物流节点数据
]);

这种状态管理方式简洁明了,使用单个状态变量管理相关联的数据,提高了代码的组织性。

包裹查询

应用实现了包裹查询的核心功能:

const handleTrackPackage = () => {
  if (!trackingNumber.trim()) {
    Alert.alert('提示', '请输入快递单号');
    return;
  }

  // 模拟查询结果
  setTimeout(() => {
    setPackageInfo({
      trackingNumber: trackingNumber,
      sender: '北京仓库',
      recipient: '上海客户',
      estimatedDelivery: '2023-12-05 18:00',
      status: '运输中',
      currentLocation: '南京转运中心'
    });

    setLogisticsNodes([
      // 物流节点数据
    ]);

    Alert.alert('成功', '包裹信息查询成功');
  }, 1000);
};

这种实现方式使用了setTimeout模拟网络请求的延迟,然后更新包裹信息和物流节点数据,确保了包裹查询过程的完整性和可靠性。

物流节点展示

应用实现了物流节点展示的核心功能,通过映射物流节点数组,为每个节点创建对应的UI元素,显示节点的状态、时间、地点和描述等信息。

PackageTrackingApp组件展示了如何使用React Native构建一个功能完整、用户体验良好的包裹追踪应用。通过现代化的React函数式组件架构和Hooks状态管理,实现了清晰的代码结构和高效的开发体验。

在跨端开发方面,应用使用了React Native的核心组件和API,为后续的鸿蒙平台适配奠定了基础。通过合理的组件选择和状态管理,减少了跨平台适配的复杂度。


1. 状态管理

当前应用使用了多个useState钩子管理状态,可以考虑将相关状态组合到一个状态对象中,提高代码的组织性:

const [appState, setAppState] = useState({
  trackingNumber: '',
  packageInfo: {
    trackingNumber: '',
    sender: '北京仓库',
    recipient: '上海客户',
    estimatedDelivery: '2023-12-05 18:00',
    status: '运输中',
    currentLocation: '南京转运中心'
  },
  logisticsNodes: [
    // 物流节点数据
  ],
  isLoading: false
});

2. 组件拆分

将大组件拆分为更小的、可复用的子组件,提高代码的可读性和可维护性:

// 子组件示例
const LogisticsNodeItem: React.FC<{ node: LogisticsNode; isLast: boolean }> = ({ node, isLast }) => (
  <View style={styles.logisticsNode}>
    <View style={styles.nodeTime}>
      <Text style={styles.nodeStatus}>{node.status}</Text>
      <Text style={styles.nodeTimeText}>{node.time}</Text>
    </View>
    <View style={styles.nodeContent}>
      <View style={[styles.nodeDot, { backgroundColor: getStatusColor(node.status) }]} />
      <View style={styles.nodeInfo}>
        <Text style={styles.nodeLocation}>{node.location}</Text>
        <Text style={styles.nodeDescription}>{node.description}</Text>
      </View>
      {!isLast && <View style={styles.nodeLine} />}
    </View>
  </View>
);

// 父组件中使用
<View style={styles.logisticsNodes}>
  {logisticsNodes.map((node, index) => (
    <LogisticsNodeItem
      key={node.id}
      node={node}
      isLast={index === logisticsNodes.length - 1}
    />
  ))}
</View>

3. 类型定义

使用TypeScript的接口和类型别名,为状态和props定义更清晰的类型结构:

interface LogisticsNode {
  id: string;
  status: string;
  time: string;
  location: string;
  description: string;
}

interface PackageInfo {
  trackingNumber: string;
  sender: string;
  recipient: string;
  estimatedDelivery: string;
  status: string;
  currentLocation: string;
}

interface AppState {
  trackingNumber: string;
  packageInfo: PackageInfo;
  logisticsNodes: LogisticsNode[];
  isLoading: boolean;
}

4. 错误处理

添加更全面的错误处理机制,提高应用的稳定性和可靠性:

const handleTrackPackage = async () => {
  if (!trackingNumber.trim()) {
    Alert.alert('提示', '请输入快递单号');
    return;
  }

  try {
    setAppState(prev => ({ ...prev, isLoading: true }));
    
    // 模拟网络请求
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // 更新包裹信息和物流节点
    setAppState(prev => ({
      ...prev,
      packageInfo: {
        trackingNumber: trackingNumber,
        sender: '北京仓库',
        recipient: '上海客户',
        estimatedDelivery: '2023-12-05 18:00',
        status: '运输中',
        currentLocation: '南京转运中心'
      },
      logisticsNodes: [
        // 物流节点数据
      ],
      isLoading: false
    }));
    
    Alert.alert('成功', '包裹信息查询成功');
  } catch (error) {
    setAppState(prev => ({ ...prev, isLoading: false }));
    Alert.alert('错误', '查询包裹信息失败,请稍后重试', [
      { text: '确定' }
    ]);
  }
};

5. 网络请求

实现真正的网络请求,获取真实的包裹信息和物流节点数据:

const fetchPackageInfo = async (trackingNumber: string) => {
  try {
    const response = await fetch(`https://api.example.com/tracking/${trackingNumber}`);
    if (!response.ok) {
      throw new Error('网络请求失败');
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('获取包裹信息失败:', error);
    throw error;
  }
};

const handleTrackPackage = async () => {
  if (!trackingNumber.trim()) {
    Alert.alert('提示', '请输入快递单号');
    return;
  }

  try {
    setAppState(prev => ({ ...prev, isLoading: true }));
    
    const data = await fetchPackageInfo(trackingNumber);
    
    setAppState(prev => ({
      ...prev,
      packageInfo: data.packageInfo,
      logisticsNodes: data.logisticsNodes,
      isLoading: false
    }));
    
    Alert.alert('成功', '包裹信息查询成功');
  } catch (error) {
    setAppState(prev => ({ ...prev, isLoading: false }));
    Alert.alert('错误', '查询包裹信息失败,请稍后重试', [
      { text: '确定' }
    ]);
  }
};

6. 加载状态

添加加载指示器,提供清晰的加载状态反馈:

<TouchableOpacity 
  style={styles.searchButton} 
  onPress={handleTrackPackage}
  disabled={isLoading}
>
  {isLoading ? (
    <ActivityIndicator color="#fff" />
  ) : (
    <Text style={styles.searchButtonText}>查询包裹</Text>
  )}
</TouchableOpacity>

通过以上优化建议,可以进一步提高PackageTrackingApp组件的性能、可维护性和用户体验,使其成为一个更加完善的包裹追踪应用。

React Native包裹追踪应用展示了如何使用现代化的React技术构建一个功能完整、用户体验良好的移动应用。通过合理的架构设计、状态管理和性能优化,可以构建出高质量的跨端应用,同时为后续的鸿蒙平台适配奠定基础。


真实演示案例代码:




// 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==',
  search: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  location: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  calendar: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  time: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  truck: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  check: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// 物流节点类型
type LogisticsNode = {
  id: string;
  status: string;
  time: string;
  location: string;
  description: string;
};

// 包裹追踪应用组件
const PackageTrackingApp: React.FC = () => {
  const [trackingNumber, setTrackingNumber] = useState('');
  const [packageInfo, setPackageInfo] = useState({
    trackingNumber: '',
    sender: '北京仓库',
    recipient: '上海客户',
    estimatedDelivery: '2023-12-05 18:00',
    status: '运输中',
    currentLocation: '南京转运中心'
  });

  const [logisticsNodes, setLogisticsNodes] = useState<LogisticsNode[]>([
    {
      id: '1',
      status: '已揽收',
      time: '2023-12-01 09:30',
      location: '北京朝阳区网点',
      description: '快件已被揽收'
    },
    {
      id: '2',
      status: '运输中',
      time: '2023-12-01 15:20',
      location: '北京转运中心',
      description: '快件正在发往下一个转运中心'
    },
    {
      id: '3',
      status: '运输中',
      time: '2023-12-02 08:45',
      location: '南京转运中心',
      description: '快件正在南京转运中心处理'
    }
  ]);

  const getStatusColor = (status: string) => {
    switch (status) {
      case '已揽收': return '#3b82f6';
      case '运输中': return '#f59e0b';
      case '派送中': return '#8b5cf6';
      case '已签收': return '#10b981';
      default: return '#6b7280';
    }
  };

  const handleTrackPackage = () => {
    if (!trackingNumber.trim()) {
      Alert.alert('提示', '请输入快递单号');
      return;
    }

    // 模拟查询结果
    setTimeout(() => {
      setPackageInfo({
        trackingNumber: trackingNumber,
        sender: '北京仓库',
        recipient: '上海客户',
        estimatedDelivery: '2023-12-05 18:00',
        status: '运输中',
        currentLocation: '南京转运中心'
      });

      setLogisticsNodes([
        {
          id: '1',
          status: '已揽收',
          time: '2023-12-01 09:30',
          location: '北京朝阳区网点',
          description: '快件已被揽收'
        },
        {
          id: '2',
          status: '运输中',
          time: '2023-12-01 15:20',
          location: '北京转运中心',
          description: '快件正在发往下一个转运中心'
        },
        {
          id: '3',
          status: '运输中',
          time: '2023-12-02 08:45',
          location: '南京转运中心',
          description: '快件正在南京转运中心处理'
        },
        {
          id: '4',
          status: '运输中',
          time: '2023-12-03 14:30',
          location: '上海转运中心',
          description: '快件已到达上海转运中心'
        },
        {
          id: '5',
          status: '派送中',
          time: '2023-12-04 09:15',
          location: '上海浦东新区网点',
          description: '快件正在派送中'
        }
      ]);

      Alert.alert('成功', '包裹信息查询成功');
    }, 1000);
  };

  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}>
          <Text style={styles.sectionTitle}>输入快递单号</Text>
          
          <View style={styles.inputContainer}>
            <Text style={styles.searchIcon}>🔍</Text>
            <TextInput
              style={styles.trackingInput}
              placeholder="请输入快递单号"
              value={trackingNumber}
              onChangeText={setTrackingNumber}
            />
          </View>
          
          <TouchableOpacity 
            style={styles.searchButton}
            onPress={handleTrackPackage}
          >
            <Text style={styles.searchButtonText}>查询包裹</Text>
          </TouchableOpacity>
        </View>

        {/* 包裹基本信息 */}
        {packageInfo.trackingNumber && (
          <View style={styles.packageCard}>
            <Text style={styles.sectionTitle}>包裹信息</Text>
            
            <View style={styles.packageInfo}>
              <View style={styles.infoRow}>
                <Text style={styles.infoLabel}>运单号:</Text>
                <Text style={styles.infoValue}>{packageInfo.trackingNumber}</Text>
              </View>
              <View style={styles.infoRow}>
                <Text style={styles.infoLabel}>发件地:</Text>
                <Text style={styles.infoValue}>{packageInfo.sender}</Text>
              </View>
              <View style={styles.infoRow}>
                <Text style={styles.infoLabel}>收件地:</Text>
                <Text style={styles.infoValue}>{packageInfo.recipient}</Text>
              </View>
              <View style={styles.infoRow}>
                <Text style={styles.infoLabel}>当前状态:</Text>
                <View style={[
                  styles.statusBadge,
                  { backgroundColor: getStatusColor(packageInfo.status) }
                ]}>
                  <Text style={styles.statusText}>{packageInfo.status}</Text>
                </View>
              </View>
              <View style={styles.infoRow}>
                <Text style={styles.infoLabel}>当前位置:</Text>
                <Text style={styles.infoValue}>{packageInfo.currentLocation}</Text>
              </View>
              <View style={styles.infoRow}>
                <Text style={styles.infoLabel}>预计送达:</Text>
                <Text style={styles.infoValue}>{packageInfo.estimatedDelivery}</Text>
              </View>
            </View>
          </View>
        )}

        {/* 物流轨迹 */}
        {logisticsNodes.length > 0 && (
          <View style={styles.trackingCard}>
            <Text style={styles.sectionTitle}>物流轨迹</Text>
            
            <View style={styles.timeline}>
              {logisticsNodes.map((node, index) => (
                <View key={node.id} style={styles.timelineItem}>
                  <View style={styles.timelineDotContainer}>
                    <View style={[
                      styles.timelineDot,
                      index === logisticsNodes.length - 1 && styles.currentDot
                    ]} />
                    {index < logisticsNodes.length - 1 && (
                      <View style={styles.timelineLine} />
                    )}
                  </View>
                  
                  <View style={styles.timelineContent}>
                    <View style={styles.nodeHeader}>
                      <Text style={[
                        styles.nodeStatus,
                        index === logisticsNodes.length - 1 && styles.currentStatus
                      ]}>
                        {node.status}
                      </Text>
                      <Text style={styles.nodeTime}>{node.time}</Text>
                    </View>
                    <Text style={styles.nodeLocation}>{node.location}</Text>
                    <Text style={styles.nodeDescription}>{node.description}</Text>
                  </View>
                </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: '#eff6ff',
  },
  header: {
    flexDirection: 'column',
    padding: 16,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#dbeafe',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e3a8a',
    marginBottom: 4,
  },
  subtitle: {
    fontSize: 14,
    color: '#2563eb',
  },
  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,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1e3a8a',
    marginBottom: 12,
  },
  inputContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#eff6ff',
    borderRadius: 8,
    paddingHorizontal: 12,
    marginBottom: 12,
  },
  searchIcon: {
    fontSize: 16,
    color: '#64748b',
    marginRight: 8,
  },
  trackingInput: {
    flex: 1,
    paddingVertical: 12,
    fontSize: 14,
    color: '#1e3a8a',
  },
  searchButton: {
    backgroundColor: '#2563eb',
    paddingVertical: 14,
    borderRadius: 8,
    alignItems: 'center',
  },
  searchButtonText: {
    color: '#ffffff',
    fontSize: 16,
    fontWeight: '600',
  },
  packageCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  packageInfo: {
    // 包裹信息样式
  },
  infoRow: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 8,
  },
  infoLabel: {
    fontSize: 14,
    color: '#64748b',
    width: 80,
  },
  infoValue: {
    fontSize: 14,
    color: '#1e3a8a',
    flex: 1,
  },
  statusBadge: {
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 12,
  },
  statusText: {
    fontSize: 12,
    color: '#ffffff',
    fontWeight: '500',
  },
  trackingCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  timeline: {
    // 时间线样式
  },
  timelineItem: {
    flexDirection: 'row',
    marginBottom: 16,
  },
  timelineDotContainer: {
    alignItems: 'center',
    marginRight: 12,
  },
  timelineDot: {
    width: 12,
    height: 12,
    borderRadius: 6,
    backgroundColor: '#d1d5db',
  },
  currentDot: {
    backgroundColor: '#2563eb',
    width: 16,
    height: 16,
    borderRadius: 8,
  },
  timelineLine: {
    width: 2,
    height: 40,
    backgroundColor: '#d1d5db',
  },
  timelineContent: {
    flex: 1,
  },
  nodeHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 4,
  },
  nodeStatus: {
    fontSize: 14,
    fontWeight: '500',
    color: '#64748b',
  },
  currentStatus: {
    color: '#2563eb',
    fontWeight: '600',
  },
  nodeTime: {
    fontSize: 12,
    color: '#94a3b8',
  },
  nodeLocation: {
    fontSize: 13,
    color: '#1e3a8a',
    marginBottom: 2,
  },
  nodeDescription: {
    fontSize: 12,
    color: '#64748b',
  },
  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: '#dbeafe',
    paddingVertical: 12,
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  activeNavItem: {
    paddingTop: 4,
    borderTopWidth: 2,
    borderTopColor: '#2563eb',
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  activeNavIcon: {
    color: '#2563eb',
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  activeNavText: {
    color: '#2563eb',
    fontWeight: '500',
  },
});

export default PackageTrackingApp;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文介绍了基于React Native开发的物流追踪应用技术实现,重点解析了其物流轨迹可视化、状态管理和交互设计。文章提出完整的鸿蒙ArkTS跨端适配方案,为物流类应用开发提供参考。主要内容包括:1)物流场景领域模型设计,涵盖节点管理、状态可视化和轨迹展示;2)三层核心状态架构实现全流程管控;3)单号查询-异步请求-轨迹渲染的闭环交互流程;4)时间轴可视化与状态语义化设计。同时详细阐述了React Native到鸿蒙平台的迁移策略,强调类型复用、逻辑对等和体验统一原则,确保物流追踪功能的专业性和跨平台一致性。

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

Logo

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

更多推荐