# 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:启用`。

**决策**：

- **在常量配置中定义套餐专用的状态枚举**
- **前端页面使用项目统一的 CommonStatus（0/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 业务错误**：根据错误信息显示具体提示（）
- **表单验证错误**：在表单字段下显示错误提示

**错误提示方式**：

```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**: 待确认，当前设计是创建时计算一次，不自动更新
