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

365 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
// 状态标签
<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数据示例
### 完整状态数据(理想情况)
```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<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. **防抖处理**: 防止用户频繁点击查询
## 📚 参考资料
- [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