物流轨迹数据结构详解:progresses 数组怎么用
2026-07-26 · 阅读约 6 分钟
查一次快递,返回的不只是一个「当前到哪了」,还有一整条物流轨迹 ——
也就是 progresses 数组。很多人只取了 deliveryStatus 就收工,
白白浪费了这条能拼出完整时间线的数据。本文只讲 progresses 的数据结构和用法:
每个字段是什么、怎么排序、怎么渲染成时间线、怎么入库。至于 12 种状态码各自的含义,
由状态码详解那篇负责,这里不重复。
一、progresses 在返回里的位置
每条成功的查询结果 results[i].data 里,除了运单级别的字段,还带一个
progresses 数组。下面是一条运输中包裹的精简示例:
{
"trackingNumber": "CGD0000123456",
"deliveryStatus": "IN_TRANSIT",
"deliveryStatusText": "运输中",
"isDelivered": false,
"dateDelivered": null,
"dateLastProgress": "2026-07-25 18:02:11",
"progresses": [
{
"dateTime": "2026-07-25 18:02:11",
"location": "深圳转运中心",
"status": "运输中",
"statusCode": "IN_TRANSIT",
"description": "快件已从深圳转运中心发出"
},
{
"dateTime": "2026-07-24 09:15:40",
"location": "广州集散中心",
"status": "已揽收",
"statusCode": "PICKED_UP",
"description": null
}
]
} 二、每条轨迹的五个字段
progresses 里每一项代表一个物流节点,共五个字段。其中 location 和
description 可能缺失(值为 null 或不出现),渲染前务必判空。
| 字段 | 类型 | 含义 |
|---|---|---|
dateTime | 字符串 | 该节点发生时间,格式固定为 yyyy-MM-dd HH:mm:ss |
location | 字符串 / 空 | 节点发生地点,如「深圳转运中心」;部分节点没有地点 |
status | 字符串 | 归一后的简洁中文描述,可直接展示 |
statusCode | 字符串 | 12 种统一状态码之一,用于程序判断(含义见状态码详解) |
description | 字符串 / 空 | 承运商原文附加说明,比 status 更细;可能为空 |
一句话分工:想展示给人看就用 status / location / description,
想让程序判断就用 statusCode。两者一一对应,互不干扰。
三、排序:最新在最前,progresses[0] 就是当前状态
接口已经把 progresses 按时间倒序排好 —— 最新的节点在数组开头。
所以你不需要自己再排序,也不用比较时间戳找「最新那条」:
const d = json.data.results[0].data;
// progresses[0] 就是最新节点,与 deliveryStatus 指向同一状态
const zuixin = d.progresses[0];
console.log(zuixin.statusCode); // 等同于 d.deliveryStatus
console.log(zuixin.dateTime); // 等同于 d.dateLastProgress
换句话说:progresses[0].statusCode 与运单级的 deliveryStatus 一致,
progresses[0].dateTime 与 dateLastProgress 一致。
只想显示「当前状态」用运单级字段更省事;要展示完整历程才遍历整个数组。
四、渲染成时间线 UI
既然数组已经倒序,渲染时间线就是「从头到尾遍历、逐条追加」。下面用原生 DOM 把它塞进一个
<ul id="guiji">,注意对可能为空的 location 做了判空:
// 后端已按时间倒序返回,直接遍历即可,无需再排序
const ul = document.getElementById('guiji');
for (const p of d.progresses) {
const li = document.createElement('li');
// location 可能为空,用 || 兜底避免出现 "undefined"
const difang = p.location ? ' · ' + p.location : '';
li.textContent = p.dateTime + difang + ' ' + p.status;
ul.appendChild(li);
}
想做得更精致,可以把最新一条(index === 0)高亮为「当前状态」,其余作为历史节点弱化显示;
description 存在时作为二级说明补在 status 下方。这些都是纯展示逻辑,数据层面无需额外处理。
五、配合 isDelivered 与时间字段
轨迹数组之外,运单级还有三个字段和它配合得很好:
| 字段 | 用途 |
|---|---|
isDelivered | 布尔值。为 true 即已签收,可停止轮询、归档订单 |
dateDelivered | 签收时间;未签收时为空 |
dateLastProgress | 最新一条轨迹的时间,等于 progresses[0].dateTime |
一个常用场景是判断包裹「多久没动了」:拿当前时间减去 dateLastProgress,
超过某个阈值(比如 48 小时)就标记为「疑似停滞」提醒人工介入 —— 全程不用翻 progresses,
直接读这个字段即可。
六、存进数据库的建议
把轨迹落库时,推荐拆成两张表,避免每次更新都重写整条数组:
- 运单主表:一条运单一行,存
trackingNumber、courierCode、deliveryStatus、isDelivered、dateDelivered、dateLastProgress。 查列表、做筛选都靠它。 - 轨迹明细表:一个节点一行,存
dateTime、location、status、statusCode、description,外加所属运单号。展示详情时按dateTime倒序取出即可。
追加新节点时会遇到重复:同一单再次查询,旧节点还会再返回一遍。用
trackingNumber + dateTime + statusCode 作为去重键,只插入新出现的节点,就不会写重。
isDelivered 为 true 后这单已是终态,可以停止再查,省额度也省写入。
七、小结
progresses已按时间倒序,progresses[0]就是当前状态,不用自己排序。location和description可能为空,渲染与入库都要判空。- 展示用
status/location,判断用statusCode;是否结束看isDelivered。 - 落库拆「运单主表 + 轨迹明细表」,用三字段组合键去重。
每个 statusCode 的具体含义与流转见物流状态码详解;
完整字段清单见接口文档。
还没有密钥就先注册(邮箱即可,无需企业认证),
或用首页在线演示跑一遍看看真实的 progresses 长什么样。