基于React Native鸿蒙跨平台实现物流类应用开发时间轴可视化、状态可视化和轨迹展示
本文介绍了基于React Native开发的物流追踪应用技术实现,重点解析了其物流轨迹可视化、状态管理和交互设计。文章提出完整的鸿蒙ArkTS跨端适配方案,为物流类应用开发提供参考。主要内容包括:1)物流场景领域模型设计,涵盖节点管理、状态可视化和轨迹展示;2)三层核心状态架构实现全流程管控;3)单号查询-异步请求-轨迹渲染的闭环交互流程;4)时间轴可视化与状态语义化设计。同时详细阐述了React
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
物流追踪是电商履约和快递服务的核心场景,高效的包裹追踪系统需要兼顾实时性、可视化、跨端适配三大核心特性。本文将深度拆解基于 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 端的适配实现,则验证了跨端开发中"逻辑复用、体验统一"的核心原则。
- 包裹追踪系统的类型设计需贴合物流场景,覆盖物流节点的状态、时间、位置、描述等核心属性;
- 时间轴可视化是物流轨迹展示的核心,需保证当前节点突出、连线逻辑准确、信息层级清晰;
- 跨端适配的核心是物流业务逻辑复用 + 平台特性适配,单号查询、状态映射等核心逻辑无需重写;
- 状态色彩的语义化设计能显著提升物流状态的识别效率,不同平台需保持相同的色彩映射规则;
- 异步查询的模拟实现保留了对接真实物流 API 的扩展能力,是跨端适配的重要设计考量;
- 输入校验和操作反馈是提升物流查询体验的关键技术手段,需在跨端适配中完整保留;
- 物流场景化的视觉设计(蓝色系主色调、卡片式布局)是提升追踪效率的核心体验要素。
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:鸿蒙平台支持文本输入功能。
-
View和Text:鸿蒙平台提供了基础的布局和文本组件。
API差异
React Native与鸿蒙平台在API层面存在一些差异,需要注意以下几点:
-
Dimensions API:应用使用Dimensions.get(‘window’)获取屏幕尺寸,在鸿蒙平台上需要使用相应的API获取屏幕信息。
-
Alert API:React Native的Alert组件在鸿蒙平台上可能有不同的实现方式,需要进行适配。
-
setTimeout API:应用使用setTimeout模拟网络请求的延迟,在鸿蒙平台上需要使用相应的API实现延迟操作。
-
Base64图标:应用使用Base64编码的图标,这种方式在跨平台开发中是可行的,但需要注意性能影响。
样式
React Native的样式系统与鸿蒙平台的样式系统存在差异,需要注意以下几点:
-
样式写法:React Native使用驼峰命名法定义样式,而鸿蒙平台可能使用不同的样式定义方式。
-
布局系统:虽然两者都支持Flexbox布局,但在具体实现细节上可能存在差异。
-
响应式设计:需要确保应用在不同屏幕尺寸和方向下都能正常显示。
组件
PackageTrackingApp组件在性能优化方面采取了以下措施:
-
避免不必要的重渲染:使用useState钩子管理状态,确保只有状态变化时才会触发组件重渲染。
-
合理使用ScrollView:使用ScrollView包装内容,确保在小屏幕设备上的完整显示,同时避免一次性加载过多内容。
-
样式复用:通过StyleSheet.create()创建可复用的样式,减少运行时的样式计算。
-
条件渲染:使用条件渲染,只在有包裹信息时显示包裹详情,减少不必要的渲染。
用户体验
-
视觉层次感:通过卡片式设计、阴影效果和边框,创建清晰的视觉层次,提高界面的可读性。
-
色彩方案:为不同物流状态定义了不同的颜色,增强视觉识别度,提高用户体验。
-
交互反馈:所有可点击元素都应该有明确的视觉反馈,如TouchableOpacity的默认触摸效果。
-
加载状态:使用setTimeout模拟网络请求的延迟,实际应用中应该添加加载指示器,提供清晰的加载状态反馈。
-
错误处理:在用户未输入快递单号时,显示错误提示,提高用户体验。
-
状态反馈:通过颜色和文本,清晰展示包裹的当前状态和物流节点信息,帮助用户了解包裹情况。
数据结构
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到鸿蒙平台的迁移策略,强调类型复用、逻辑对等和体验统一原则,确保物流追踪功能的专业性和跨平台一致性。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐


所有评论(0)