Files
SaaS2/docs/sf-express-logistics-status-guide.md
RUI d8398afa12
Some checks failed
Next.js CI/CD 流水线 / deploy (push) Failing after 22s
0609.1
2025-06-09 01:27:51 +08:00

12 KiB
Raw Blame History

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版本过旧
  • 权限配置不足
  • 请求参数不正确

解决方案:

  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. 防抖处理: 防止用户频繁点击查询

📚 参考资料

📧 联系方式

如有问题或建议,请联系:

  • 开发者:阿瑞
  • 技术栈TypeScript + Next.js + Ant Design
  • 文档版本v1.0.0