# SF快递物流状态判断指南 > **作者**: 阿瑞 > **版本**: 1.0.0 > **更新时间**: 2025-01-28 ## 📖 概述 本文档详细介绍了如何在SF快递物流查询系统中准确判断和显示物流状态。基于SF快递官方API文档和实际开发经验总结。 ## 🔍 状态判断数据源 ### 官方标准数据结构 根据SF快递官方API文档,完整的路由节点应包含以下状态信息: ```typescript interface RouteInfo { acceptTime: string; // 路由节点发生时间 acceptAddress: string; // 路由节点发生地点 remark: string; // 路由节点具体描述 opCode: string; // 路由节点操作码 firstStatusCode?: string; // 一级状态编码 firstStatusName?: string; // 一级状态名称 secondaryStatusCode?: string; // 二级状态编码 secondaryStatusName?: string; // 二级状态名称 } ``` ### 数据完整性检查 实际API返回的数据可能缺少状态编码字段,需要多种备用方案: | 字段 | 重要程度 | 描述 | |------|----------|------| | `firstStatusName` | ⭐⭐⭐ | 官方一级状态,最准确 | | `secondaryStatusName` | ⭐⭐⭐ | 官方二级状态,最详细 | | `opCode` | ⭐⭐ | 操作码,可映射到状态 | | `remark` | ⭐ | 文本描述,可通过关键词分析 | ## 🎯 状态判断方法 ### 方法一:官方状态字段(推荐) **优先级:最高** ```javascript if (route.firstStatusName) { return route.firstStatusName; // 直接使用官方状态 } ``` **常见状态值:** - `待揽收` - 快件等待收取 - `已揽收` - 快件已被收取 - `运输中` - 快件正在运输 - `派送中` - 快件正在派送 - `已签收` - 快件成功签收 - `已拒收` - 快件被拒收 - `退回中` - 快件正在退回 - `已退回` - 快件已退回发件人 ### 方法二:opCode映射表(备用) **优先级:中等** > ✅ **数据来源**: 基于真实SF快递物流数据分析,包含完整快递生命周期的opCode对应关系。 > > 📋 **数据样本**: 分析了从揽收到退回成功的47个物流节点,包含以下新发现的opCode: > - `43`: 已收取(重复确认) > - `302`: 长途运输(显示距离信息) > - `310`: 途经中转(城市中转点) > - `44`: 准备派送(分配快递员) > - `204`: 派送中(具体快递员信息) > - `70`: 派送失败(多种失败原因) > - `33`: 待派送(约定时间) > - `517`: 退回申请中(客户申请) > - `99`: 退回中(实际退回过程) > - `80`: 退回成功(最终完成) ```javascript 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: '快件已揽收' }, }; ``` ### 方法三:文本关键词分析(最后备用) **优先级:最低** ```javascript 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' }; }; ``` ## 🔄 状态判断流程图 ```mermaid 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` | 等待/默认 | ### 时间轴节点显示 ```jsx // 状态标签 {status} // 详细信息
📍 {acceptAddress} 🔢 opCode: {opCode} 📊 一级状态: {firstStatusName} 📋 二级状态: {secondaryStatusName}
``` ## 📊 API数据示例 ### 完整状态数据(理想情况) ```json { "acceptTime": "2025-06-07 00:29:27", "acceptAddress": "广州市", "remark": "顺丰速运 已收取快件", "opCode": "54", "firstStatusCode": "1", "firstStatusName": "已揽收", "secondaryStatusCode": "101", "secondaryStatusName": "已揽收" } ``` ### 缺失状态数据(当前情况) ```json { "acceptTime": "2025-06-07 00:29:27", "acceptAddress": "广州市", "remark": "顺丰速运 已收取快件", "opCode": "54" // 缺少 firstStatusName 等字段 } ``` ## ⚙️ 实现代码示例 ### 完整状态判断函数 ```typescript 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 = { '待揽收': '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版本过旧 - 权限配置不足 - 请求参数不正确 **解决方案**: 1. 检查API版本,使用最新版本 2. 确认账号权限包含状态字段获取 3. 检查 `methodType` 参数设置 4. 使用opCode映射作为备用方案 #### 2. 状态显示不准确 **问题**: 显示的状态与实际情况不符 **排查步骤**: 1. 检查时间轴排序(最新的应该在前) 2. 验证opCode映射表的准确性 3. 检查文本关键词匹配逻辑 4. 查看原始API返回数据 #### 3. 状态更新延迟 **问题**: 状态更新不及时 **注意事项**: - SF快递API数据更新可能有延迟 - 建议设置合理的查询间隔 - 避免频繁查询造成限流 ## 📝 开发建议 ### 最佳实践 1. **分层状态判断**: 按优先级使用多种方法 2. **缓存机制**: 避免重复查询相同运单 3. **错误处理**: 优雅处理API异常情况 4. **用户体验**: 提供加载状态和错误提示 5. **数据验证**: 验证API返回数据的完整性 ### 性能优化 1. **状态映射表**: 预定义常用状态避免重复计算 2. **条件渲染**: 只在有数据时渲染状态组件 3. **防抖处理**: 防止用户频繁点击查询 ## 📚 参考资料 - [SF快递开发者文档](https://open.sf-express.com/) - [路由查询接口文档](https://open.sf-express.com/api/EXP_RECE_SEARCH_ROUTES) - [Ant Design Timeline组件](https://ant.design/components/timeline) ## 📧 联系方式 如有问题或建议,请联系: - 开发者:阿瑞 - 技术栈:TypeScript + Next.js + Ant Design - 文档版本:v1.0.0