This commit is contained in:
@@ -2,7 +2,8 @@
|
|||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(git add:*)",
|
"Bash(git add:*)",
|
||||||
"Bash(npm list:*)"
|
"Bash(npm list:*)",
|
||||||
|
"Bash(find . -type f \\\\\\(-name *.openapi.* -o -name *-spec.yaml -o -name *-spec.yml \\\\\\))"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
1
.env.development
Normal file
1
.env.development
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_BASE_URL=https://cmp-api.boss160.cn
|
||||||
1
.env.production
Normal file
1
.env.production
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_BASE_URL=https://cmp-api.xm-iot.cn
|
||||||
436
docs/换货.md
Normal file
436
docs/换货.md
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
# 换货流程升级接口变更说明
|
||||||
|
|
||||||
|
## 一、数据库字段新增
|
||||||
|
|
||||||
|
### 表:`tb_exchange_order`
|
||||||
|
|
||||||
|
新增以下字段,所有换货单相关接口响应均会返回:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ------------ | ------------- | ---------------------------------------------------------- |
|
||||||
|
| flow_type | string | 流程类型:`shipping`(物流换货)或 `direct`(直接换货),历史数据为空时视为 `shipping` |
|
||||||
|
| shipped_at | string | null | 发货时间,仅 `shipping` 流程发货后有值 |
|
||||||
|
| completed_at | string | null | 换货完成时间,换货完成后有值 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 二、后台接口变更
|
||||||
|
|
||||||
|
## 1. 创建换货单
|
||||||
|
|
||||||
|
### POST /api/admin/exchanges
|
||||||
|
|
||||||
|
### 新增请求参数
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"old_asset_type": "iot_card",
|
||||||
|
"old_identifier": "xxx",
|
||||||
|
"exchange_reason": "设备损坏",
|
||||||
|
"flow_type": "shipping",
|
||||||
|
"new_identifier": "yyy",
|
||||||
|
"migrate_data": false,
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 参数说明
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| -------------- | ------------- | ---------- | ---------------------------------------- |
|
||||||
|
| flow_type | string | 否 | 流程类型:`shipping` / `direct`,默认 `shipping` |
|
||||||
|
| new_identifier | string | direct 时必填 | 新资产标识符 |
|
||||||
|
| migrate_data | boolean | 否 | 是否迁移数据,默认 false |
|
||||||
|
| remark | string | null | 否 | 备注 |
|
||||||
|
|
||||||
|
### 不同流程行为差异
|
||||||
|
|
||||||
|
| flow_type | 创建后状态(status) | completed_at |
|
||||||
|
| --------- | ------------- | ------------ |
|
||||||
|
| shipping | 1(待填写信息) | null |
|
||||||
|
| direct | 4(已完成) | 当前时间 |
|
||||||
|
|
||||||
|
### 说明
|
||||||
|
|
||||||
|
* shipping 为原有流程
|
||||||
|
* direct 创建后直接完成
|
||||||
|
* direct 不需要填写地址
|
||||||
|
* direct 不需要发货
|
||||||
|
* direct 不需要确认完成
|
||||||
|
* 非法 flow_type 返回参数错误
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 换货单列表
|
||||||
|
|
||||||
|
### GET /api/admin/exchanges
|
||||||
|
|
||||||
|
### 新增查询参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
| ---------- | ------ | ------------------------ |
|
||||||
|
| flow_type | string | 按流程类型筛选(shipping/direct) |
|
||||||
|
| identifier | string | 资产标识符模糊查询(同时匹配旧资产、新资产) |
|
||||||
|
|
||||||
|
### 响应新增字段
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"flow_type": "shipping",
|
||||||
|
"flow_type_name": "物流换货",
|
||||||
|
"shipped_at": null,
|
||||||
|
"completed_at": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 换货单详情
|
||||||
|
|
||||||
|
### GET /api/admin/exchanges/:id
|
||||||
|
|
||||||
|
### 响应新增字段
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"flow_type": "shipping",
|
||||||
|
"flow_type_name": "物流换货",
|
||||||
|
"shipped_at": null,
|
||||||
|
"completed_at": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 说明
|
||||||
|
|
||||||
|
对于 direct 类型:
|
||||||
|
|
||||||
|
* 收货信息为空
|
||||||
|
* 物流信息为空
|
||||||
|
|
||||||
|
属于正常情况,前端需根据 flow_type 控制相关区块显示。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 发货
|
||||||
|
|
||||||
|
### POST /api/admin/exchanges/:id/ship
|
||||||
|
|
||||||
|
### 新增限制
|
||||||
|
|
||||||
|
仅支持:
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = shipping
|
||||||
|
```
|
||||||
|
|
||||||
|
### 不允许情况
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = direct
|
||||||
|
```
|
||||||
|
|
||||||
|
返回:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "流程类型不支持该操作"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前端处理
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
exchange.flow_type === 'shipping'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 确认完成
|
||||||
|
|
||||||
|
### POST /api/admin/exchanges/:id/complete
|
||||||
|
|
||||||
|
### 新增限制
|
||||||
|
|
||||||
|
仅支持:
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = shipping
|
||||||
|
status = 3
|
||||||
|
```
|
||||||
|
|
||||||
|
### 不允许情况
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = direct
|
||||||
|
```
|
||||||
|
|
||||||
|
返回错误。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 取消换货
|
||||||
|
|
||||||
|
### POST /api/admin/exchanges/:id/cancel
|
||||||
|
|
||||||
|
### 新增限制
|
||||||
|
|
||||||
|
允许取消:
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = shipping
|
||||||
|
status IN (1, 2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 不允许取消
|
||||||
|
|
||||||
|
#### direct 单据
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = direct
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 已发货单据
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = shipping
|
||||||
|
status = 3
|
||||||
|
```
|
||||||
|
|
||||||
|
返回错误。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 三、客户端接口变更
|
||||||
|
|
||||||
|
## 1. 查询进行中的换货单
|
||||||
|
|
||||||
|
### GET /api/c/v1/exchange/pending
|
||||||
|
|
||||||
|
### 变更后返回规则
|
||||||
|
|
||||||
|
仅返回:
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = shipping
|
||||||
|
status IN (1, 2, 3)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 不返回
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = direct
|
||||||
|
```
|
||||||
|
|
||||||
|
客户端无需特殊处理。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 填写收货地址
|
||||||
|
|
||||||
|
### POST /api/c/v1/exchange/:id/shipping-info
|
||||||
|
|
||||||
|
### 新增限制
|
||||||
|
|
||||||
|
仅支持:
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = shipping
|
||||||
|
status = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 不允许
|
||||||
|
|
||||||
|
```text
|
||||||
|
flow_type = direct
|
||||||
|
```
|
||||||
|
|
||||||
|
返回错误。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 四、前端 UI 改造建议
|
||||||
|
|
||||||
|
## 换货单详情页
|
||||||
|
|
||||||
|
### 新增展示字段
|
||||||
|
|
||||||
|
* 流程类型(flow_type_name)
|
||||||
|
* 完成时间(completed_at)
|
||||||
|
* 发货时间(shipped_at)
|
||||||
|
|
||||||
|
### 按流程类型控制按钮
|
||||||
|
|
||||||
|
#### direct
|
||||||
|
|
||||||
|
不显示:
|
||||||
|
|
||||||
|
* 发货
|
||||||
|
* 确认完成
|
||||||
|
* 取消
|
||||||
|
|
||||||
|
#### shipping + status = 1
|
||||||
|
|
||||||
|
显示:
|
||||||
|
|
||||||
|
* 取消
|
||||||
|
|
||||||
|
#### shipping + status = 2
|
||||||
|
|
||||||
|
显示:
|
||||||
|
|
||||||
|
* 发货
|
||||||
|
* 取消
|
||||||
|
|
||||||
|
#### shipping + status = 3
|
||||||
|
|
||||||
|
显示:
|
||||||
|
|
||||||
|
* 确认完成
|
||||||
|
|
||||||
|
### 信息区块控制
|
||||||
|
|
||||||
|
#### direct
|
||||||
|
|
||||||
|
隐藏:
|
||||||
|
|
||||||
|
* 收货信息
|
||||||
|
* 物流信息
|
||||||
|
|
||||||
|
或者显示:
|
||||||
|
|
||||||
|
```text
|
||||||
|
不适用
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 创建换货单弹窗
|
||||||
|
|
||||||
|
### 新增字段
|
||||||
|
|
||||||
|
流程类型:
|
||||||
|
|
||||||
|
* 物流换货(shipping)
|
||||||
|
* 直接换货(direct)
|
||||||
|
|
||||||
|
### 选择 direct 时
|
||||||
|
|
||||||
|
显示:
|
||||||
|
|
||||||
|
* 新资产标识符(必填)
|
||||||
|
* 是否迁移数据(开关)
|
||||||
|
|
||||||
|
对应参数:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"new_identifier": "xxx",
|
||||||
|
"migrate_data": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 选择 shipping 时
|
||||||
|
|
||||||
|
隐藏:
|
||||||
|
|
||||||
|
* 新资产标识符
|
||||||
|
* 是否迁移数据
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 换货单列表页
|
||||||
|
|
||||||
|
### 新增表格列
|
||||||
|
|
||||||
|
```text
|
||||||
|
流程类型
|
||||||
|
```
|
||||||
|
|
||||||
|
显示:
|
||||||
|
|
||||||
|
```text
|
||||||
|
物流换货
|
||||||
|
直接换货
|
||||||
|
```
|
||||||
|
|
||||||
|
### 新增筛选项
|
||||||
|
|
||||||
|
```text
|
||||||
|
流程类型
|
||||||
|
```
|
||||||
|
|
||||||
|
下拉选项:
|
||||||
|
|
||||||
|
```text
|
||||||
|
全部
|
||||||
|
物流换货(shipping)
|
||||||
|
直接换货(direct)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 五、流程状态说明
|
||||||
|
|
||||||
|
## shipping(物流换货)
|
||||||
|
|
||||||
|
```text
|
||||||
|
创建
|
||||||
|
↓
|
||||||
|
待填写信息(1)
|
||||||
|
↓
|
||||||
|
待发货(2)
|
||||||
|
↓
|
||||||
|
已发货(3)
|
||||||
|
↓
|
||||||
|
已完成(4)
|
||||||
|
```
|
||||||
|
|
||||||
|
## direct(直接换货)
|
||||||
|
|
||||||
|
```text
|
||||||
|
创建
|
||||||
|
↓
|
||||||
|
已完成(4)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 六、状态与操作矩阵
|
||||||
|
|
||||||
|
| flow_type | status | 发货 | 确认完成 | 取消 |
|
||||||
|
| --------- | ------ | -- | ---- | -- |
|
||||||
|
| shipping | 1 | ❌ | ❌ | ✅ |
|
||||||
|
| shipping | 2 | ✅ | ❌ | ✅ |
|
||||||
|
| shipping | 3 | ❌ | ✅ | ❌ |
|
||||||
|
| shipping | 4 | ❌ | ❌ | ❌ |
|
||||||
|
| direct | 4 | ❌ | ❌ | ❌ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 七、枚举定义
|
||||||
|
|
||||||
|
## flow_type
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"shipping": "物流换货",
|
||||||
|
"direct": "直接换货"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## flow_type_name 示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"flow_type": "shipping",
|
||||||
|
"flow_type_name": "物流换货"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"flow_type": "direct",
|
||||||
|
"flow_type_name": "直接换货"
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
# Exchange Flow Type Support - Implementation Summary
|
||||||
|
|
||||||
|
## 实施完成 ✅
|
||||||
|
|
||||||
|
### 变更概述
|
||||||
|
|
||||||
|
根据后端换货流程升级,前端已完成对新增字段的支持:
|
||||||
|
- `flow_type`: 流程类型(shipping/direct)
|
||||||
|
- `flow_type_name`: 流程类型名称
|
||||||
|
- `shipped_at`: 发货时间
|
||||||
|
- `completed_at`: 完成时间
|
||||||
|
|
||||||
|
### 已完成的工作
|
||||||
|
|
||||||
|
#### 1. 创建 OpenSpec 文档
|
||||||
|
- ✅ `proposal.md` - 提案说明
|
||||||
|
- ✅ `specs/exchange-flow-type-display/spec.md` - 详细规格
|
||||||
|
- ✅ `tasks.md` - 任务清单
|
||||||
|
|
||||||
|
#### 2. 代码实现
|
||||||
|
**修改文件:** `pages/device-exchange/device-exchange.vue`
|
||||||
|
|
||||||
|
**新增显示内容:**
|
||||||
|
```vue
|
||||||
|
<!-- 换货类型 - 始终显示 -->
|
||||||
|
<view class="info-row flex-row-sb">
|
||||||
|
<view class="info-label">换货类型</view>
|
||||||
|
<view class="info-value">{{ exchangeData.flow_type_name || '物流换货' }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 发货时间 - 条件显示 -->
|
||||||
|
<view class="info-row flex-row-sb" v-if="exchangeData.shipped_at">
|
||||||
|
<view class="info-label">发货时间</view>
|
||||||
|
<view class="info-value">{{ formatTime(exchangeData.shipped_at) }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 完成时间 - 条件显示 -->
|
||||||
|
<view class="info-row flex-row-sb" v-if="exchangeData.completed_at && exchangeData.status === 4">
|
||||||
|
<view class="info-label">完成时间</view>
|
||||||
|
<view class="info-value">{{ formatTime(exchangeData.completed_at) }}</view>
|
||||||
|
</view>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关键设计决策
|
||||||
|
|
||||||
|
#### 1. 后端保证的行为
|
||||||
|
- `GET /api/c/v1/exchange/pending` 仅返回 `shipping` 类型的进行中换货单
|
||||||
|
- `direct` 类型创建后立即完成,不会出现在待处理列表
|
||||||
|
- 前端无需额外过滤或判断
|
||||||
|
|
||||||
|
#### 2. 向后兼容
|
||||||
|
- 历史数据没有 `flow_type_name` 时,默认显示"物流换货"
|
||||||
|
- 新增字段都是条件显示,不影响现有数据展示
|
||||||
|
- 原有的按钮和表单逻辑保持不变
|
||||||
|
|
||||||
|
#### 3. 无需修改的部分
|
||||||
|
- ✅ API 模块 (`api/modules/exchange.js`) - 自动透传所有响应字段
|
||||||
|
- ✅ 表单提交逻辑 - 后端负责流程类型校验
|
||||||
|
- ✅ 按钮显示逻辑 - 因为 direct 类型不会进入待处理状态
|
||||||
|
|
||||||
|
### 显示逻辑
|
||||||
|
|
||||||
|
| 字段 | 显示条件 | 默认值 |
|
||||||
|
|------|---------|--------|
|
||||||
|
| 换货类型 | 始终显示 | "物流换货"(当 `flow_type_name` 为空时) |
|
||||||
|
| 发货时间 | `shipped_at` 不为空 | 不显示 |
|
||||||
|
| 完成时间 | `completed_at` 不为空 且 `status === 4` | 不显示 |
|
||||||
|
|
||||||
|
### 测试场景
|
||||||
|
|
||||||
|
需要测试的场景(待后端接口就绪后验证):
|
||||||
|
|
||||||
|
1. **待填写信息** (`status: 1`)
|
||||||
|
- 显示:换货类型、换货原因、申请时间
|
||||||
|
- 显示"填写信息"按钮
|
||||||
|
|
||||||
|
2. **待发货** (`status: 2`)
|
||||||
|
- 显示:换货类型、换货原因、申请时间、收件信息
|
||||||
|
- 不显示发货时间
|
||||||
|
|
||||||
|
3. **已发货** (`status: 3`)
|
||||||
|
- 显示:换货类型、换货原因、申请时间、发货时间、收件信息
|
||||||
|
- 不显示按钮
|
||||||
|
|
||||||
|
4. **已完成** (`status: 4`)
|
||||||
|
- 显示:换货类型、换货原因、申请时间、发货时间、完成时间、收件信息
|
||||||
|
- 不显示按钮
|
||||||
|
|
||||||
|
5. **无待处理换货单**
|
||||||
|
- 显示空状态:"暂无换货记录"
|
||||||
|
|
||||||
|
### API 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": 123,
|
||||||
|
"exchange_no": "EXC20260603001",
|
||||||
|
"old_asset_type": "iot_card",
|
||||||
|
"old_identifier": "8986012345678901234",
|
||||||
|
"exchange_reason": "设备损坏",
|
||||||
|
"status": 2,
|
||||||
|
"status_text": "待发货",
|
||||||
|
"flow_type": "shipping",
|
||||||
|
"flow_type_name": "物流换货",
|
||||||
|
"shipped_at": null,
|
||||||
|
"completed_at": null,
|
||||||
|
"created_at": "2026-06-03T10:00:00+08:00",
|
||||||
|
"recipient_name": "张三",
|
||||||
|
"recipient_phone": "13800138000",
|
||||||
|
"recipient_address": "北京市朝阳区xxx路xxx号"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 影响范围
|
||||||
|
|
||||||
|
- **修改文件**: 1 个 (`pages/device-exchange/device-exchange.vue`)
|
||||||
|
- **新增文档**: 3 个(proposal, spec, tasks)
|
||||||
|
- **影响用户**: 使用换货功能的客户端用户
|
||||||
|
- **兼容性**: 完全向后兼容,不影响现有流程
|
||||||
|
|
||||||
|
### 后续工作
|
||||||
|
|
||||||
|
- [ ] 等待后端接口部署
|
||||||
|
- [ ] 在测试环境验证所有测试场景
|
||||||
|
- [ ] 确认不同状态下的显示效果
|
||||||
|
- [ ] 验证历史数据的兼容性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**实施日期**: 2026-06-03
|
||||||
|
**实施人员**: Claude
|
||||||
|
**状态**: ✅ 代码实现完成,待测试验证
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
The backend exchange system now supports two flow types: **shipping** (logistics-based exchange requiring address collection and shipment) and **direct** (immediate completion without logistics). The current frontend client only handles the original shipping flow and does not process the new `flow_type`, `flow_type_name`, `shipped_at`, and `completed_at` response fields.
|
||||||
|
|
||||||
|
This prevents the frontend from correctly displaying exchange records that may include both flow types and from adapting UI behavior based on flow type constraints.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Update the exchange pending query and shipping info submission to handle new response fields: `flow_type`, `flow_type_name`, `shipped_at`, `completed_at`
|
||||||
|
- Preserve existing behavior where the pending exchange API only returns `shipping` type exchanges (status 1/2/3), as `direct` exchanges are completed immediately and never appear in pending state
|
||||||
|
- Display flow type information in the exchange detail view
|
||||||
|
- Ensure shipping info submission validates that the exchange is `flow_type = shipping` and `status = 1` (backend enforces this, frontend should handle error gracefully)
|
||||||
|
- No UI flow changes required since `direct` type exchanges will not appear in the pending list by design
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
- `exchange-flow-type-display`: Display flow type and completion timestamps in exchange records
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
- `exchange-pending-query`: Handle new response fields from `/api/c/v1/exchange/pending`
|
||||||
|
- `exchange-shipping-submission`: Handle new response fields from `/api/c/v1/exchange/{id}/shipping-info`
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected code: `pages/device-exchange/device-exchange.vue`, `api/modules/exchange.js`
|
||||||
|
- Affected API usage: `/api/c/v1/exchange/pending`, `/api/c/v1/exchange/{id}/shipping-info`
|
||||||
|
- No breaking changes: existing shipping flow behavior remains unchanged
|
||||||
|
- Backend ensures `direct` type exchanges are not returned by pending query, so frontend maintains current UX
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
# Exchange Flow Type Display Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Update the exchange display to show the new flow type and timestamp fields returned by the backend exchange APIs.
|
||||||
|
|
||||||
|
## API Response Changes
|
||||||
|
|
||||||
|
### GET /api/c/v1/exchange/pending
|
||||||
|
|
||||||
|
**New Response Fields:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"msg": "success",
|
||||||
|
"timestamp": "2026-06-03T10:00:00+08:00",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"exchange_no": "EXC20260603001",
|
||||||
|
"order_no": "EXC20260603001",
|
||||||
|
"old_asset_type": "iot_card",
|
||||||
|
"old_identifier": "8986012345678901234",
|
||||||
|
"exchange_reason": "设备损坏",
|
||||||
|
"status": 2,
|
||||||
|
"status_text": "待发货",
|
||||||
|
"flow_type": "shipping",
|
||||||
|
"flow_type_name": "物流换货",
|
||||||
|
"shipped_at": null,
|
||||||
|
"completed_at": null,
|
||||||
|
"created_at": "2026-06-03T10:00:00+08:00",
|
||||||
|
"recipient_name": "张三",
|
||||||
|
"recipient_phone": "13800138000",
|
||||||
|
"recipient_address": "北京市朝阳区xxx路xxx号"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Field Descriptions:**
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `flow_type` | string | 流程类型: `shipping` (物流换货) or `direct` (直接换货)。历史数据为空时视为 `shipping` |
|
||||||
|
| `flow_type_name` | string | 流程类型名称: "物流换货" or "直接换货" |
|
||||||
|
| `shipped_at` | string\|null | 发货时间,仅 `shipping` 流程发货后有值 |
|
||||||
|
| `completed_at` | string\|null | 换货完成时间,换货完成后有值 |
|
||||||
|
|
||||||
|
**Backend Behavior:**
|
||||||
|
|
||||||
|
- Only returns exchanges where `flow_type = shipping` AND `status IN (1, 2, 3)`
|
||||||
|
- `direct` type exchanges are never returned (they complete immediately on creation)
|
||||||
|
- If no pending exchange exists, returns `data: null`
|
||||||
|
|
||||||
|
### POST /api/c/v1/exchange/{id}/shipping-info
|
||||||
|
|
||||||
|
**Response includes the same new fields as above**
|
||||||
|
|
||||||
|
**Backend Constraints:**
|
||||||
|
|
||||||
|
- Only accepts `flow_type = shipping` AND `status = 1`
|
||||||
|
- Returns 400 error if `flow_type = direct`: `{"code": 400, "message": "流程类型不支持该操作"}`
|
||||||
|
- Returns 400 error if status is not 1: `{"code": 400, "message": "当前状态不允许填写收货信息"}`
|
||||||
|
|
||||||
|
## UI Changes
|
||||||
|
|
||||||
|
### Exchange Detail Card
|
||||||
|
|
||||||
|
Add flow type display in the exchange info section:
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```
|
||||||
|
换货原因: 设备损坏
|
||||||
|
申请时间: 2026-06-03 10:00:00
|
||||||
|
收件人: 张三
|
||||||
|
联系电话: 13800138000
|
||||||
|
收货地址: 北京市朝阳区xxx路xxx号
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```
|
||||||
|
换货类型: 物流换货
|
||||||
|
换货原因: 设备损坏
|
||||||
|
申请时间: 2026-06-03 10:00:00
|
||||||
|
发货时间: 2026-06-04 15:30:00 (仅已发货时显示)
|
||||||
|
完成时间: 2026-06-05 10:00:00 (仅已完成时显示)
|
||||||
|
收件人: 张三
|
||||||
|
联系电话: 13800138000
|
||||||
|
收货地址: 北京市朝阳区xxx路xxx号
|
||||||
|
```
|
||||||
|
|
||||||
|
**Display Rules:**
|
||||||
|
|
||||||
|
1. **换货类型**: Always display `flow_type_name` if present, fallback to "物流换货" if empty
|
||||||
|
2. **发货时间**: Only display if `shipped_at` is not null
|
||||||
|
3. **完成时间**: Only display if `completed_at` is not null and `status === 4`
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
When submitting shipping info fails with flow type error:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Existing error handling should catch and display backend error message
|
||||||
|
catch (error) {
|
||||||
|
console.error('提交换货信息失败', error);
|
||||||
|
// Backend will return: {"code": 400, "message": "流程类型不支持该操作"}
|
||||||
|
// Default error toast will show this message
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1. Update API Module Types
|
||||||
|
|
||||||
|
No changes needed to `api/modules/exchange.js` - it already passes through all response fields.
|
||||||
|
|
||||||
|
### 2. Update Exchange Page Template
|
||||||
|
|
||||||
|
File: `pages/device-exchange/device-exchange.vue`
|
||||||
|
|
||||||
|
Add flow type and timestamp displays:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<view class="exchange-info">
|
||||||
|
<view class="info-row flex-row-sb">
|
||||||
|
<view class="info-label">换货类型</view>
|
||||||
|
<view class="info-value">{{ exchangeData.flow_type_name || '物流换货' }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="info-row flex-row-sb">
|
||||||
|
<view class="info-label">换货原因</view>
|
||||||
|
<view class="info-value">{{ exchangeData.exchange_reason }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="info-row flex-row-sb">
|
||||||
|
<view class="info-label">申请时间</view>
|
||||||
|
<view class="info-value">{{ formatTime(exchangeData.created_at) }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="info-row flex-row-sb" v-if="exchangeData.shipped_at">
|
||||||
|
<view class="info-label">发货时间</view>
|
||||||
|
<view class="info-value">{{ formatTime(exchangeData.shipped_at) }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="info-row flex-row-sb" v-if="exchangeData.completed_at && exchangeData.status === 4">
|
||||||
|
<view class="info-label">完成时间</view>
|
||||||
|
<view class="info-value">{{ formatTime(exchangeData.completed_at) }}</view>
|
||||||
|
</view>
|
||||||
|
<!-- Existing recipient fields -->
|
||||||
|
</view>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. No Button Logic Changes
|
||||||
|
|
||||||
|
Since `direct` type exchanges never appear in pending (they complete immediately), the existing button visibility logic remains valid:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<view class="exchange-actions" v-if="exchangeData.status === 1">
|
||||||
|
<button class="btn-apple btn-primary" @tap="openPopup">填写信息</button>
|
||||||
|
</view>
|
||||||
|
```
|
||||||
|
|
||||||
|
Backend enforces that only `shipping` + status 1 can submit shipping info, so frontend doesn't need additional flow type checks.
|
||||||
|
|
||||||
|
## Testing Scenarios
|
||||||
|
|
||||||
|
### 1. Shipping Flow Exchange (Existing Behavior)
|
||||||
|
|
||||||
|
- Load pending exchange with `flow_type: "shipping"`, `status: 1`
|
||||||
|
- Verify "换货类型: 物流换货" displays
|
||||||
|
- Verify "填写信息" button shows
|
||||||
|
- Submit shipping info successfully
|
||||||
|
- Verify response includes new fields
|
||||||
|
|
||||||
|
### 2. Shipped Exchange
|
||||||
|
|
||||||
|
- Load exchange with `flow_type: "shipping"`, `status: 3`, `shipped_at: "2026-06-04T15:30:00+08:00"`
|
||||||
|
- Verify "发货时间" row displays
|
||||||
|
- Verify no action buttons show
|
||||||
|
|
||||||
|
### 3. Completed Exchange
|
||||||
|
|
||||||
|
- Load exchange with `flow_type: "shipping"`, `status: 4`, `completed_at: "2026-06-05T10:00:00+08:00"`
|
||||||
|
- Verify "完成时间" row displays
|
||||||
|
- Verify no action buttons show
|
||||||
|
|
||||||
|
### 4. No Pending Exchange
|
||||||
|
|
||||||
|
- Call API with no pending exchange
|
||||||
|
- Verify empty state shows: "暂无换货记录"
|
||||||
|
|
||||||
|
### 5. Error Handling (Edge Case)
|
||||||
|
|
||||||
|
If somehow a `direct` type exchange appeared and user tried to submit shipping info (should not happen), backend returns 400 error which existing error handler will toast.
|
||||||
|
|
||||||
|
## Backward Compatibility
|
||||||
|
|
||||||
|
- Historical data without `flow_type` will be treated as `shipping` (per backend spec)
|
||||||
|
- All new fields are optional in frontend display logic
|
||||||
|
- Existing shipping submission flow is unchanged
|
||||||
17
openspec/changes/update-exchange-flow-type-support/tasks.md
Normal file
17
openspec/changes/update-exchange-flow-type-support/tasks.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Update Exchange Detail Display
|
||||||
|
- [x] Add flow type display row in exchange info section
|
||||||
|
- [x] Add shipped_at display row (conditional on value)
|
||||||
|
- [x] Add completed_at display row (conditional on value and status)
|
||||||
|
- [x] Ensure backward compatibility for historical data without flow_type
|
||||||
|
|
||||||
|
## 2. Testing
|
||||||
|
- [ ] Test with shipping flow exchange (status 1, 2, 3)
|
||||||
|
- [ ] Test with completed exchange showing completion time
|
||||||
|
- [ ] Test with shipped exchange showing ship time
|
||||||
|
- [ ] Test empty state when no pending exchange
|
||||||
|
- [ ] Test shipping info submission success with new response fields
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
- [x] `pages/device-exchange/device-exchange.vue`: Added new field displays
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:h5": "uni",
|
"dev:h5": "uni",
|
||||||
"build:h5": "cross-env UNI_INPUT_DIR=. uni build -p h5"
|
"build:h5": "cross-env UNI_INPUT_DIR=. uni build -p h5",
|
||||||
|
"build:h5:production": "cross-env UNI_INPUT_DIR=. uni build -p h5 --mode production"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dcloudio/types": "3.4.19",
|
"@dcloudio/types": "3.4.19",
|
||||||
|
|||||||
@@ -15,6 +15,10 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="exchange-info">
|
<view class="exchange-info">
|
||||||
|
<view class="info-row flex-row-sb">
|
||||||
|
<view class="info-label">换货类型</view>
|
||||||
|
<view class="info-value">{{ exchangeData.flow_type_name || '物流换货' }}</view>
|
||||||
|
</view>
|
||||||
<view class="info-row flex-row-sb">
|
<view class="info-row flex-row-sb">
|
||||||
<view class="info-label">换货原因</view>
|
<view class="info-label">换货原因</view>
|
||||||
<view class="info-value">{{ exchangeData.exchange_reason }}</view>
|
<view class="info-value">{{ exchangeData.exchange_reason }}</view>
|
||||||
@@ -23,6 +27,14 @@
|
|||||||
<view class="info-label">申请时间</view>
|
<view class="info-label">申请时间</view>
|
||||||
<view class="info-value">{{ formatTime(exchangeData.created_at) }}</view>
|
<view class="info-value">{{ formatTime(exchangeData.created_at) }}</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="info-row flex-row-sb" v-if="exchangeData.shipped_at">
|
||||||
|
<view class="info-label">发货时间</view>
|
||||||
|
<view class="info-value">{{ formatTime(exchangeData.shipped_at) }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="info-row flex-row-sb" v-if="exchangeData.completed_at && exchangeData.status === 4">
|
||||||
|
<view class="info-label">完成时间</view>
|
||||||
|
<view class="info-value">{{ formatTime(exchangeData.completed_at) }}</view>
|
||||||
|
</view>
|
||||||
<view class="info-row flex-row-sb" v-if="exchangeData.recipient_name">
|
<view class="info-row flex-row-sb" v-if="exchangeData.recipient_name">
|
||||||
<view class="info-label">收件人</view>
|
<view class="info-label">收件人</view>
|
||||||
<view class="info-value">{{ exchangeData.recipient_name }}</view>
|
<view class="info-value">{{ exchangeData.recipient_name }}</view>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export const BASE_URL = 'https://cmp-api.boss160.cn';
|
export const BASE_URL = import.meta.env.VITE_BASE_URL;
|
||||||
|
|
||||||
export const APP_TYPE = 'official_account';
|
export const APP_TYPE = 'official_account';
|
||||||
|
|||||||
@@ -1,8 +1,39 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import uni from '@dcloudio/vite-plugin-uni'
|
import uni from '@dcloudio/vite-plugin-uni'
|
||||||
|
import { copyFileSync, existsSync, mkdirSync } from 'node:fs'
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
|
||||||
|
const mpVerifyFiles = [
|
||||||
|
'MP_verify_0UoX8yClVgREjgPj.txt',
|
||||||
|
'MP_verify_4E1dVbOcZ9KzTzoc.txt',
|
||||||
|
'MP_verify_yU2Z8mp831jh6QX7.txt'
|
||||||
|
]
|
||||||
|
|
||||||
|
function copyMpVerifyFiles() {
|
||||||
|
return {
|
||||||
|
name: 'copy-mp-verify-files',
|
||||||
|
closeBundle() {
|
||||||
|
const outputDir = resolve(process.cwd(), 'dist/build/h5')
|
||||||
|
|
||||||
|
if (!existsSync(outputDir)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdirSync(outputDir, { recursive: true })
|
||||||
|
|
||||||
|
mpVerifyFiles.forEach((file) => {
|
||||||
|
const source = resolve(process.cwd(), file)
|
||||||
|
|
||||||
|
if (existsSync(source)) {
|
||||||
|
copyFileSync(source, resolve(outputDir, file))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [uni()],
|
plugins: [uni(), copyMpVerifyFiles()],
|
||||||
root: process.cwd(),
|
root: process.cwd(),
|
||||||
css: {
|
css: {
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
|
|||||||
Reference in New Issue
Block a user