diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1f11a55..47c35d8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,8 @@ "permissions": { "allow": [ "Bash(git add:*)", - "Bash(npm list:*)" + "Bash(npm list:*)", + "Bash(find . -type f \\\\\\(-name *.openapi.* -o -name *-spec.yaml -o -name *-spec.yml \\\\\\))" ], "deny": [], "ask": [] diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..d3adb97 --- /dev/null +++ b/.env.development @@ -0,0 +1 @@ +VITE_BASE_URL=https://cmp-api.boss160.cn diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..ffcd2b6 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +VITE_BASE_URL=https://cmp-api.xm-iot.cn diff --git a/docs/换货.md b/docs/换货.md new file mode 100644 index 0000000..f81725e --- /dev/null +++ b/docs/换货.md @@ -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": "直接换货" +} +``` diff --git a/openspec/changes/update-exchange-flow-type-support/IMPLEMENTATION.md b/openspec/changes/update-exchange-flow-type-support/IMPLEMENTATION.md new file mode 100644 index 0000000..17a67c4 --- /dev/null +++ b/openspec/changes/update-exchange-flow-type-support/IMPLEMENTATION.md @@ -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 + + + 换货类型 + {{ exchangeData.flow_type_name || '物流换货' }} + + + + + 发货时间 + {{ formatTime(exchangeData.shipped_at) }} + + + + + 完成时间 + {{ formatTime(exchangeData.completed_at) }} + +``` + +### 关键设计决策 + +#### 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 +**状态**: ✅ 代码实现完成,待测试验证 diff --git a/openspec/changes/update-exchange-flow-type-support/proposal.md b/openspec/changes/update-exchange-flow-type-support/proposal.md new file mode 100644 index 0000000..a7fc24c --- /dev/null +++ b/openspec/changes/update-exchange-flow-type-support/proposal.md @@ -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 diff --git a/openspec/changes/update-exchange-flow-type-support/specs/exchange-flow-type-display/spec.md b/openspec/changes/update-exchange-flow-type-support/specs/exchange-flow-type-display/spec.md new file mode 100644 index 0000000..17565e8 --- /dev/null +++ b/openspec/changes/update-exchange-flow-type-support/specs/exchange-flow-type-display/spec.md @@ -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 + + + 换货类型 + {{ exchangeData.flow_type_name || '物流换货' }} + + + 换货原因 + {{ exchangeData.exchange_reason }} + + + 申请时间 + {{ formatTime(exchangeData.created_at) }} + + + 发货时间 + {{ formatTime(exchangeData.shipped_at) }} + + + 完成时间 + {{ formatTime(exchangeData.completed_at) }} + + + +``` + +### 3. No Button Logic Changes + +Since `direct` type exchanges never appear in pending (they complete immediately), the existing button visibility logic remains valid: + +```vue + + + +``` + +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 diff --git a/openspec/changes/update-exchange-flow-type-support/tasks.md b/openspec/changes/update-exchange-flow-type-support/tasks.md new file mode 100644 index 0000000..6031ccb --- /dev/null +++ b/openspec/changes/update-exchange-flow-type-support/tasks.md @@ -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 diff --git a/package.json b/package.json index 7bb4f89..ddb02e3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "scripts": { "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": { "@dcloudio/types": "3.4.19", diff --git a/pages/device-exchange/device-exchange.vue b/pages/device-exchange/device-exchange.vue index 5bce716..0d469d0 100644 --- a/pages/device-exchange/device-exchange.vue +++ b/pages/device-exchange/device-exchange.vue @@ -15,6 +15,10 @@ + + 换货类型 + {{ exchangeData.flow_type_name || '物流换货' }} + 换货原因 {{ exchangeData.exchange_reason }} @@ -23,6 +27,14 @@ 申请时间 {{ formatTime(exchangeData.created_at) }} + + 发货时间 + {{ formatTime(exchangeData.shipped_at) }} + + + 完成时间 + {{ formatTime(exchangeData.completed_at) }} + 收件人 {{ exchangeData.recipient_name }} diff --git a/utils/env.js b/utils/env.js index c0f6995..761a76b 100644 --- a/utils/env.js +++ b/utils/env.js @@ -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'; diff --git a/vite.config.js b/vite.config.js index ef7d0d1..5234511 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,8 +1,39 @@ import { defineConfig } from 'vite' 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({ - plugins: [uni()], + plugins: [uni(), copyMpVerifyFiles()], root: process.cwd(), css: { preprocessorOptions: { @@ -11,4 +42,4 @@ export default defineConfig({ } } } -}) \ No newline at end of file +})