12 KiB
12 KiB
SF快递物流状态判断指南
作者: 阿瑞
版本: 1.0.0
更新时间: 2025-01-28
📖 概述
本文档详细介绍了如何在SF快递物流查询系统中准确判断和显示物流状态。基于SF快递官方API文档和实际开发经验总结。
🔍 状态判断数据源
官方标准数据结构
根据SF快递官方API文档,完整的路由节点应包含以下状态信息:
interface RouteInfo {
acceptTime: string; // 路由节点发生时间
acceptAddress: string; // 路由节点发生地点
remark: string; // 路由节点具体描述
opCode: string; // 路由节点操作码
firstStatusCode?: string; // 一级状态编码
firstStatusName?: string; // 一级状态名称
secondaryStatusCode?: string; // 二级状态编码
secondaryStatusName?: string; // 二级状态名称
}
数据完整性检查
实际API返回的数据可能缺少状态编码字段,需要多种备用方案:
| 字段 | 重要程度 | 描述 |
|---|---|---|
firstStatusName |
⭐⭐⭐ | 官方一级状态,最准确 |
secondaryStatusName |
⭐⭐⭐ | 官方二级状态,最详细 |
opCode |
⭐⭐ | 操作码,可映射到状态 |
remark |
⭐ | 文本描述,可通过关键词分析 |
🎯 状态判断方法
方法一:官方状态字段(推荐)
优先级:最高
if (route.firstStatusName) {
return route.firstStatusName; // 直接使用官方状态
}
常见状态值:
待揽收- 快件等待收取已揽收- 快件已被收取运输中- 快件正在运输派送中- 快件正在派送已签收- 快件成功签收已拒收- 快件被拒收退回中- 快件正在退回已退回- 快件已退回发件人
方法二:opCode映射表(备用)
优先级:中等
✅ 数据来源: 基于真实SF快递物流数据分析,包含完整快递生命周期的opCode对应关系。
📋 数据样本: 分析了从揽收到退回成功的47个物流节点,包含以下新发现的opCode:
43: 已收取(重复确认)302: 长途运输(显示距离信息)310: 途经中转(城市中转点)44: 准备派送(分配快递员)204: 派送中(具体快递员信息)70: 派送失败(多种失败原因)33: 待派送(约定时间)517: 退回申请中(客户申请)99: 退回中(实际退回过程)80: 退回成功(最终完成)
const statusMap = {
// ========== 揽收相关 ==========
'54': { status: '已揽收', color: 'blue', description: '快件已被顺丰收取' },
'43': { status: '已收取', color: 'blue', description: '顺丰速运已收取快件' },
// ========== 分拣运输相关 ==========
'30': { status: '分拣中', color: 'processing', description: '快件正在分拣处理' },
'36': { status: '运输中', color: 'processing', description: '快件正在运输途中' },
'31': { status: '到达网点', color: 'processing', description: '快件已到达转运中心' },
'302': { status: '长途运输', color: 'processing', description: '快件正在安全运输中(长距离)' },
'310': { status: '途经中转', color: 'processing', description: '快件途经中转城市' },
// ========== 派送相关 ==========
'44': { status: '准备派送', color: 'processing', description: '正在为快件分配合适的快递员' },
'204': { status: '派送中', color: 'processing', description: '快件已交给快递员,正在派送途中' },
'70': { status: '派送失败', color: 'warning', description: '快件派送不成功,待再次派送' },
'33': { status: '待派送', color: 'warning', description: '已与客户约定新派送时间,待派送' },
// ========== 退回相关 ==========
'517': { status: '退回申请中', color: 'warning', description: '快件退回申请已提交,正在处理中' },
'99': { status: '退回中', color: 'warning', description: '应客户要求,快件正在退回中' },
'80': { status: '退回成功', color: 'success', description: '退回快件已派送成功' },
// ========== 签收相关 ==========
'81': { status: '已签收', color: 'success', description: '快件已成功签收' },
'82': { status: '已拒收', color: 'error', description: '快件被拒收' },
// ========== 其他状态 ==========
'655': { status: '待揽收', color: 'default', description: '快件等待揽收' },
'701': { status: '待揽收', color: 'default', description: '快件等待揽收' },
'101': { status: '已揽收', color: 'blue', description: '快件已揽收' },
};
方法三:文本关键词分析(最后备用)
优先级:最低
const analyzeStatusByRemark = (remark) => {
const text = remark.toLowerCase();
// 揽收相关
if (text.includes('已收取快件') || text.includes('已揽收')) {
return { status: '已揽收', color: 'blue' };
}
// 分拣相关
if (text.includes('完成分拣')) {
return { status: '分拣中', color: 'processing' };
}
// 运输相关
if (text.includes('离开') || text.includes('发往') || text.includes('路上') || text.includes('安全运输中')) {
return { status: '运输中', color: 'processing' };
}
if (text.includes('途经')) {
return { status: '途经中转', color: 'processing' };
}
if (text.includes('到达')) {
return { status: '到达网点', color: 'processing' };
}
// 派送相关
if (text.includes('分配最合适的快递员')) {
return { status: '准备派送', color: 'processing' };
}
if (text.includes('交给') && text.includes('正在派送途中')) {
return { status: '派送中', color: 'processing' };
}
if (text.includes('派送不成功') || text.includes('约定新派送时间')) {
return { status: '派送失败', color: 'warning' };
}
if (text.includes('待派送')) {
return { status: '待派送', color: 'warning' };
}
// 签收相关
if (text.includes('派送成功') || text.includes('签收')) {
return { status: '已签收', color: 'success' };
}
if (text.includes('拒收') || text.includes('拒付费用')) {
return { status: '已拒收', color: 'error' };
}
// 退回相关
if (text.includes('退回申请已提交')) {
return { status: '退回申请中', color: 'warning' };
}
if (text.includes('快件正在退回中') || text.includes('应客户要求')) {
return { status: '退回中', color: 'warning' };
}
if (text.includes('退回快件已派送成功')) {
return { status: '退回成功', color: 'success' };
}
return { status: '处理中', color: 'default' };
};
🔄 状态判断流程图
graph TD
A[接收路由数据] --> B{是否有firstStatusName?}
B -->|有| C[使用官方一级状态]
B -->|无| D{是否有opCode?}
D -->|有| E[查询opCode映射表]
D -->|无| F[分析remark文本]
E --> G{映射表中存在?}
G -->|是| H[返回映射状态]
G -->|否| F
F --> I[返回文本分析结果]
C --> J[状态判断完成]
H --> J
I --> J
🎨 UI状态显示规范
状态颜色映射
| 状态类型 | 颜色 | CSS类 | 含义 |
|---|---|---|---|
| 已签收 | 🟢 绿色 | bg-green-100 text-green-700 |
成功完成 |
| 已拒收/已退回 | 🔴 红色 | bg-red-100 text-red-700 |
失败/异常 |
| 退回中 | 🟡 黄色 | bg-yellow-100 text-yellow-700 |
警告状态 |
| 运输中/派送中 | 🔵 蓝色 | bg-blue-100 text-blue-700 |
进行中 |
| 待揽收/处理中 | ⚪ 灰色 | bg-gray-100 text-gray-700 |
等待/默认 |
时间轴节点显示
// 状态标签
<span className={`px-2 py-1 rounded-full text-xs font-medium ${colorClass}`}>
{status}
</span>
// 详细信息
<div className="flex flex-wrap gap-4 text-sm text-gray-500">
<span>📍 {acceptAddress}</span>
<span>🔢 opCode: {opCode}</span>
<span>📊 一级状态: {firstStatusName}</span>
<span>📋 二级状态: {secondaryStatusName}</span>
</div>
📊 API数据示例
完整状态数据(理想情况)
{
"acceptTime": "2025-06-07 00:29:27",
"acceptAddress": "广州市",
"remark": "顺丰速运 已收取快件",
"opCode": "54",
"firstStatusCode": "1",
"firstStatusName": "已揽收",
"secondaryStatusCode": "101",
"secondaryStatusName": "已揽收"
}
缺失状态数据(当前情况)
{
"acceptTime": "2025-06-07 00:29:27",
"acceptAddress": "广州市",
"remark": "顺丰速运 已收取快件",
"opCode": "54"
// 缺少 firstStatusName 等字段
}
⚙️ 实现代码示例
完整状态判断函数
const getOverallStatus = (routes: RouteInfo[]): { status: string; color: string } => {
if (!routes || routes.length === 0) {
return { status: '暂无信息', color: 'default' };
}
// 取第一个(最新的)路由节点来判断当前状态
const latestRoute = routes[0];
// 优先使用官方状态字段
if (latestRoute.firstStatusName) {
const statusColorMap: Record<string, string> = {
'待揽收': 'default',
'已揽收': 'blue',
'运输中': 'processing',
'派送中': 'processing',
'已签收': 'success',
'已拒收': 'error',
'退回中': 'warning',
'已退回': 'error'
};
return {
status: latestRoute.firstStatusName,
color: statusColorMap[latestRoute.firstStatusName] || 'default'
};
}
// 备用方案:通过opCode判断
const opCodeStatus = getStatusByOpCode(latestRoute.opCode);
if (opCodeStatus.status !== '处理中') {
return { status: opCodeStatus.status, color: opCodeStatus.color };
}
// 最后备用:通过remark分析
return analyzeStatusByRemark(latestRoute.remark);
};
🔧 故障排查
常见问题及解决方案
1. 状态字段缺失
问题: API返回数据缺少 firstStatusName 等状态字段
可能原因:
- API版本过旧
- 权限配置不足
- 请求参数不正确
解决方案:
- 检查API版本,使用最新版本
- 确认账号权限包含状态字段获取
- 检查
methodType参数设置 - 使用opCode映射作为备用方案
2. 状态显示不准确
问题: 显示的状态与实际情况不符
排查步骤:
- 检查时间轴排序(最新的应该在前)
- 验证opCode映射表的准确性
- 检查文本关键词匹配逻辑
- 查看原始API返回数据
3. 状态更新延迟
问题: 状态更新不及时
注意事项:
- SF快递API数据更新可能有延迟
- 建议设置合理的查询间隔
- 避免频繁查询造成限流
📝 开发建议
最佳实践
- 分层状态判断: 按优先级使用多种方法
- 缓存机制: 避免重复查询相同运单
- 错误处理: 优雅处理API异常情况
- 用户体验: 提供加载状态和错误提示
- 数据验证: 验证API返回数据的完整性
性能优化
- 状态映射表: 预定义常用状态避免重复计算
- 条件渲染: 只在有数据时渲染状态组件
- 防抖处理: 防止用户频繁点击查询
📚 参考资料
📧 联系方式
如有问题或建议,请联系:
- 开发者:阿瑞
- 技术栈:TypeScript + Next.js + Ant Design
- 文档版本:v1.0.0