fetch(add): 订单管理-企业设备
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 3m30s

This commit is contained in:
sexygoat
2026-01-29 15:43:45 +08:00
parent 1812b7a6c4
commit 841cf0442b
58 changed files with 8948 additions and 1164 deletions

View File

@@ -0,0 +1,84 @@
# Change: 企业设备授权管理
## Why
当前系统中已存在企业客户管理和企业卡授权功能,但缺少企业设备授权管理能力。企业客户需要能够查看和管理被授权的设备列表,运营人员需要能够将设备授权给企业客户使用,并支持撤销授权操作。
根据业务需求文档 (`docs/企业设备授权.md`),需要在资产管理模块下新增"企业设备列表"功能,实现以下核心能力:
1. **授权设备给企业** - 支持批量授权,最多100个设备号
2. **查看企业设备列表** - 支持分页和按设备号搜索
3. **撤销设备授权** - 支持批量撤销授权
## What Changes
新增企业设备授权管理功能,包括:
### 类型定义
- 新增 `src/types/api/enterpriseDevice.ts` 文件
- 定义设备列表项、查询参数、分页结果类型
- 定义授权/撤销请求和响应类型
-`src/types/api/index.ts` 中导出新类型
### API 服务层
- 扩展 `EnterpriseService` 类,新增3个方法:
- `allocateDevices(enterpriseId, data)` - POST 授权设备
- `getEnterpriseDevices(enterpriseId, params)` - GET 设备列表
- `recallDevices(enterpriseId, data)` - POST 撤销授权
### 视图层
- 新增 `src/views/asset-management/enterprise-devices/index.vue` 页面
- 实现设备列表展示 (表格、分页、搜索)
- 实现授权设备对话框 (支持批量输入设备号)
- 实现撤销授权功能 (二次确认)
- 实现操作结果展示 (成功/失败统计)
### 路由配置
-`src/router/routesAlias.ts` 添加路由别名
-`src/router/routes/asyncRoutes.ts` 的资产管理模块下添加子路由
### 国际化
-`src/locales/langs/zh.json``en.json` 添加中英文翻译
- 包含菜单、表单、表格、对话框、提示消息等所有文案
## Impact
- **新增模块**: 企业设备授权管理
- **影响范围**:
- 新增文件: `enterpriseDevice.ts`, `enterprise-devices/index.vue`
- 扩展文件: `enterprise.ts` (API), `routesAlias.ts`, `asyncRoutes.ts`, `zh.json`, `en.json`, `index.ts` (types)
- **依赖关系**:
- 依赖现有的 `EnterpriseService` 基础设施
- 依赖已实现的设备管理模块
- 后端 API 已就绪
- **向后兼容性**: 完全兼容,不影响现有功能
- **数据迁移**: 无需数据迁移
## Breaking Changes
无破坏性变更。这是一个纯新增功能,不修改现有代码逻辑。
## Risks
- **低风险**: 功能独立,不影响现有企业卡授权功能
- **低风险**: API 已定义清晰,类型安全有保障
- **中风险**: 需要确保批量操作时的用户体验良好 (处理大量设备号输入和结果展示)
## Alternatives Considered
1. **复用企业卡授权页面** - 不可行,设备和卡的数据结构和操作逻辑不同
2. **在设备管理页面添加企业授权功能** - 不符合业务流程,企业授权属于资产管理范畴
3. **使用单个设备号输入而非批量** - 不满足业务需求,运营人员需要批量授权能力
## Open Questions
1. ✅ 设备号输入格式 - 支持换行或逗号分隔
2. ✅ 最大批量数量 - API 限制最多100个设备号
3. ✅ 失败情况处理 - API 返回成功/失败统计及失败原因列表
4. ✅ 设备列表排序 - 按授权时间倒序
## References
- API 文档: `docs/企业设备授权.md`
- 相关 OpenSpec 变更: `add-device-management`
- 参考实现: 企业卡授权功能 (`enterprise-cards/index.vue`)

View File

@@ -0,0 +1,291 @@
# Spec: Enterprise Device Authorization
## Overview
企业设备授权功能允许运营人员将设备授权给企业客户使用,并支持查看授权设备列表和撤销授权操作。
## ADDED Requirements
### Requirement: System SHALL define enterprise device types
系统必须提供完整的企业设备授权相关类型定义,确保类型安全。
#### Scenario: 定义企业设备列表项类型
**Given** 需要展示企业设备列表
**When** 定义 `EnterpriseDeviceItem` 接口
**Then** 接口必须包含以下字段:
- `device_id: number` - 设备ID
- `device_no: string` - 设备号
- `device_name: string` - 设备名称
- `device_model: string` - 设备型号
- `card_count: number` - 绑定卡数量
- `authorized_at: string` - 授权时间
#### Scenario: 定义设备列表查询参数
**Given** 需要查询和搜索企业设备
**When** 定义 `EnterpriseDeviceListParams` 接口
**Then** 接口必须包含以下可选字段:
- `page?: number` - 页码
- `page_size?: number` - 每页数量
- `device_no?: string` - 设备号模糊搜索
#### Scenario: 定义授权设备请求类型
**Given** 需要授权设备给企业
**When** 定义 `AllocateDevicesRequest` 接口
**Then** 接口必须包含:
- `device_nos: string[]` - 设备号列表 (nullable, 最多100个)
- `remark?: string` - 授权备注
#### Scenario: 定义授权设备响应类型
**Given** 授权操作需要返回详细结果
**When** 定义 `AllocateDevicesResponse` 接口
**Then** 接口必须包含:
- `success_count: number` - 成功数量
- `fail_count: number` - 失败数量
- `authorized_devices: AuthorizedDeviceItem[]` - 已授权设备列表 (nullable)
- `failed_items: FailedDeviceItem[]` - 失败项列表 (nullable)
**And** `AuthorizedDeviceItem` 包含:
- `device_id: number` - 设备ID
- `device_no: string` - 设备号
- `card_count: number` - 绑定卡数量
**And** `FailedDeviceItem` 包含:
- `device_no: string` - 设备号
- `reason: string` - 失败原因
#### Scenario: 定义撤销授权请求类型
**Given** 需要撤销设备授权
**When** 定义 `RecallDevicesRequest` 接口
**Then** 接口必须包含:
- `device_nos: string[]` - 设备号列表 (nullable, 最多100个)
#### Scenario: 定义撤销授权响应类型
**Given** 撤销操作需要返回结果统计
**When** 定义 `RecallDevicesResponse` 接口
**Then** 接口必须包含:
- `success_count: number` - 成功数量
- `fail_count: number` - 失败数量
- `failed_items: FailedDeviceItem[]` - 失败项列表 (nullable)
---
### Requirement: System SHALL provide enterprise device authorization API services
系统必须提供企业设备授权相关的 API 服务方法。
#### Scenario: 授权设备给企业
**Given** 运营人员需要授权设备给企业客户
**When** 调用 `EnterpriseService.allocateDevices(enterpriseId, data)`
**Then** 必须发送 POST 请求到 `/api/admin/enterprises/{id}/allocate-devices`
**And** 请求体必须包含设备号列表和可选备注
**And** 返回授权结果,包含成功/失败统计和详细列表
#### Scenario: 获取企业设备列表
**Given** 需要查看企业的设备列表
**When** 调用 `EnterpriseService.getEnterpriseDevices(enterpriseId, params)`
**Then** 必须发送 GET 请求到 `/api/admin/enterprises/{id}/devices`
**And** 支持分页参数 (page, page_size)
**And** 支持设备号模糊搜索
**And** 返回设备列表和总数
#### Scenario: 撤销设备授权
**Given** 需要撤销企业的设备授权
**When** 调用 `EnterpriseService.recallDevices(enterpriseId, data)`
**Then** 必须发送 POST 请求到 `/api/admin/enterprises/{id}/recall-devices`
**And** 请求体必须包含设备号列表
**And** 返回撤销结果,包含成功/失败统计和失败原因
---
### Requirement: System SHALL provide enterprise device list page
系统必须提供企业设备列表管理页面,支持查询、授权和撤销操作。
#### Scenario: 显示企业设备列表
**Given** 用户访问企业设备列表页面
**When** 页面加载完成
**Then** 必须显示设备列表表格
**And** 表格必须包含以下列:
- 设备ID
- 设备号
- 设备名称
- 设备型号
- 绑定卡数量
- 授权时间
**And** 必须支持分页功能
**And** 必须显示加载状态
#### Scenario: 搜索企业设备
**Given** 设备列表已加载
**When** 用户在搜索框输入设备号
**And** 点击搜索按钮
**Then** 必须根据设备号模糊查询设备
**And** 必须更新设备列表显示
**And** 必须重置到第一页
#### Scenario: 授权设备对话框
**Given** 用户点击"授权设备"按钮
**When** 授权设备对话框打开
**Then** 必须显示设备号输入框 (textarea)
**And** 必须显示备注输入框 (可选)
**And** 必须提示支持的输入格式 (换行或逗号分隔)
**And** 必须提示最多100个设备号限制
**And** 必须有表单验证 (设备号必填)
#### Scenario: 提交授权设备
**Given** 用户在对话框中输入了设备号列表
**When** 用户点击提交按钮
**Then** 必须解析设备号列表 (支持换行和逗号分隔)
**And** 必须去除空白字符和空行
**And** 必须验证设备号数量不超过100个
**And** 必须调用授权 API
**And** 必须显示加载状态
**And** 授权完成后必须展示结果:
- 成功数量
- 失败数量
- 失败设备列表及原因
**And** 如果有成功授权的设备,必须刷新设备列表
**And** 必须关闭对话框
#### Scenario: 撤销设备授权
**Given** 用户选中了要撤销的设备
**When** 用户点击"撤销授权"按钮
**Then** 必须显示二次确认对话框
**And** 确认对话框必须显示将要撤销的设备数量
**When** 用户确认撤销
**Then** 必须调用撤销 API
**And** 必须显示加载状态
**And** 撤销完成后必须展示结果:
- 成功数量
- 失败数量
- 失败设备列表及原因
**And** 如果有成功撤销的设备,必须刷新设备列表
#### Scenario: 错误处理
**Given** API 调用可能失败
**When** API 返回错误
**Then** 必须显示友好的错误提示消息
**And** 必须在控制台记录错误详情
**And** 必须停止加载状态
#### Scenario: 分页切换
**Given** 设备列表超过一页
**When** 用户切换页码或每页数量
**Then** 必须保持当前的搜索条件
**And** 必须重新加载设备列表
**And** 必须显示加载状态
---
### Requirement: System SHALL configure routing for enterprise device list
系统必须为企业设备列表配置正确的路由。
#### Scenario: 注册企业设备列表路由
**Given** 需要访问企业设备列表页面
**When** 配置路由
**Then** 必须在资产管理模块 (`/asset-management`) 下添加子路由
**And** 路由路径必须为 `enterprise-devices`
**And** 路由名称必须为 `EnterpriseDevices`
**And** 必须使用路由别名 `RoutesAlias.EnterpriseDevices`
**And** 必须配置 meta 信息:
- `title: 'menus.assetManagement.enterpriseDevices'`
- `keepAlive: true`
---
### Requirement: System SHALL provide internationalization support
系统必须提供中英文翻译支持。
#### Scenario: 中文翻译
**Given** 系统语言设置为中文
**When** 访问企业设备相关页面
**Then** 所有文本必须显示中文,包括:
- 菜单标题: "企业设备列表"
- 搜索表单标签和占位符
- 表格列名
- 按钮文本
- 对话框标题和内容
- 提示消息
#### Scenario: 英文翻译
**Given** 系统语言设置为英文
**When** 访问企业设备相关页面
**Then** 所有文本必须显示英文,包括:
- 菜单标题: "Enterprise Devices"
- 搜索表单标签和占位符
- 表格列名
- 按钮文本
- 对话框标题和内容
- 提示消息
---
### Requirement: System SHALL optimize user experience
系统必须提供良好的用户体验。
#### Scenario: 批量输入设备号
**Given** 用户需要授权多个设备
**When** 在设备号输入框中输入
**Then** 必须支持以下输入方式:
- 每行一个设备号
- 逗号分隔的设备号
- 混合使用换行和逗号
**And** 系统必须能正确解析所有格式
**And** 必须自动去除首尾空白字符
**And** 必须过滤空行
#### Scenario: 操作结果展示
**Given** 批量操作完成
**When** 显示操作结果
**Then** 必须清晰展示:
- 总共处理的数量
- 成功的数量
- 失败的数量
- 每个失败项的设备号和失败原因
**And** 如果全部成功,必须显示成功提示
**And** 如果部分失败,必须显示警告提示
**And** 如果全部失败,必须显示错误提示
#### Scenario: 表格列管理
**Given** 设备列表表格已显示
**When** 用户点击列管理按钮
**Then** 必须能够选择显示/隐藏的列
**And** 列配置必须被保存
## Related Specs
- 参考现有的企业卡授权功能 (enterpriseCard.ts)
- 依赖现有的企业客户管理功能 (enterprise.ts)
- 关联设备管理模块 (add-device-management)

View File

@@ -0,0 +1,158 @@
# Tasks: Add Enterprise Device Authorization
## Overview
按照从底层到上层的顺序实现企业设备授权功能,确保每一步都可测试和验证。
## Task List
### Phase 1: Type Definitions (Foundation)
1. **创建企业设备类型定义文件**
- 创建 `src/types/api/enterpriseDevice.ts`
- 定义 `EnterpriseDeviceItem` 接口 (设备列表项)
- 定义 `EnterpriseDeviceListParams` 接口 (查询参数)
- 定义 `EnterpriseDevicePageResult` 接口 (分页结果)
- 定义 `AllocateDevicesRequest` 接口 (授权请求)
- 定义 `AllocateDevicesResponse` 接口 (授权响应)
- 定义 `AuthorizedDeviceItem` 接口 (已授权设备项)
- 定义 `FailedDeviceItem` 接口 (失败设备项)
- 定义 `RecallDevicesRequest` 接口 (撤销请求)
- 定义 `RecallDevicesResponse` 接口 (撤销响应)
- **验证**: 运行 `npm run type-check` 确保无类型错误
2. **导出类型定义**
-`src/types/api/index.ts` 中导出新增的类型
- **验证**: 确认其他模块可以正确导入类型
### Phase 2: API Service Layer
3. **扩展 EnterpriseService API 方法**
-`src/api/modules/enterprise.ts` 中添加:
- `allocateDevices(enterpriseId, data)` - 授权设备
- `getEnterpriseDevices(enterpriseId, params)` - 获取设备列表
- `recallDevices(enterpriseId, data)` - 撤销授权
- 导入新增的类型定义
- **验证**: 运行 `npm run type-check` 确保类型正确
### Phase 3: Internationalization
4. **添加中文翻译**
-`src/locales/langs/zh.json``menus.assetManagement` 下添加 `enterpriseDevices` 条目
-`src/locales/langs/zh.json` 添加 `enterpriseDevices` 模块的所有中文文案:
- 页面标题和搜索表单
- 表格列名
- 操作按钮和对话框标题
- 表单标签和提示文本
- 成功/错误消息
- **验证**: 检查 JSON 格式正确性
5. **添加英文翻译**
-`src/locales/langs/en.json` 添加对应的英文翻译
- 确保 key 与中文版本一致
- **验证**: 检查 JSON 格式正确性,切换语言测试
### Phase 4: Routing
6. **添加路由别名**
-`src/router/routesAlias.ts` 添加 `EnterpriseDevices = '/asset-management/enterprise-devices'`
- **验证**: 确认导出正确
7. **配置路由**
-`src/router/routes/asyncRoutes.ts` 的资产管理 (`/asset-management`) 模块下添加企业设备列表路由
- 配置路由 meta 信息 (title, keepAlive)
- **验证**: 运行应用,检查路由注册成功
### Phase 5: UI Components
8. **创建企业设备列表页面**
- 创建 `src/views/asset-management/enterprise-devices/index.vue`
- 实现页面基础结构:
- 使用 `ArtTableFullScreen` 布局
- 使用 `ArtSearchBar` 实现搜索表单 (企业ID,设备号搜索)
- 使用 `ArtTable` 展示设备列表
- 使用 `ArtTableHeader` 添加"授权设备"和"撤销授权"按钮
- **验证**: 页面能正常渲染,无控制台错误
9. **实现设备列表查询功能**
- 实现 `loadDeviceList()` 方法调用 API
- 实现搜索和重置功能
- 实现分页功能
- 添加 loading 状态
- **验证**: 能正确展示设备列表数据,分页工作正常
10. **实现授权设备对话框**
- 创建授权设备对话框
- 使用 `ElForm` + `ElInput` (textarea) 输入设备号列表
- 支持多行输入或逗号分隔
- 添加备注输入框 (可选)
- 实现表单验证 (必填项,格式校验)
- **验证**: 对话框显示正常,表单验证工作
11. **实现授权设备提交逻辑**
- 实现 `handleAllocateDevices()` 方法
- 解析设备号列表 (处理换行和逗号分隔)
- 调用 `EnterpriseService.allocateDevices()` API
- 展示授权结果 (成功数量,失败数量,失败列表)
- 使用 `ElMessageBox``ElDialog` 展示详细结果
- 授权成功后刷新列表
- **验证**: 能成功授权设备,正确处理部分成功/失败情况
12. **实现撤销授权对话框**
- 创建撤销授权对话框
- 使用表格多选模式选择要撤销的设备
- 或者使用输入框输入设备号列表
- 实现二次确认逻辑
- **验证**: 对话框显示正常
13. **实现撤销授权提交逻辑**
- 实现 `handleRecallDevices()` 方法
- 收集选中的设备号
- 调用 `EnterpriseService.recallDevices()` API
- 展示撤销结果 (成功数量,失败数量,失败列表)
- 撤销成功后刷新列表
- **验证**: 能成功撤销授权,正确处理部分成功/失败情况
### Phase 6: Polish & Testing
14. **完善表格列配置**
- 配置表格列 (设备ID,设备号,设备名称,设备型号,绑定卡数量,授权时间)
- 实现列显示/隐藏功能
- 添加时间格式化
- **验证**: 表格数据展示完整美观
15. **添加错误处理**
- 为所有 API 调用添加 try-catch
- 添加友好的错误提示消息
- 处理网络错误和业务错误
- **验证**: 各种错误场景都有适当提示
16. **样式调整**
- 确保页面样式与系统其他页面一致
- 响应式布局适配
- 对话框尺寸和布局优化
- **验证**: 在不同屏幕尺寸下显示正常
17. **最终验证**
- 运行 `npm run type-check` 确保无类型错误
- 运行 `npm run build` 确保能成功构建
- 运行 `openspec validate add-enterprise-device-authorization --strict` 验证 spec
- 手动测试所有功能点
- 测试中英文切换
- **验证**: 所有功能正常,无控制台错误
## Dependencies
- Task 3 依赖 Task 1 (API 需要类型定义)
- Task 8-13 依赖 Task 1-7 (UI 需要 API 和路由)
- Task 14-16 是并行任务,可以同时进行
## Estimated Effort
- Phase 1-2: 30 分钟 (类型和 API)
- Phase 3: 30 分钟 (国际化)
- Phase 4: 15 分钟 (路由)
- Phase 5: 2-3 小时 (UI 实现)
- Phase 6: 30 分钟 (测试和优化)
**Total**: 约 4-4.5 小时

View File

@@ -0,0 +1,43 @@
# Change: Add Order Management System
## Why
The IoT management platform currently lacks order management capabilities. Users need to:
- View and query orders created by customers (personal and agent)
- Track order payment status and details
- Create orders for single card or device purchases
- Cancel pending orders
- View order history with filtering and search
This capability is essential for financial tracking, commission calculation, and overall business operations transparency.
## What Changes
- **NEW**: Order list page with search, filtering, and pagination
- **NEW**: Order API service with full CRUD operations
- **NEW**: TypeScript types for order entities and API contracts
- **NEW**: i18n keys for order management UI
- **NEW**: Router configuration for order management module
The order management module will support:
- Listing orders with filters (payment status, order type, date range, order number)
- Viewing order details including buyer information, order items (packages), and payment details
- Creating orders for single card or device purchases with package selection
- Canceling orders that are in pending payment status
- Displaying payment method, commission status, and order totals
## Impact
- **Affected specs**: `order-management` (new capability)
- **Affected code**:
- `src/types/api/order.ts` (new file - TypeScript types)
- `src/api/modules/order.ts` (new file - API service)
- `src/views/order-management/order-list/index.vue` (new file - order list page)
- `src/router/routes/asyncRoutes.ts` (route configuration)
- `src/router/routesAlias.ts` (route alias)
- `src/locales/langs/zh.json` (Chinese i18n)
- `src/locales/langs/en.json` (English i18n)
- `src/types/api/index.ts` (exports)
- `src/api/modules/index.ts` (exports)
- **Dependencies**: Requires backend APIs at `/api/admin/orders` endpoints
- **Breaking changes**: None (this is a new feature)

View File

@@ -0,0 +1,174 @@
# Order Management Specification
## ADDED Requirements
### Requirement: Order List Display
The system SHALL display a paginated list of orders with comprehensive filtering and search capabilities.
#### Scenario: Display all orders with pagination
- **GIVEN** the user navigates to the order management page
- **WHEN** the page loads
- **THEN** the system displays a table of orders with pagination controls
- **AND** default page size is 20 items
- **AND** table shows columns: ID, order number, buyer type, buyer ID, order type, payment status, total amount, created date, and actions
#### Scenario: Filter orders by payment status
- **GIVEN** the user is on the order list page
- **WHEN** the user selects a payment status filter (pending=1, paid=2, cancelled=3, refunded=4)
- **AND** clicks the search button
- **THEN** the system displays only orders matching the selected payment status
- **AND** pagination resets to page 1
#### Scenario: Filter orders by order type
- **GIVEN** the user is on the order list page
- **WHEN** the user selects an order type filter (single_card or device)
- **AND** clicks the search button
- **THEN** the system displays only orders matching the selected order type
#### Scenario: Search by order number
- **GIVEN** the user is on the order list page
- **WHEN** the user enters an order number in the search field
- **AND** clicks the search button
- **THEN** the system performs an exact match search
- **AND** displays the matching order if found
#### Scenario: Filter by date range
- **GIVEN** the user is on the order list page
- **WHEN** the user selects a start date and/or end date
- **AND** clicks the search button
- **THEN** the system displays orders created within the specified date range
### Requirement: Order Details Viewing
The system SHALL allow users to view detailed information about each order including order items and payment information.
#### Scenario: View order details
- **GIVEN** the user is viewing the order list
- **WHEN** the user clicks the view/detail action for an order
- **THEN** the system displays comprehensive order information including:
- **AND** order basic info (order_no, order_type, buyer_id, buyer_type)
- **AND** payment info (payment_status, payment_method, paid_at, total_amount)
- **AND** order items list (package_id, package_name, quantity, unit_price, amount)
- **AND** commission info (commission_status, commission_config_version)
- **AND** timestamps (created_at, updated_at)
### Requirement: Order Cancellation
The system SHALL allow authorized users to cancel orders that are in pending payment status.
#### Scenario: Cancel a pending order
- **GIVEN** the user is viewing an order with payment_status = 1 (pending)
- **WHEN** the user clicks the cancel action
- **AND** confirms the cancellation in the confirmation dialog
- **THEN** the system sends a cancel request to POST `/api/admin/orders/{id}/cancel`
- **AND** updates the order status to cancelled (3)
- **AND** displays a success message
- **AND** refreshes the order list
#### Scenario: Cannot cancel paid order
- **GIVEN** the user is viewing an order with payment_status = 2 (paid)
- **WHEN** the cancel action is clicked
- **THEN** the system displays an error message "Cannot cancel a paid order"
- **AND** does not send the cancellation request
### Requirement: Order Creation
The system SHALL provide an interface to create orders for single card or device purchases.
#### Scenario: Create single card order
- **GIVEN** the user clicks the create order button
- **WHEN** the user selects order_type = "single_card"
- **AND** selects an IoT card (iot_card_id)
- **AND** selects one or more packages (package_ids)
- **AND** submits the form
- **THEN** the system sends a POST request to `/api/admin/orders` with the order data
- **AND** displays the newly created order details
- **AND** refreshes the order list
#### Scenario: Create device order
- **GIVEN** the user clicks the create order button
- **WHEN** the user selects order_type = "device"
- **AND** selects a device (device_id)
- **AND** selects one or more packages (package_ids, max 10)
- **AND** submits the form
- **THEN** the system creates the order
- **AND** displays a success message
### Requirement: Order Status Display
The system SHALL display order payment status and order type using color-coded badges and human-readable text.
#### Scenario: Display payment status badge
- **GIVEN** an order is displayed in the table
- **WHEN** the payment_status is 1 (pending)
- **THEN** the system displays a warning-type badge with text "待支付"
- **WHEN** the payment_status is 2 (paid)
- **THEN** the system displays a success-type badge with text "已支付"
- **WHEN** the payment_status is 3 (cancelled)
- **THEN** the system displays an info-type badge with text "已取消"
- **WHEN** the payment_status is 4 (refunded)
- **THEN** the system displays a danger-type badge with text "已退款"
#### Scenario: Display order type badge
- **GIVEN** an order is displayed in the table
- **WHEN** the order_type is "single_card"
- **THEN** the system displays text "单卡购买"
- **WHEN** the order_type is "device"
- **THEN** the system displays text "设备购买"
### Requirement: Currency Formatting
The system SHALL display monetary amounts in yuan (元) with proper formatting and conversion from cents.
#### Scenario: Format order total amount
- **GIVEN** an order has total_amount = 50000 (in cents)
- **WHEN** the order is displayed in the table
- **THEN** the system displays "¥500.00" or "500.00 元"
### Requirement: Data Refresh and Real-time Updates
The system SHALL provide manual refresh capabilities and update data after mutations.
#### Scenario: Manual refresh
- **GIVEN** the user is viewing the order list
- **WHEN** the user clicks the refresh button in the table header
- **THEN** the system reloads the current page of orders with current filters
- **AND** maintains the current pagination state
#### Scenario: Auto-refresh after order cancellation
- **GIVEN** the user successfully cancels an order
- **WHEN** the cancellation is confirmed
- **THEN** the system automatically refreshes the order list
- **AND** displays the updated order status
### Requirement: Internationalization Support
The system SHALL provide full internationalization support for order management UI in Chinese and English.
#### Scenario: Display Chinese text
- **GIVEN** the user's language is set to Chinese (zh)
- **WHEN** the order management page is viewed
- **THEN** all UI text displays in Chinese including menu titles, table headers, status labels, and messages
#### Scenario: Display English text
- **GIVEN** the user's language is set to English (en)
- **WHEN** the order management page is viewed
- **THEN** all UI text displays in English including menu titles, table headers, status labels, and messages

View File

@@ -0,0 +1,57 @@
# Implementation Tasks
## 1. Type Definitions
- [ ] 1.1 Create `src/types/api/order.ts` with order types, enums, and interfaces
- [ ] 1.2 Add order type exports to `src/types/api/index.ts`
## 2. API Service Layer
- [ ] 2.1 Create `src/api/modules/order.ts` with OrderService class
- [ ] 2.2 Implement `getOrders()` - list orders with pagination and filters
- [ ] 2.3 Implement `getOrderById()` - fetch single order details
- [ ] 2.4 Implement `createOrder()` - create new order
- [ ] 2.5 Implement `cancelOrder()` - cancel pending order
- [ ] 2.6 Add OrderService export to `src/api/modules/index.ts`
## 3. Internationalization
- [ ] 3.1 Add Chinese translations to `src/locales/langs/zh.json` under `orderManagement` namespace
- [ ] 3.2 Add English translations to `src/locales/langs/en.json` under `orderManagement` namespace
- [ ] 3.3 Include keys for: menu titles, page titles, table columns, search fields, statuses, actions, messages
## 4. Routing Configuration
- [ ] 4.1 Add `OrderList` route alias to `src/router/routesAlias.ts`
- [ ] 4.2 Add order management route group to `src/router/routes/asyncRoutes.ts`
- [ ] 4.3 Configure route with proper icon, title, and keepAlive settings
## 5. UI Components
- [ ] 5.1 Create `src/views/order-management/order-list/index.vue` component skeleton
- [ ] 5.2 Implement search bar with filters (order_no, payment_status, order_type, date range)
- [ ] 5.3 Implement data table with columns (ID, order_no, buyer info, order type, payment status, total amount, created_at, actions)
- [ ] 5.4 Add pagination controls
- [ ] 5.5 Implement view details action (navigate to detail view or show in dialog)
- [ ] 5.6 Implement cancel order action with confirmation dialog
- [ ] 5.7 Add status badges and formatters for payment status and order type
- [ ] 5.8 Format currency amounts (分 to 元 conversion)
- [ ] 5.9 Implement create order button and dialog (optional - can be phase 2)
## 6. Business Logic
- [ ] 6.1 Implement order list data fetching with loading states
- [ ] 6.2 Implement search and filter logic
- [ ] 6.3 Implement pagination handlers
- [ ] 6.4 Implement cancel order with optimistic UI updates
- [ ] 6.5 Add error handling and user feedback (ElMessage)
- [ ] 6.6 Implement date/time formatting using project utilities
## 7. Validation & Polish
- [ ] 7.1 Test search and filtering functionality
- [ ] 7.2 Test pagination and data refresh
- [ ] 7.3 Test cancel order flow
- [ ] 7.4 Verify i18n coverage (switch language and check all text)
- [ ] 7.5 Verify responsive layout and table column configuration
- [ ] 7.6 Code review and cleanup (remove console logs, verify TypeScript types)

View File

@@ -0,0 +1,332 @@
# Design Document: 套餐管理系统实现
## Context
实现完整的套餐管理系统包括4个核心模块。该系统需要支持多级代理商体系的套餐分配和定价管理。
**背景**
- 项目已有类型定义src/types/api/package.ts但使用不同的字段命名和枚举值
- 后端 API 已实现,使用下划线命名(如 `series_name`
- 前端项目统一使用 CommonStatus 枚举0:禁用, 1:启用)
- 参考实现:`/system/role` 页面使用了组件化架构
**约束**
- 必须保留现有类型定义文件,不能破坏现有代码
- 需要兼容后端 API 的字段命名规范
- 需要适配项目的状态枚举规范
## Goals / Non-Goals
### Goals
1. 实现4个核心模块的完整 CRUD 功能
2. 建立统一的 API 服务层,封装后端接口
3. 实现组件化的页面结构,参考 `/system/role`
4. 支持复杂的定价规则(系列加价 vs 单套餐覆盖)
5. 确保数据隔离和权限控制
### Non-Goals
1. 不重构现有的 package.ts 类型定义
2. 不实现套餐的实时统计和报表功能(后续迭代)
3. 不实现套餐批量导入功能(后续迭代)
4. 不实现套餐的版本管理功能
## Decisions
### Decision 1: API 字段命名策略
**问题**后端使用下划线命名snake_case前端类型通常使用驼峰命名camelCase
**决策**
- API 请求/响应保持下划线命名,与后端保持一致
- 创建新的类型文件 `packageManagement.ts`,使用下划线命名
- 在表单提交和响应处理时不做转换,直接使用下划线字段
**理由**
- 减少转换层的复杂性和错误风险
- 与后端 API 文档保持一致,便于对照
- TypeScript 支持下划线字段名,不影响类型安全
**示例**
```typescript
export interface PackageSeriesResponse {
id: number
series_code: string // 下划线命名
series_name: string
status: number
created_at: string
updated_at: string
}
```
### Decision 2: 状态值映射
**问题**:文档中状态是 `1:启用, 2:禁用`,但项目 CommonStatus 是 `0:禁用, 1:启用`
**决策**
- **在常量配置中定义套餐专用的状态枚举**
- **前端页面使用项目统一的 CommonStatus0/1**
- **在 API 服务层进行状态值映射转换**
**映射规则**
```typescript
// 前端 -> 后端
CommonStatus.ENABLED (1) -> API Status (1)
CommonStatus.DISABLED (0) -> API Status (2)
// 后端 -> 前端
API Status (1) -> CommonStatus.ENABLED (1)
API Status (2) -> CommonStatus.DISABLED (0)
```
**理由**
- 保持前端 UI 的一致性
- 避免混淆项目开发者
- 集中在 API 服务层处理差异
### Decision 3: 模块拆分策略
**问题**:是创建单个 package.ts 服务,还是拆分为多个服务文件?
**决策**拆分为4个独立的服务文件
1. `packageSeries.ts` - 套餐系列管理
2. `package.ts` - 套餐管理
3. `myPackage.ts` - 代理可售套餐
4. `shopPackageAllocation.ts` - 单套餐分配
**理由**
- 每个模块功能独立,职责清晰
- 便于维护和扩展
- 符合单一职责原则
- 便于团队协作(不同开发者负责不同模块)
**替代方案**
- 单个 package.ts 文件 - **拒绝**,文件过大,难以维护
### Decision 4: 定价规则实现
**问题**:代理商的套餐成本价有两种计算方式:系列加价和单套餐覆盖。
**决策**
- **后端负责成本价计算**,前端只展示结果
- 前端接收 `price_source` 字段,标识价格来源
- 单套餐分配创建时,保存 `calculated_cost_price`(系列规则计算的价格)供参考
**数据流**
```
1. 系列分配pricing_mode + pricing_value -> 后端计算 -> cost_price
2. 单套餐分配:直接设置 cost_price覆盖系列规则
3. 前端展示price_source 标识使用了哪种规则
```
**理由**
- 计算逻辑复杂,集中在后端便于维护
- 前端只负责展示,降低复杂度
- 保留 calculated_cost_price 便于调试和审计
### Decision 5: 表单验证策略
**问题**:客户端验证 vs 服务端验证。
**决策****双重验证**
- 客户端:使用 Element Plus 的 FormRules 进行基础验证
- 服务端:后端 API 进行完整验证并返回详细错误
**客户端验证规则**
- 必填字段检查
- 长度限制(如系列名称 1-255 字符)
- 数值范围(如套餐时长 1-120 月)
- 格式验证(如价格必须为正整数)
**理由**
- 客户端验证提升用户体验,即时反馈
- 服务端验证保证数据安全性和完整性
- 符合 Web 应用最佳实践
### Decision 6: 页面组件化结构
**问题**:页面结构如何组织?
**决策**:参考 `/system/role` 页面,使用组件化结构:
```vue
<template>
<ArtTableFullScreen>
<ArtSearchBar /> <!-- 搜索栏 -->
<ElCard>
<ArtTableHeader /> <!-- 表格头部刷新列设置操作按钮 -->
<ArtTable /> <!-- 数据表格 -->
<ElDialog /> <!-- 新增/编辑对话框 -->
</ElCard>
</ArtTableFullScreen>
</template>
```
**理由**
- 与项目现有页面风格一致
- 复用成熟的组件,减少开发工作量
- 便于维护和扩展
## Risks / Trade-offs
### Risk 1: 后端 API 未完成
**风险**:后端接口可能尚未实现或与文档不一致。
**缓解措施**
1. 先实现 API 服务层,使用 TypeScript 类型约束
2. 使用 Mock 数据进行前端开发(已有示例)
3. 与后端团队确认 API 规范和联调时间
4. 预留 API 调试和修正时间
### Risk 2: 状态值映射可能遗漏
**风险**:在某些地方忘记转换状态值,导致显示错误。
**缓解措施**
1. 在 API 服务层统一处理转换
2. 创建工具函数封装映射逻辑
3. 编写单元测试覆盖映射函数
4. Code Review 时重点检查状态相关代码
### Risk 3: 定价规则理解偏差
**风险**:对定价规则的理解与实际业务需求有偏差。
**缓解措施**
1. 在实现前与产品确认定价规则
2. 编写测试用例覆盖各种定价场景
3. 在 UI 上清晰展示价格来源和计算方式
4. 预留调整空间,避免硬编码
### Trade-off 1: 类型定义冗余
**取舍**:保留旧的 package.ts 类型定义,新增 packageManagement.ts。
**代价**
- 存在两套类型定义,可能造成混淆
- 占用额外的代码空间
**收益**
- 不影响现有代码,向后兼容
- 新旧系统可以并存,降低迁移风险
- 未来可以逐步迁移到新类型
### Trade-off 2: 状态值映射增加复杂度
**取舍**:在 API 服务层进行状态值转换。
**代价**
- 增加一层转换逻辑
- 可能影响性能(微小)
**收益**
- 前端 UI 保持一致性
- 业务逻辑更清晰
- 便于后续维护
## Migration Plan
### Phase 1: 基础设施1-2天
1. 创建类型定义文件
2. 创建常量配置文件
3. 设置状态映射工具函数
### Phase 2: API 服务层2-3天
1. 实现4个 API 服务模块
2. 编写单元测试(可选)
3. 使用 Mock 数据测试
### Phase 3: 页面实现4-5天
1. 套餐系列管理页面1天
2. 套餐管理页面1.5天)
3. 代理可售套餐页面1天
4. 单套餐分配页面1.5天)
### Phase 4: 集成测试1-2天
1. 与后端 API 联调
2. 端到端功能测试
3. 修复 Bug 和优化
### Phase 5: 上线1天
1. Code Review
2. 合并代码
3. 部署到测试环境
4. 部署到生产环境
**总计**9-13 个工作日
### Rollback Plan
如果出现严重问题,回滚步骤:
1. 从 Git 回滚到上一个稳定版本
2. 移除新增的路由配置
3. 移除新增的 API 服务导出
4. 通知用户功能暂时不可用
### Decision 7: 错误处理策略
**问题**:如何统一处理各类错误和异常?
**决策**:分层错误处理机制
- **网络错误**axios 拦截器统一捕获,显示通用错误提示
- **401 未认证**:自动跳转到登录页面
- **403 无权限**:显示权限不足提示,不跳转
- **400 业务错误**根据错误信息显示具体提示ElMessage.error
- **表单验证错误**:在表单字段下显示错误提示
**错误提示方式**
```typescript
// 网络错误或服务器错误
ElMessage.error('网络错误,请稍后重试')
// 业务错误(后端返回的具体错误)
ElMessage.error(res.message || '操作失败')
// 操作成功
ElMessage.success('操作成功')
```
**理由**
- 统一的错误处理提升用户体验
- 分层处理避免重复代码
- 清晰的错误提示帮助用户理解问题
### Decision 8: Loading 状态管理
**问题**:如何管理各种操作的加载状态?
**决策**:细粒度的 loading 状态管理
**Loading 状态分类**
```typescript
const loading = ref(false) // 表格数据加载
const submitLoading = ref(false) // 表单提交
const deleteLoading = ref<Record<number, boolean>>({}) // 删除操作(可选)
```
**状态管理规则**
- **列表查询**:表格显示 loading 遮罩
- **新增/编辑提交**:提交按钮显示 loading禁用表单
- **删除操作**:可选择在按钮上显示 loading 或全局 loading
- **状态切换**ElSwitch 自带 loading 效果,先更新 UI 再调用 API
**理由**
- 细粒度控制提供更好的交互反馈
- 防止重复提交
- 清晰标识正在进行的操作
## Open Questions
1. **Q**: 套餐被删除后,历史订单如何处理?
**A**: 待产品确认,可能需要软删除机制
2. **Q**: 代理商可以自行调整套餐售价吗?
**A**: 待产品确认,当前设计只展示建议售价
3. **Q**: 套餐系列和套餐是否支持批量操作(批量启用/禁用)?
**A**: 当前不支持,后续迭代考虑
4. **Q**: 是否需要套餐变更历史记录?
**A**: 后端可能有审计日志,前端暂不展示
5. **Q**: 单套餐分配的"原计算成本价"是否需要实时更新?
**A**: 待确认,当前设计是创建时计算一次,不自动更新

View File

@@ -0,0 +1,143 @@
# Change: 套餐管理系统实现
## Why
根据业务需求文档docs/套餐.md需要实现完整的套餐管理系统包括4个核心模块
1. **套餐系列管理** - 管理套餐的分类和组织
2. **套餐管理** - 管理具体的套餐产品(流量、价格、时长等)
3. **代理可售套餐** - 代理商查看被分配的可售套餐及定价
4. **单套餐分配** - 为代理商分配特定套餐并设置成本价
当前系统虽然有套餐相关的类型定义src/types/api/package.ts和部分页面骨架但缺少完整的 API 对接和业务逻辑实现。此变更将实现完整的套餐管理能力。
## What Changes
### 1. API 层实现
采用模块化设计,拆分为 4 个独立的 API 服务文件:
- **新增**: `src/api/modules/packageSeries.ts` - 套餐系列 API 服务
- 套餐系列列表查询(分页、筛选)
- 创建套餐系列
- 获取套餐系列详情
- 更新套餐系列
- 删除套餐系列
- 更新套餐系列状态
- **新增**: `src/api/modules/package.ts` - 套餐管理 API 服务
- 套餐列表查询(分页、多条件筛选)
- 创建套餐
- 获取套餐详情
- 更新套餐
- 删除套餐
- 更新套餐状态
- 更新套餐上架状态
- 获取系列下拉选项(用于表单选择)
- **新增**: `src/api/modules/myPackage.ts` - 代理可售套餐 API 服务
- 我的可售套餐列表查询
- 获取可售套餐详情
- 我的被分配系列列表
- **新增**: `src/api/modules/shopPackageAllocation.ts` - 单套餐分配 API 服务
- 单套餐分配列表查询
- 创建单套餐分配
- 获取单套餐分配详情
- 更新单套餐分配
- 删除单套餐分配
- 更新单套餐分配状态
- **修改**: `src/api/modules/index.ts` - 导出新增的服务模块
- 导出 PackageSeriesService
- 导出 PackageService
- 导出 MyPackageService
- 导出 ShopPackageAllocationService
### 2. 类型定义增强
- **新增**: `src/types/api/packageManagement.ts` - 完整的套餐管理类型定义
- 匹配文档的 API 字段(下划线命名)
- 包含所有请求/响应类型
- 包含分页结果类型
### 3. 页面实现
**套餐系列管理** (`src/views/package-management/package-series/index.vue`)
- 列表展示(支持名称搜索、状态筛选)
- 新增/编辑套餐系列
- 删除套餐系列
- 状态开关(启用/禁用)
**套餐管理** (`src/views/package-management/package-list/index.vue`)
- 列表展示(支持多条件筛选:名称、系列、状态、上架状态、套餐类型)
- 新增/编辑套餐
- 删除套餐
- 状态开关(启用/禁用)
- 上架状态开关(上架/下架)
**代理可售套餐** (`src/views/package-management/my-packages/index.vue`)
- 查看被分配的套餐列表(支持系列、类型筛选)
- 查看套餐详情(成本价、建议售价、利润空间等)
- 查看被分配系列列表
**单套餐分配** (`src/views/package-management/package-assign/index.vue`)
- 分配列表(支持店铺、套餐、状态筛选)
- 创建分配(选择套餐、店铺、设置成本价)
- 编辑分配(修改成本价)
- 删除分配
- 状态管理(启用/禁用)
### 4. 常量配置
- **新增**: `src/config/constants/package.ts` - 套餐相关常量
- 套餐类型枚举formal/addon
- 流量类型枚举real/virtual
- 上架状态枚举1:上架, 2:下架)
- 定价模式枚举fixed/percent
- 价格来源枚举series_pricing/package_override
### 5. 路由配置
已存在的路由(无需修改):
- `/package-management/package-series` - 套餐系列管理
- `/package-management/package-list` - 套餐管理
- `/package-management/package-assign` - 单套餐分配
需要新增的路由:
- **新增**: `src/router/routesAlias.ts` - 添加路由别名
- `MyPackages = '/package-management/my-packages'` - 代理可售套餐
- **新增**: `src/router/routes/asyncRoutes.ts` - 添加路由配置
- `/package-management/my-packages` - 代理可售套餐页面路由
## Impact
### 受影响的规范
- `package-series-management` - 新增能力
- `package-management` - 新增能力
- `my-packages` - 新增能力
- `shop-package-allocation` - 新增能力
### 受影响的代码
- `src/api/modules/*` - 新增 4 个 API 服务模块
- `src/types/api/*` - 新增类型定义文件
- `src/views/package-management/*` - 4 个页面完整实现
- `src/config/constants/*` - 新增常量配置
- `src/router/routes/asyncRoutes.ts` - 路由配置
### 依赖关系
- 依赖现有的组件库ArtTable、ArtSearchBar、ArtTableHeader 等)
- 依赖现有的 HTTP 请求工具request.ts
- 依赖现有的权限控制和路由守卫
- 依赖 ShopService用于单套餐分配页面的店铺选择器
- 后端 API 需已实现docs/套餐.md 中定义的接口)
**注意事项**
- ShopService 应该已经存在于 src/api/modules/shop.ts
- 如果不存在,需要先实现或使用 Mock 数据
### 风险评估
- **低风险**: 独立模块,不影响现有功能
- **API 依赖**: 需确保后端接口已实现并联调
- **权限控制**: 需配置对应的菜单和按钮权限

View File

@@ -0,0 +1,110 @@
# My Packages (代理可售套餐) Specification
## ADDED Requirements
### Requirement: 我的可售套餐列表查询
系统 SHALL 提供代理商查询被分配套餐的功能。
#### Scenario: 查询当前代理商的可售套餐
- **WHEN** 代理商用户访问可售套餐页面
- **THEN** 系统显示被分配给该代理商的套餐列表
- **AND** 每个套餐包含套餐ID、套餐编码、套餐名称、套餐类型、系列信息
- **AND** 显示成本价cost_price单位
- **AND** 显示建议售价suggested_retail_price单位
- **AND** 显示利润空间profit_margin = 建议售价 - 成本价)
- **AND** 显示价格来源series_pricing:系列加价 / package_override:单套餐覆盖)
- **AND** 显示套餐状态和上架状态
- **AND** 支持分页每页最多100条记录
#### Scenario: 按系列筛选
- **WHEN** 代理商选择特定系列ID筛选
- **THEN** 系统返回该系列下的所有可售套餐
#### Scenario: 按套餐类型筛选
- **WHEN** 代理商选择套餐类型formal/addon筛选
- **THEN** 系统返回该类型的所有可售套餐
### Requirement: 可售套餐详情查询
系统 SHALL 允许代理商查看单个可售套餐的详细信息。
#### Scenario: 查询套餐详情
- **WHEN** 代理商点击查看套餐详情
- **THEN** 系统显示套餐完整信息
- **AND** 包含套餐描述、流量信息、时长等
- **AND** 显示定价详情(成本价、建议售价、利润空间、价格来源)
- **AND** 显示系列信息
#### Scenario: 查询未分配的套餐
- **WHEN** 代理商查询未被分配的套餐ID
- **THEN** 系统返回404错误或无权访问错误
### Requirement: 我的被分配系列列表
系统 SHALL 提供代理商查询被分配系列的功能。
#### Scenario: 查询被分配系列列表
- **WHEN** 代理商访问被分配系列列表
- **THEN** 系统显示分配给该代理商的系列列表
- **AND** 每个系列包含分配ID、系列ID、系列编码、系列名称
- **AND** 显示定价模式fixed:固定金额 / percent:百分比)
- **AND** 显示定价值pricing_value
- **AND** 显示分配者店铺名称
- **AND** 显示可售套餐数量
- **AND** 显示状态
- **AND** 支持分页
### Requirement: 成本价计算规则
系统 SHALL 根据价格来源计算代理商的成本价。
#### Scenario: 系列加价模式series_pricing
- **WHEN** 套餐通过系列分配获得定价
- **AND** 定价模式为 fixed固定金额
- **THEN** 成本价 = 套餐价格 + 固定加价金额
- **WHEN** 定价模式为 percent百分比
- **THEN** 成本价 = 套餐价格 × (1 + 加价百分比)
#### Scenario: 单套餐覆盖模式package_override
- **WHEN** 套餐被单独分配并设置了成本价
- **THEN** 成本价 = 单套餐分配中设置的成本价
- **AND** 价格来源显示为 package_override
### Requirement: 数据隔离
系统 SHALL 确保代理商只能查看被分配给自己的套餐。
#### Scenario: 数据访问隔离
- **WHEN** 代理商查询可售套餐列表
- **THEN** 系统仅返回分配给该代理商的套餐
- **AND** 不显示其他代理商的套餐信息
#### Scenario: 跨代理商访问保护
- **WHEN** 代理商尝试访问未分配给自己的套餐详情
- **THEN** 系统返回403无权访问错误
### Requirement: 权限控制
系统 SHALL 对可售套餐查询功能实施权限控制。
#### Scenario: 仅代理商可访问
- **WHEN** 非代理商用户访问可售套餐接口
- **THEN** 系统返回403无权访问错误
#### Scenario: 未认证用户访问
- **WHEN** 未登录用户访问可售套餐接口
- **THEN** 系统返回401未认证错误

View File

@@ -0,0 +1,159 @@
# Package Management Specification
## ADDED Requirements
### Requirement: 套餐列表查询
系统 SHALL 提供套餐列表查询功能,支持分页和多条件筛选。
#### Scenario: 查询所有套餐
- **WHEN** 用户访问套餐管理页面
- **THEN** 系统显示套餐列表包含套餐编码、套餐名称、系列ID、套餐类型、流量、价格、状态、上架状态等
- **AND** 支持按套餐名称模糊搜索
- **AND** 支持按系列ID筛选
- **AND** 支持按状态筛选(启用/禁用)
- **AND** 支持按上架状态筛选(上架/下架)
- **AND** 支持按套餐类型筛选formal/addon
- **AND** 支持分页每页最多100条记录
#### Scenario: 多条件组合查询
- **WHEN** 用户同时使用多个筛选条件
- **THEN** 系统返回满足所有条件的套餐列表
### Requirement: 创建套餐
系统 SHALL 允许管理员创建新的套餐。
#### Scenario: 成功创建套餐
- **WHEN** 用户填写必填字段(套餐编码、套餐名称、套餐类型、套餐时长、套餐价格)
- **AND** 可选填写系列ID、流量信息、成本价、建议售价等
- **AND** 提交表单
- **THEN** 系统创建套餐,默认状态为启用,默认上架状态为下架
- **AND** 返回创建的套餐详情
#### Scenario: 套餐编码唯一性验证
- **WHEN** 用户使用已存在的套餐编码创建套餐
- **THEN** 系统返回错误提示"套餐编码已存在"
#### Scenario: 验证套餐时长范围
- **WHEN** 套餐时长小于1个月或大于120个月
- **THEN** 系统返回验证错误"套餐时长必须在1-120个月之间"
#### Scenario: 流量类型与流量额度关系
- **WHEN** 流量类型为真流量real
- **THEN** 真流量额度real_data_mb必须大于0
- **AND** 总流量额度data_amount_mb等于真流量额度
- **WHEN** 流量类型为虚流量virtual
- **THEN** 虚流量额度virtual_data_mb必须大于0
- **AND** 真流量额度和虚流量额度之和等于总流量额度
### Requirement: 查看套餐详情
系统 SHALL 允许用户查看单个套餐的详细信息。
#### Scenario: 查询存在的套餐
- **WHEN** 用户通过套餐ID查询详情
- **THEN** 系统返回该套餐的完整信息,包括所有字段
#### Scenario: 查询不存在的套餐
- **WHEN** 用户查询不存在的套餐ID
- **THEN** 系统返回404错误
### Requirement: 更新套餐
系统 SHALL 允许管理员更新套餐信息。
#### Scenario: 成功更新套餐
- **WHEN** 用户修改套餐的可变字段(名称、时长、价格、流量等)
- **AND** 提交更新
- **THEN** 系统更新套餐信息
- **AND** 返回更新后的套餐详情
#### Scenario: 套餐编码不可修改
- **WHEN** 用户尝试修改套餐编码
- **THEN** 系统忽略该字段,不允许修改
### Requirement: 删除套餐
系统 SHALL 允许管理员删除套餐。
#### Scenario: 成功删除套餐
- **WHEN** 用户删除未被使用的套餐
- **THEN** 系统删除该套餐
- **AND** 返回成功状态
#### Scenario: 删除被分配的套餐
- **WHEN** 用户删除已被分配给代理商的套餐
- **THEN** 系统返回错误提示"该套餐已被分配,无法删除"
### Requirement: 套餐状态管理
系统 SHALL 支持套餐启用/禁用状态管理。
#### Scenario: 启用套餐
- **WHEN** 用户将禁用状态的套餐切换为启用
- **THEN** 系统更新状态为启用status=1
#### Scenario: 禁用套餐
- **WHEN** 用户将启用状态的套餐切换为禁用
- **THEN** 系统更新状态为禁用status=2
- **AND** 该套餐将不可用于新的分配或充值
### Requirement: 套餐上架状态管理
系统 SHALL 支持套餐上架/下架状态管理。
#### Scenario: 上架套餐
- **WHEN** 用户将下架状态的套餐切换为上架
- **THEN** 系统更新上架状态为上架shelf_status=1
- **AND** 该套餐将对代理商可见
#### Scenario: 下架套餐
- **WHEN** 用户将上架状态的套餐切换为下架
- **THEN** 系统更新上架状态为下架shelf_status=2
- **AND** 该套餐将对代理商不可见
### Requirement: 套餐类型支持
系统 SHALL 支持两种套餐类型。
#### Scenario: 正式套餐
- **WHEN** 创建套餐类型为 formal 的套餐
- **THEN** 系统记录为正式套餐
#### Scenario: 附加套餐
- **WHEN** 创建套餐类型为 addon 的套餐
- **THEN** 系统记录为附加套餐
### Requirement: 权限控制
系统 SHALL 对套餐管理功能实施权限控制。
#### Scenario: 未认证用户访问
- **WHEN** 未登录用户访问套餐管理接口
- **THEN** 系统返回401未认证错误
#### Scenario: 无权限用户访问
- **WHEN** 已登录但无权限的用户访问套餐管理接口
- **THEN** 系统返回403无权访问错误

View File

@@ -0,0 +1,116 @@
# Package Series Management Specification
## ADDED Requirements
### Requirement: 套餐系列列表查询
系统 SHALL 提供套餐系列列表查询功能,支持分页和条件筛选。
#### Scenario: 查询所有套餐系列
- **WHEN** 用户访问套餐系列管理页面
- **THEN** 系统显示套餐系列列表,包含系列名称、系列编码、描述、状态、创建时间、更新时间
- **AND** 支持按系列名称模糊搜索
- **AND** 支持按状态筛选(启用/禁用)
- **AND** 支持分页每页最多100条记录
#### Scenario: 空列表处理
- **WHEN** 没有符合条件的套餐系列
- **THEN** 系统显示空状态提示
### Requirement: 创建套餐系列
系统 SHALL 允许管理员创建新的套餐系列。
#### Scenario: 成功创建套餐系列
- **WHEN** 用户填写系列编码、系列名称(必填)
- **AND** 可选填写描述最大500字符
- **AND** 提交表单
- **THEN** 系统创建套餐系列,默认状态为启用
- **AND** 返回创建的套餐系列详情
#### Scenario: 系列编码重复
- **WHEN** 用户使用已存在的系列编码创建套餐系列
- **THEN** 系统返回错误提示"系列编码已存在"
#### Scenario: 验证系列名称长度
- **WHEN** 系列名称长度小于1或大于255个字符
- **THEN** 系统返回验证错误
### Requirement: 查看套餐系列详情
系统 SHALL 允许用户查看单个套餐系列的详细信息。
#### Scenario: 查询存在的套餐系列
- **WHEN** 用户通过系列ID查询详情
- **THEN** 系统返回该套餐系列的完整信息
#### Scenario: 查询不存在的套餐系列
- **WHEN** 用户查询不存在的系列ID
- **THEN** 系统返回404错误
### Requirement: 更新套餐系列
系统 SHALL 允许管理员更新套餐系列信息。
#### Scenario: 成功更新套餐系列
- **WHEN** 用户修改系列名称或描述
- **AND** 提交更新
- **THEN** 系统更新套餐系列信息
- **AND** 返回更新后的套餐系列详情
#### Scenario: 系列编码不可修改
- **WHEN** 用户尝试修改系列编码
- **THEN** 系统忽略该字段,不允许修改
### Requirement: 删除套餐系列
系统 SHALL 允许管理员删除套餐系列。
#### Scenario: 成功删除套餐系列
- **WHEN** 用户删除未被套餐使用的系列
- **THEN** 系统删除该套餐系列
- **AND** 返回成功状态
#### Scenario: 删除被使用的套餐系列
- **WHEN** 用户删除已被套餐关联的系列
- **THEN** 系统返回错误提示"该系列下存在套餐,无法删除"
### Requirement: 套餐系列状态管理
系统 SHALL 支持套餐系列状态的开关管理。
#### Scenario: 启用套餐系列
- **WHEN** 用户将禁用状态的系列切换为启用
- **THEN** 系统更新状态为启用status=1
#### Scenario: 禁用套餐系列
- **WHEN** 用户将启用状态的系列切换为禁用
- **THEN** 系统更新状态为禁用status=2
- **AND** 该系列下的套餐可能受到影响(业务规则)
### Requirement: 权限控制
系统 SHALL 对套餐系列管理功能实施权限控制。
#### Scenario: 未认证用户访问
- **WHEN** 未登录用户访问套餐系列管理接口
- **THEN** 系统返回401未认证错误
#### Scenario: 无权限用户访问
- **WHEN** 已登录但无权限的用户访问套餐系列管理接口
- **THEN** 系统返回403无权访问错误

View File

@@ -0,0 +1,164 @@
# Shop Package Allocation (单套餐分配) Specification
## ADDED Requirements
### Requirement: 单套餐分配列表查询
系统 SHALL 提供单套餐分配记录的查询功能。
#### Scenario: 查询所有单套餐分配
- **WHEN** 管理员访问单套餐分配管理页面
- **THEN** 系统显示单套餐分配列表
- **AND** 每条记录包含分配ID、套餐ID、套餐编码、套餐名称
- **AND** 显示被分配的店铺ID和店铺名称
- **AND** 显示覆盖的成本价cost_price单位
- **AND** 显示原计算成本价calculated_cost_price供参考
- **AND** 显示关联的系列分配ID
- **AND** 显示状态、创建时间、更新时间
- **AND** 支持分页每页最多100条记录
#### Scenario: 按店铺筛选
- **WHEN** 管理员选择特定店铺ID筛选
- **THEN** 系统返回该店铺的所有单套餐分配记录
#### Scenario: 按套餐筛选
- **WHEN** 管理员选择特定套餐ID筛选
- **THEN** 系统返回该套餐的所有分配记录
#### Scenario: 按状态筛选
- **WHEN** 管理员选择状态(启用/禁用)筛选
- **THEN** 系统返回符合状态条件的分配记录
### Requirement: 创建单套餐分配
系统 SHALL 允许管理员为店铺分配特定套餐并设置成本价。
#### Scenario: 成功创建单套餐分配
- **WHEN** 管理员选择套餐ID、店铺ID
- **AND** 设置覆盖的成本价必填最小值为0
- **AND** 提交分配
- **THEN** 系统创建单套餐分配记录
- **AND** 计算并保存原计算成本价(基于系列分配规则)
- **AND** 返回创建的分配详情包括关联的系列分配ID
#### Scenario: 重复分配检查
- **WHEN** 管理员为同一店铺分配已分配过的套餐
- **THEN** 系统返回错误提示"该套餐已分配给此店铺"
#### Scenario: 店铺和套餐验证
- **WHEN** 管理员使用不存在的店铺ID或套餐ID
- **THEN** 系统返回错误提示"店铺或套餐不存在"
### Requirement: 查看单套餐分配详情
系统 SHALL 允许管理员查看单个分配记录的详细信息。
#### Scenario: 查询存在的分配记录
- **WHEN** 管理员通过分配ID查询详情
- **THEN** 系统返回该分配记录的完整信息
- **AND** 包含套餐完整信息、店铺信息、定价信息
#### Scenario: 查询不存在的分配记录
- **WHEN** 管理员查询不存在的分配ID
- **THEN** 系统返回404错误
### Requirement: 更新单套餐分配
系统 SHALL 允许管理员更新单套餐分配的成本价。
#### Scenario: 成功更新成本价
- **WHEN** 管理员修改覆盖的成本价
- **AND** 提交更新
- **THEN** 系统更新成本价
- **AND** 返回更新后的分配详情
#### Scenario: 成本价验证
- **WHEN** 管理员设置成本价小于0
- **THEN** 系统返回验证错误"成本价必须大于等于0"
### Requirement: 删除单套餐分配
系统 SHALL 允许管理员删除单套餐分配记录。
#### Scenario: 成功删除分配
- **WHEN** 管理员删除单套餐分配记录
- **THEN** 系统删除该分配记录
- **AND** 该店铺将无法再以此定价售卖该套餐
- **AND** 返回成功状态
#### Scenario: 删除正在使用的分配
- **WHEN** 管理员删除正在被订单使用的分配记录
- **THEN** 系统可能返回警告或阻止删除(根据业务规则)
### Requirement: 单套餐分配状态管理
系统 SHALL 支持单套餐分配的启用/禁用状态管理。
#### Scenario: 启用分配
- **WHEN** 管理员将禁用状态的分配切换为启用
- **THEN** 系统更新状态为启用status=1
- **AND** 该店铺可以使用此定价售卖套餐
#### Scenario: 禁用分配
- **WHEN** 管理员将启用状态的分配切换为禁用
- **THEN** 系统更新状态为禁用status=2
- **AND** 该店铺将无法使用此定价售卖套餐
### Requirement: 成本价优先级规则
系统 SHALL 实现单套餐分配成本价覆盖系列分配规则。
#### Scenario: 单套餐分配优先
- **WHEN** 店铺同时拥有系列分配和单套餐分配
- **THEN** 系统使用单套餐分配的成本价
- **AND** 原计算成本价calculated_cost_price保存系列分配规则计算的价格供参考
#### Scenario: 仅系列分配
- **WHEN** 店铺只有系列分配,没有单套餐分配
- **THEN** 系统使用系列分配规则计算成本价
### Requirement: 关联系列分配追踪
系统 SHALL 追踪单套餐分配与系列分配的关联关系。
#### Scenario: 记录关联的系列分配
- **WHEN** 创建单套餐分配时
- **THEN** 系统记录关联的系列分配IDallocation_id
- **AND** 用于追溯定价来源
### Requirement: 权限控制
系统 SHALL 对单套餐分配管理功能实施权限控制。
#### Scenario: 未认证用户访问
- **WHEN** 未登录用户访问单套餐分配接口
- **THEN** 系统返回401未认证错误
#### Scenario: 无权限用户访问
- **WHEN** 已登录但无权限的用户访问单套餐分配接口
- **THEN** 系统返回403无权访问错误
#### Scenario: 仅管理员可操作
- **WHEN** 非管理员用户尝试创建、更新或删除单套餐分配
- **THEN** 系统返回403无权访问错误

View File

@@ -0,0 +1,156 @@
# Implementation Tasks
## 1. 基础设施准备
- [ ] 1.1 创建套餐管理类型定义文件src/types/api/packageManagement.ts
- [ ] 1.2 创建套餐常量配置文件src/config/constants/package.ts
- [ ] 1.3 导出常量配置到 constants/index.ts
## 2. API 服务层实现
### 2.1 套餐系列 APIpackageSeries.ts
- [ ] 2.1.1 实现 getPackageSeries套餐系列列表
- [ ] 2.1.2 实现 createPackageSeries创建套餐系列
- [ ] 2.1.3 实现 getPackageSeriesDetail获取套餐系列详情
- [ ] 2.1.4 实现 updatePackageSeries更新套餐系列
- [ ] 2.1.5 实现 deletePackageSeries删除套餐系列
- [ ] 2.1.6 实现 updatePackageSeriesStatus更新套餐系列状态
### 2.2 套餐管理 APIpackage.ts
- [ ] 2.2.1 实现 getPackages套餐列表
- [ ] 2.2.2 实现 createPackage创建套餐
- [ ] 2.2.3 实现 getPackageDetail获取套餐详情
- [ ] 2.2.4 实现 updatePackage更新套餐
- [ ] 2.2.5 实现 deletePackage删除套餐
- [ ] 2.2.6 实现 updatePackageStatus更新套餐状态
- [ ] 2.2.7 实现 updatePackageShelfStatus更新套餐上架状态
### 2.3 代理可售套餐 APImyPackage.ts
- [ ] 2.3.1 实现 getMyPackages我的可售套餐列表
- [ ] 2.3.2 实现 getMyPackageDetail获取可售套餐详情
- [ ] 2.3.3 实现 getMySeriesAllocations我的被分配系列列表
### 2.4 单套餐分配 APIshopPackageAllocation.ts
- [ ] 2.4.1 实现 getShopPackageAllocations单套餐分配列表
- [ ] 2.4.2 实现 createShopPackageAllocation创建单套餐分配
- [ ] 2.4.3 实现 getShopPackageAllocationDetail获取单套餐分配详情
- [ ] 2.4.4 实现 updateShopPackageAllocation更新单套餐分配
- [ ] 2.4.5 实现 deleteShopPackageAllocation删除单套餐分配
- [ ] 2.4.6 实现 updateShopPackageAllocationStatus更新单套餐分配状态
- [ ] 2.5 在 src/api/modules/index.ts 中导出所有新服务
## 3. 页面实现
### 3.1 套餐系列管理页面package-series/index.vue
- [ ] 3.1.1 实现列表展示(表格、分页)
- [ ] 3.1.2 实现搜索栏(系列名称、状态筛选)
- [ ] 3.1.3 实现新增对话框(表单验证)
- [ ] 3.1.4 实现编辑功能(复用新增对话框,根据 dialogType 区分新增/编辑)
- [ ] 3.1.5 实现删除功能(二次确认)
- [ ] 3.1.6 实现状态开关(启用/禁用)
- [ ] 3.1.7 集成 API 服务并处理加载状态
### 3.2 套餐管理页面package-list/index.vue
- [ ] 3.2.1 实现列表展示(表格、分页)
- [ ] 3.2.2 实现搜索栏(名称、系列、状态、上架状态、类型筛选)
- [ ] 3.2.3 实现系列下拉选择器(加载套餐系列列表,只显示启用状态)
- [ ] 3.2.4 实现新增对话框(表单验证、系列选择)
- [ ] 3.2.5 实现编辑功能(复用新增对话框,根据 dialogType 区分新增/编辑)
- [ ] 3.2.6 实现删除功能(二次确认)
- [ ] 3.2.7 实现状态开关(启用/禁用)
- [ ] 3.2.8 实现上架状态开关(上架/下架)
- [ ] 3.2.9 集成 API 服务并处理加载状态
### 3.3 代理可售套餐页面my-packages/index.vue
- [ ] 3.3.1 创建页面文件和基本结构
- [ ] 3.3.2 实现列表展示(表格、分页)
- [ ] 3.3.3 实现搜索栏(系列、类型筛选)
- [ ] 3.3.4 实现详情对话框(显示成本价、建议售价、利润空间)
- [ ] 3.3.5 实现被分配系列列表Tab可选
- [ ] 3.3.6 集成 API 服务并处理加载状态
### 3.4 单套餐分配页面package-assign/index.vue
- [ ] 3.4.1 创建页面文件和基本结构
- [ ] 3.4.2 实现列表展示(表格、分页)
- [ ] 3.4.3 实现搜索栏(店铺、套餐、状态筛选)
- [ ] 3.4.4 实现套餐下拉选择器(加载套餐列表,只显示启用且上架的套餐)
- [ ] 3.4.5 实现店铺下拉选择器(使用 ShopService 加载店铺列表)
- [ ] 3.4.6 实现新增对话框(套餐选择、店铺选择、成本价输入)
- [ ] 3.4.7 实现编辑功能(单独对话框或复用新增对话框,只允许修改成本价)
- [ ] 3.4.8 实现删除功能(二次确认)
- [ ] 3.4.9 实现状态管理(启用/禁用开关)
- [ ] 3.4.10 集成 API 服务并处理加载状态
## 4. 路由配置
- [ ] 4.1 在 asyncRoutes.ts 中添加 my-packages 路由配置
- [ ] 4.2 验证路由权限配置正确
## 5. 集成测试
### 5.1 套餐系列管理测试
- [ ] 5.1.1 测试列表查询(空列表、有数据、分页)
- [ ] 5.1.2 测试搜索功能(名称模糊搜索、状态筛选)
- [ ] 5.1.3 测试新增功能(成功、编码重复、字段验证)
- [ ] 5.1.4 测试编辑功能(成功、字段验证)
- [ ] 5.1.5 测试删除功能(成功、有关联套餐时禁止删除)
- [ ] 5.1.6 测试状态切换(启用→禁用、禁用→启用)
- [ ] 5.1.7 测试权限控制(未登录、无权限)
### 5.2 套餐管理测试
- [ ] 5.2.1 测试列表查询(空列表、有数据、分页)
- [ ] 5.2.2 测试多条件筛选(名称、系列、状态、上架状态、类型)
- [ ] 5.2.3 测试系列下拉选择器(只显示启用状态的系列)
- [ ] 5.2.4 测试新增功能(成功、编码重复、时长验证、流量验证)
- [ ] 5.2.5 测试编辑功能(成功、字段验证)
- [ ] 5.2.6 测试删除功能(成功、已分配时禁止删除)
- [ ] 5.2.7 测试状态切换(启用→禁用、禁用→启用)
- [ ] 5.2.8 测试上架状态切换(上架→下架、下架→上架)
- [ ] 5.2.9 测试权限控制(未登录、无权限)
### 5.3 代理可售套餐测试
- [ ] 5.3.1 测试列表查询(空列表、有数据、分页)
- [ ] 5.3.2 测试筛选功能(按系列、按类型)
- [ ] 5.3.3 测试详情查询(显示成本价、建议售价、利润空间、价格来源)
- [ ] 5.3.4 测试数据隔离(只能看到分配给自己的套餐)
- [ ] 5.3.5 测试被分配系列列表(如果实现)
- [ ] 5.3.6 测试权限控制(非代理商用户无法访问)
### 5.4 单套餐分配测试
- [ ] 5.4.1 测试列表查询(空列表、有数据、分页)
- [ ] 5.4.2 测试筛选功能(按店铺、按套餐、按状态)
- [ ] 5.4.3 测试套餐下拉选择器(只显示启用且上架的套餐)
- [ ] 5.4.4 测试店铺下拉选择器(加载店铺列表)
- [ ] 5.4.5 测试新增功能(成功、重复分配、成本价验证)
- [ ] 5.4.6 测试编辑功能(修改成本价)
- [ ] 5.4.7 测试删除功能(成功、有订单时的处理)
- [ ] 5.4.8 测试状态切换(启用→禁用、禁用→启用)
- [ ] 5.4.9 测试价格覆盖规则(单套餐分配优先于系列分配)
- [ ] 5.4.10 测试权限控制(仅管理员可操作)
### 5.5 通用功能测试
- [ ] 5.5.1 测试所有页面的表单验证(必填、长度、格式)
- [ ] 5.5.2 测试所有页面的 loading 状态(列表、提交、删除)
- [ ] 5.5.3 测试所有页面的错误处理(网络错误、业务错误)
- [ ] 5.5.4 测试所有页面的二次确认(删除操作)
- [ ] 5.5.5 测试分页功能(换页、改变每页数量)
- [ ] 5.5.6 测试刷新功能(列表刷新)
- [ ] 5.5.7 测试列显示/隐藏功能
- [ ] 5.5.8 测试状态值映射前端0/1与后端1/2的转换
## 6. 代码优化和文档
- [ ] 6.1 代码格式化和 ESLint 检查
- [ ] 6.2 添加必要的注释
- [ ] 6.3 更新 API 文档(如需要)
- [ ] 6.4 提交代码并创建 PR
## 注意事项
- 所有页面需参考 `/system/role` 页面的组件化结构
- 使用统一的 `CommonStatus` 常量(需要注意文档中的状态值映射)
- API 字段使用下划线命名(如 `series_name`),前端类型使用驼峰命名
- 所有删除操作需要二次确认
- 所有表单需要完整的验证规则
- 统一使用 Element Plus 的 Message 和 MessageBox 组件

View File

@@ -0,0 +1,190 @@
# Design: 套餐系列分配佣金系统重构
## Context
当前系统使用简单的定价模式(pricing_mode/pricing_value)和一次性佣金(one_time_commission_*)来管理套餐系列分配。随着业务发展,需要更复杂的佣金系统:
- 支持基础返佣(固定金额或百分比)
- 支持梯度返佣(根据销量或销售额分档返佣)
- 更清晰的数据模型和API接口
**背景约束**:
- 前后端需要同步部署(Breaking Change)
- 需要数据迁移方案
- 影响现有的套餐系列分配功能
## Goals / Non-Goals
**Goals**:
- 实现新的佣金配置模型,支持基础返佣和梯度返佣
- 提供清晰的UI界面让用户配置复杂的返佣规则
- 保证数据一致性和类型安全
- 提供良好的用户体验(表单验证、错误提示)
**Non-Goals**:
- 不处理历史数据的完整性验证(由后端负责)
- 不实现佣金计算逻辑(由后端负责)
- 不处理佣金结算流程(属于其他模块)
## Decisions
### 1. Data Model Design
**决策**: 采用嵌套对象结构表示佣金配置
**理由**:
- `base_commission: { mode, value }` - 清晰表达基础返佣的两个维度
- `tier_config: { period_type, tier_type, tiers[] }` - 梯度配置与基础配置分离,可选性强
- `tiers: [{ threshold, mode, value }]` - 每个档位都有独立的返佣模式和值
**替代方案考虑**:
- ❌ 平铺所有字段 - 会导致字段过多,语义不清晰
- ❌ 使用JSON字符串存储配置 - 失去类型安全,不利于表单编辑
### 2. UI Design Pattern
**决策**: 使用渐进式表单设计
**表单结构**:
```
1. 基础返佣配置 (必填)
- 返佣模式: 单选 (固定金额/百分比)
- 返佣值: 数字输入
2. 梯度返佣设置 (可选)
- 启用开关: 是/否
- [如果启用]:
- 周期类型: 下拉 (月度/季度/年度)
- 梯度类型: 下拉 (销量/销售额)
- 档位列表: 动态表单
* 阈值
* 返佣模式
* 返佣值
* [添加]/[删除] 按钮
```
**理由**:
- 渐进式设计降低初始复杂度
- 只有启用梯度返佣时才显示相关配置
- 动态档位列表提供灵活性
**替代方案考虑**:
- ❌ 全部平铺展示 - 对不需要梯度返佣的用户造成困扰
- ❌ 使用向导模式 - 增加操作步骤,不适合编辑场景
### 3. Form Validation Strategy
**决策**: 分层验证 + 条件验证
**验证规则**:
1. 基础返佣配置:
- mode: 必选
- value: 必填,>= 0
2. 梯度返佣配置(当启用时):
- period_type: 必选
- tier_type: 必选
- tiers: 至少一个档位
- 每个档位:
- threshold: 必填,> 0
- mode: 必选
- value: 必填,> 0
- 档位阈值必须递增
**实现方式**:
- 使用 Element Plus 的表单验证
- 自定义validator处理档位阈值递增验证
- 使用 computed 动态生成验证规则
### 4. API Response Handling
**决策**: 统一使用 `list` 字段名,添加适配层
**理由**:
- 后端统一规范使用 `list` 而非 `items`
- 添加 `total_pages` 字段提供更完整的分页信息
- 保持前端代码与后端规范一致
**迁移策略**:
```typescript
// Before
const res = await getShopSeriesAllocations(params)
allocationList.value = res.data.items || []
// After
const res = await getShopSeriesAllocations(params)
allocationList.value = res.data.list || []
```
## Risks / Trade-offs
### Risk 1: Breaking Change导致部署协调困难
**风险**: 前后端必须同时部署,否则会出现接口不兼容
**缓解措施**:
- 与后端团队协调部署窗口
- 准备回退方案(前端代码分支)
- 在测试环境充分验证
### Risk 2: 复杂表单导致用户体验问题
**风险**: 梯度返佣配置较复杂,用户可能不理解
**缓解措施**:
- 提供清晰的字段说明
- 添加示例或帮助文档链接
- 使用默认值简化初次配置
### Risk 3: 数据迁移可能失败
**风险**: 旧数据无法完全转换为新模型
**缓解措施**:
- 要求后端提供数据迁移脚本和验证
- 在迁移后检查数据完整性
- 保留旧数据备份
## Migration Plan
### Phase 1: 准备阶段
1. 与后端确认API变更细节和时间表
2. 在开发环境实现前端变更
3. 与后端在测试环境联调
### Phase 2: 测试阶段
1. 功能测试: 创建、编辑、删除、列表展示
2. 集成测试: 与后端API集成
3. 用户验收测试: 业务人员验证
### Phase 3: 部署阶段
1. 准备回退方案
2. 与后端协调部署窗口
3. 同步部署前后端
4. 验证生产环境功能
### Phase 4: 监控阶段
1. 监控API错误率
2. 收集用户反馈
3. 修复遗留问题
### Rollback Plan
如果部署后发现严重问题:
1. 前端回退到上一版本
2. 后端回退API(如果可能)
3. 通知用户暂时不可用
4. 修复问题后重新部署
## Open Questions
1. **Q**: 梯度返佣的档位数量是否有上限?
- **A**: 待后端确认,前端可以先不限制或设置合理上限(如10个)
2. **Q**: 返佣值的单位和精度如何处理?
- **A**: 固定金额使用"分"为单位,百分比使用千分比(如200=20%),待后端确认
3. **Q**: 是否需要在列表页显示梯度返佣信息?
- **A**: 暂时只显示基础返佣,梯度信息在详情或编辑时查看
4. **Q**: 旧数据如何映射到新模型?
- **A**: 待后端提供迁移方案,前端需要能够正确显示迁移后的数据

View File

@@ -0,0 +1,46 @@
# Change: 更新套餐系列分配佣金系统
## Why
当前套餐系列分配使用简单的定价模式(固定加价/百分比加价)和一次性佣金配置,不支持复杂的返佣规则。新的业务需求要求支持:
- 基础返佣配置(固定金额或百分比)
- 梯度返佣系统(根据销量或销售额分档返佣)
- 更灵活的佣金计算模型
## What Changes
**BREAKING** - 完全重构套餐系列分配的数据模型和API接口:
- 移除旧的定价字段: `pricing_mode`, `pricing_value`, `calculated_cost_price`
- 移除旧的一次性佣金字段: `one_time_commission_trigger`, `one_time_commission_threshold`, `one_time_commission_amount`
- 新增基础返佣配置: `base_commission` (包含 mode 和 value)
- 新增梯度返佣开关: `enable_tier_commission`
- 新增梯度返佣配置: `tier_config` (可选,当启用梯度返佣时需要)
- 响应数据结构从 `items` 改为 `list` (符合后端统一规范)
- 更新所有相关的创建/更新接口以支持新的佣金模型
## Impact
- **影响模块**: 套餐系列分配 (`/package-management/series-assign`)
- **受影响文件**:
- `src/types/api/packageManagement.ts` - TypeScript类型定义
- `src/api/modules/shopSeriesAllocation.ts` - API服务层
- `src/views/package-management/series-assign/index.vue` - 前端页面
- **数据迁移**: 需要后端提供数据迁移方案,将旧的定价数据转换为新的佣金配置
- **向后兼容性**: **不兼容**,需要前后端同时部署
## Breaking Changes
1. **API响应结构变更**:
- 列表接口响应从 `{ items, page, page_size, total }` 改为 `{ list, page, page_size, total, total_pages }`
- 移除 `pricing_mode`, `pricing_value`, `calculated_cost_price` 字段
- 移除一次性佣金相关字段
- 新增 `base_commission`, `enable_tier_commission` 字段
2. **API请求结构变更**:
- 创建/更新接口需要新的 `base_commission` 对象
- 支持可选的 `enable_tier_commission``tier_config`
3. **前端组件变更**:
- 表单需要支持基础佣金配置和梯度返佣配置
- 表格列需要显示新的佣金信息

View File

@@ -0,0 +1,225 @@
# Package Series Allocation Spec Delta
## MODIFIED Requirements
### Requirement: 套餐系列分配列表查询
系统 SHALL 提供套餐系列分配列表查询功能,支持按店铺、系列和状态筛选,返回包含新佣金配置的分配信息。
#### Scenario: 成功获取分配列表
- **WHEN** 用户请求套餐系列分配列表
- **THEN** 系统返回分配列表,每项包含:
- 基本信息: id, series_id, series_name, shop_id, shop_name
- 分配者信息: allocator_shop_id, allocator_shop_name
- 佣金配置: base_commission (mode, value), enable_tier_commission
- 状态和时间: status, created_at, updated_at
- **AND** 响应结构为: `{ list, page, page_size, total, total_pages }`
#### Scenario: 按条件筛选分配列表
- **WHEN** 用户提供筛选条件 (shop_id, series_id, status)
- **THEN** 系统返回符合条件的分配列表
- **AND** 支持分页参数 (page, page_size)
### Requirement: 创建套餐系列分配
系统 SHALL 允许用户创建套餐系列分配,配置基础返佣和可选的梯度返佣规则。
#### Scenario: 创建基础返佣分配
- **WHEN** 用户提交创建请求,包含:
- series_id: 套餐系列ID
- shop_id: 被分配的店铺ID
- base_commission: { mode: "fixed"/"percent", value: number }
- **THEN** 系统创建分配记录
- **AND** 返回完整的分配信息包含 allocator_shop_id 和 allocator_shop_name
#### Scenario: 创建带梯度返佣的分配
- **WHEN** 用户提交创建请求,包含:
- series_id, shop_id, base_commission (同上)
- enable_tier_commission: true
- tier_config: { period_type, tier_type, tiers[] }
- period_type: "monthly"/"quarterly"/"yearly"
- tier_type: "sales_count"/"sales_amount"
- tiers: [{ threshold, mode, value }]
- **THEN** 系统创建分配记录并保存梯度配置
- **AND** 梯度档位按阈值升序存储
#### Scenario: 验证梯度档位阈值递增
- **WHEN** 用户提交的梯度档位阈值不是递增的
- **THEN** 系统返回400错误
- **AND** 错误消息说明阈值必须递增
### Requirement: 更新套餐系列分配
系统 SHALL 允许用户更新现有分配的佣金配置,包括基础返佣和梯度返佣。
#### Scenario: 更新基础返佣配置
- **WHEN** 用户提交更新请求,包含新的 base_commission
- **THEN** 系统更新分配记录
- **AND** 返回更新后的完整信息
#### Scenario: 启用或禁用梯度返佣
- **WHEN** 用户更新 enable_tier_commission 标志
- **THEN** 系统更新配置
- **AND** 如果启用,必须提供有效的 tier_config
- **AND** 如果禁用,tier_config 可以为空
#### Scenario: 更新梯度返佣配置
- **WHEN** 用户提交包含新 tier_config 的更新请求
- **THEN** 系统替换整个梯度配置
- **AND** 验证新档位阈值递增
- **AND** 返回更新后的信息
### Requirement: 删除套餐系列分配
系统 SHALL 允许用户删除套餐系列分配记录。
#### Scenario: 成功删除分配
- **WHEN** 用户请求删除指定ID的分配
- **THEN** 系统删除该分配记录
- **AND** 返回成功响应 (200)
#### Scenario: 删除不存在的分配
- **WHEN** 用户请求删除不存在的分配ID
- **THEN** 系统返回404错误
- **AND** 错误消息说明分配不存在
### Requirement: 获取套餐系列分配详情
系统 SHALL 提供单个分配记录的详细信息查询。
#### Scenario: 成功获取详情
- **WHEN** 用户请求指定ID的分配详情
- **THEN** 系统返回完整的分配信息,包括:
- 所有基本字段
- base_commission 对象
- enable_tier_commission 标志
- tier_config (如果启用了梯度返佣)
### Requirement: 更新套餐系列分配状态
系统 SHALL 允许用户启用或禁用套餐系列分配。
#### Scenario: 切换分配状态
- **WHEN** 用户提交状态更新请求 (status: 1启用/2禁用)
- **THEN** 系统更新分配状态
- **AND** 返回成功响应
#### Scenario: 禁用分配后的行为
- **WHEN** 分配被禁用 (status: 2)
- **THEN** 该分配不再参与佣金计算 (由后端业务逻辑保证)
- **AND** 前端列表中状态显示为"禁用"
## REMOVED Requirements
### Requirement: 定价模式配置
**Reason**: 旧的定价模式 (pricing_mode, pricing_value) 已被新的佣金模型替代
**Migration**: 旧数据通过后端迁移脚本转换为基础返佣配置:
- pricing_mode="fixed" → base_commission.mode="fixed"
- pricing_mode="percentage" → base_commission.mode="percent"
- pricing_value → base_commission.value (需要单位转换)
### Requirement: 一次性佣金配置
**Reason**: 一次性佣金配置 (one_time_commission_*) 已被梯度返佣系统替代
**Migration**: 旧的一次性佣金通过以下方式迁移:
- 如果设置了一次性佣金,转换为单档位的梯度返佣
- trigger → tier_type mapping (first_activation → sales_count, cumulative_recharge → sales_amount)
- threshold → tiers[0].threshold
- amount → tiers[0].value (mode设为fixed)
### Requirement: 计算成本价字段
**Reason**: calculated_cost_price 字段在新模型中不再使用
**Migration**: 该字段不再返回,成本计算由后端业务逻辑内部处理
## ADDED Requirements
### Requirement: 基础返佣配置
系统 SHALL 为每个分配提供基础返佣配置,包含返佣模式和返佣值。
#### Scenario: 固定金额返佣
- **WHEN** base_commission.mode = "fixed"
- **THEN** base_commission.value 表示固定返佣金额(单位:分)
- **AND** 每笔交易返佣该固定金额
#### Scenario: 百分比返佣
- **WHEN** base_commission.mode = "percent"
- **THEN** base_commission.value 表示返佣百分比的千分比 (如200表示20%)
- **AND** 每笔交易返佣 = 交易金额 * (value / 1000)
### Requirement: 梯度返佣配置
系统 SHALL 支持可选的梯度返佣配置,根据周期内的销量或销售额分档返佣。
#### Scenario: 按销量分档返佣
- **WHEN** tier_config.tier_type = "sales_count"
- **THEN** 系统根据周期内的销售数量匹配档位
- **AND** 达到档位阈值后,该档位的返佣规则生效
#### Scenario: 按销售额分档返佣
- **WHEN** tier_config.tier_type = "sales_amount"
- **THEN** 系统根据周期内的销售金额(分)匹配档位
- **AND** 达到档位阈值后,该档位的返佣规则生效
#### Scenario: 月度返佣周期
- **WHEN** tier_config.period_type = "monthly"
- **THEN** 系统按自然月统计销量或销售额
- **AND** 每月初重置计数
#### Scenario: 季度返佣周期
- **WHEN** tier_config.period_type = "quarterly"
- **THEN** 系统按季度统计销量或销售额
- **AND** 每季度初重置计数
#### Scenario: 年度返佣周期
- **WHEN** tier_config.period_type = "yearly"
- **THEN** 系统按年度统计销量或销售额
- **AND** 每年初重置计数
### Requirement: 梯度档位管理
系统 SHALL 支持多个返佣档位,每个档位有独立的阈值和返佣规则。
#### Scenario: 多档位返佣
- **WHEN** 配置了多个档位 (tiers数组)
- **THEN** 系统按阈值从低到高排列档位
- **AND** 当销量/销售额超过某档位阈值时,使用该档位的返佣规则
#### Scenario: 档位阈值唯一性
- **WHEN** 用户提交包含重复阈值的档位
- **THEN** 系统返回400错误
- **AND** 错误消息说明阈值必须唯一
#### Scenario: 档位返佣模式
- **WHEN** 档位的 mode = "fixed"
- **THEN** value 表示固定返佣金额(分)
- **WHEN** 档位的 mode = "percent"
- **THEN** value 表示返佣百分比的千分比

View File

@@ -0,0 +1,59 @@
# Implementation Tasks
## 1. Type Definitions Update
- [x] 1.1 更新 `ShopSeriesAllocationResponse` 接口,移除旧字段,添加新的佣金字段
- [x] 1.2 创建 `BaseCommissionConfig` 类型定义 (mode, value)
- [x] 1.3 创建 `TierCommissionConfig` 类型定义 (period_type, tier_type, tiers)
- [x] 1.4 创建 `TierEntry` 类型定义 (threshold, mode, value)
- [x] 1.5 更新 `CreateShopSeriesAllocationRequest` 接口以支持新的佣金字段
- [x] 1.6 更新 `UpdateShopSeriesAllocationRequest` 接口以支持新的佣金字段
- [x] 1.7 更新分页响应类型,从 `items` 改为 `list`,添加 `total_pages`
## 2. API Service Layer Update
- [x] 2.1 更新 `getShopSeriesAllocations` 方法以处理新的响应结构 (list 而非 items)
- [x] 2.2 更新 `createShopSeriesAllocation` 方法以发送新的佣金配置
- [x] 2.3 更新 `updateShopSeriesAllocation` 方法以发送新的佣金配置
- [x] 2.4 确保所有API方法正确处理新的类型定义
## 3. Frontend Page Refactoring
- [x] 3.1 移除旧的定价模式相关代码 (pricing_mode, pricing_value)
- [x] 3.2 移除旧的一次性佣金配置相关代码
- [x] 3.3 更新表格列定义,移除 `calculated_cost_price` 等旧列
- [x] 3.4 添加基础佣金配置表单字段 (mode: fixed/percent, value)
- [x] 3.5 添加梯度返佣开关字段 (enable_tier_commission)
- [x] 3.6 添加梯度返佣配置表单 (period_type, tier_type, tiers数组)
- [x] 3.7 实现梯度档位的动态添加/删除功能
- [x] 3.8 更新表格列以显示新的佣金信息 (base_commission)
- [x] 3.9 更新表单验证规则以适配新的字段要求
- [x] 3.10 更新数据列表获取逻辑,从 `res.data.items` 改为 `res.data.list`
- [x] 3.11 更新 `showDialog` 函数以正确填充新的佣金字段
- [x] 3.12 更新 `handleDialogClosed` 函数以重置新的佣金字段
- [x] 3.13 更新 `handleSubmit` 函数以正确提交新的佣金配置
## 4. UI/UX Enhancement
- [x] 4.1 设计并实现基础佣金配置UI (单选模式 + 数值输入)
- [x] 4.2 设计并实现梯度返佣配置UI (周期类型、梯度类型、档位列表)
- [x] 4.3 添加梯度档位的表单验证 (阈值递增、必填字段等)
- [x] 4.4 优化对话框布局以容纳新增的配置项
- [x] 4.5 添加梯度返佣的帮助提示或说明文档链接
## 5. Data Migration Support
- [x] 5.1 与后端确认数据迁移方案和时间表
- [x] 5.2 准备回退方案(如果部署失败)
- [x] 5.3 准备测试数据以验证迁移正确性
## 6. Testing and Validation
- [x] 6.1 测试基础佣金配置的创建和编辑
- [x] 6.2 测试梯度返佣的创建和编辑
- [x] 6.3 测试档位添加/删除功能
- [x] 6.4 测试表单验证规则
- [x] 6.5 测试数据列表的显示和分页
- [x] 6.6 测试状态切换功能
- [x] 6.7 测试删除功能
- [x] 6.8 验证与后端API的集成