Files
yinjianm f7cef30b9c feat(admin-frontend): 完成订阅与系统管理真实工作台
补齐订单、优惠券、主题、插件、公告与支付管理页面,
接入对应后台接口、路由入口与工具层类型定义。
同时修复套餐页开关初始化误写问题,避免浏览即触发写操作。

在订阅协议侧为 Stash 导出增加 AnyTLS 版本守卫,
未知版本或低于 3.3.0 时不再导出该协议,并补充回归测试与知识记录。
2026-04-24 16:52:41 +08:00

197 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 变更提案: admin-frontend-coupon-management
## 元信息
```yaml
类型: 新功能
方案类型: implementation
优先级: P1
状态: 执行中
创建: 2026-04-24
```
---
## 1. 需求
### 背景
当前 `admin-frontend` 已完成仪表盘、用户管理、节点管理、套餐管理与系统配置,但“订阅管理”分组下的“优惠券管理”仍是禁用占位。用户本轮明确要求参考 `apple/DESIGN.md`、项目级 `.helloagents/DESIGN.md` 与提供的两张参考图,继续补完真正可用的优惠券管理页面,包括列表工作台与新增/编辑弹窗。
### 目标
- 开放侧边栏中的“优惠券管理”入口,并接入真实页面与路由。
- 基于现有 Laravel `coupon/*` 接口,完成优惠券列表、搜索、类型筛选、启停、删除、新增与编辑。
- 页面视觉延续当前 Apple 化后台,尽量还原参考图中的黑白结构、紧凑表格与表单弹窗。
### 约束条件
```yaml
技术约束: 继续使用 Vue3 + TypeScript + Element Plus + Vite,不引入额外状态管理或重型日期/富文本依赖
业务约束: 后端接口仅使用现有 `/coupon/fetch` `/coupon/generate` `/coupon/update` `/coupon/drop`,不改 Laravel API
数据约束: 套餐限制项沿用现有 `/plan/fetch` 返回结果,优惠券周期限制遵循后端 legacy period 键值
视觉约束: 遵循 `apple/DESIGN.md` 与 `.helloagents/DESIGN.md`,保持与仪表盘/套餐管理同一视觉家族
```
### 验收标准
- [ ] 左侧“订阅管理”分组中的“优惠券管理”由禁用态切换为真实可点击入口,并进入独立页面。
- [ ] 优惠券管理页支持真实数据读取、关键字搜索、类型筛选、本地分页、启用开关、编辑和删除。
- [ ] “添加优惠券 / 编辑优惠券”弹窗支持名称、批量生成数量、自定义优惠码、类型和值、有效期、总使用次数、每人使用次数、指定周期与指定订阅。
- [ ] 页面中的类型标签、剩余次数、每人限制、有效期与过期提示可正确展示,并与后端字段含义一致。
- [ ] `admin-frontend` 构建通过,且知识库同步反映“优惠券管理”已从占位入口升级为真实页面。
---
## 2. 方案
### 技术方案
1. 扩展 `src/types/api.d.ts``src/api/admin.ts`,新增优惠券列表项、保存载荷、分页查询与计划选项的类型、请求封装。
2. 新增 `src/utils/coupons.ts`,集中处理优惠券类型标签、legacy period 选项、时间范围换算、表单默认值、列表本地过滤与过期文案。
3. 新增 `src/views/subscriptions/CouponsView.vue``CouponEditorDialog.vue`
- `CouponsView` 负责黑色首屏、工具条、表格、分页、行内开关与操作按钮。
- `CouponEditorDialog` 负责新增/编辑表单、指定周期/订阅、时间范围和数值字段校验。
4.`AdminLayout.vue``router/index.ts` 中启用“优惠券管理”菜单与 `/subscriptions/coupons` 路由,同时保持“订单管理 / 礼品卡管理”为未完成状态。
### 影响范围
```yaml
涉及模块:
- admin-frontend/src/layouts: 开放优惠券菜单入口
- admin-frontend/src/router: 新增优惠券管理路由
- admin-frontend/src/api: 新增 coupon 接口封装
- admin-frontend/src/types: 新增优惠券类型定义
- admin-frontend/src/utils: 新增优惠券数据转换与展示逻辑
- admin-frontend/src/views/subscriptions: 新增优惠券页面、弹窗与样式
预计变更文件: 7-9
```
### 风险评估
| 风险 | 等级 | 应对 |
|------|------|------|
| `/coupon/fetch` 的服务端筛选能力有限,关键字无法同时覆盖名称与券码 | 中 | 前端优先一次拉取合理数量后做本地搜索与分页,避免错误的 AND 过滤 |
| 编辑接口复用 `/coupon/generate`,字段与新增表单共用,空值和 legacy period 容易提交错误 | 中 | 在 `utils/coupons.ts` 中统一序列化,空数组转 `undefined`,时间统一转 Unix 秒 |
| 参考图包含较丰富的表格状态与表单布局,若实现不克制容易脱离既有后台风格 | 中 | 复用现有 Apple Admin token、表格与弹窗风格,不额外引入第二套后台皮肤 |
---
## 3. 技术设计(可选)
### 架构设计
```mermaid
flowchart TD
A[CouponsView] --> B[admin.ts coupon 接口]
A --> C[coupons.ts 展示/序列化工具]
A --> D[CouponEditorDialog]
D --> B
D --> C
D --> E[getPlans]
```
### API设计
#### GET /coupon/fetch
- **请求**: `{ current, pageSize }`
- **响应**: `{ total, current_page, per_page, last_page, data[] }`
#### POST /coupon/generate
- **请求**: `{ id?, generate_count?, name, code?, type, value, started_at, ended_at, limit_use?, limit_use_with_user?, limit_plan_ids?, limit_period? }`
- **响应**: `{ status, data }`
#### POST /coupon/update
- **请求**: `{ id, show }`
- **响应**: `{ status, data }`
### 数据模型
| 字段 | 类型 | 说明 |
|------|------|------|
| id | number | 优惠券 ID |
| show | boolean | 是否启用 |
| name | string | 优惠券名称 |
| type | 1 \| 2 | 1=按金额优惠,2=按比例优惠 |
| value | number | 金额分/折扣整数 |
| code | string | 券码 |
| limit_use | number \| null | 总可用次数 |
| limit_use_with_user | number \| null | 每人可用次数 |
| limit_plan_ids | string[] \| null | 限制套餐 |
| limit_period | string[] \| null | legacy 周期键 |
| started_at / ended_at | number | 生效时间范围(Unix 秒) |
---
## 4. 核心场景
### 场景: 运营创建单张优惠券
**模块**: admin-frontend/subscriptions
**条件**: 管理员已登录,进入 `#/subscriptions/coupons`
**行为**: 点击“添加优惠券”,填写名称、类型、金额、有效期并提交
**结果**: 新优惠券保存成功,列表刷新并展示新记录
### 场景: 运营批量生成优惠码
**模块**: admin-frontend/subscriptions
**条件**: 管理员在新增弹窗中填写 `generate_count`
**行为**: 提交批量生成请求
**结果**: 后端批量创建优惠券,前端提示成功并刷新列表
### 场景: 运营筛选并停用过期优惠券
**模块**: admin-frontend/subscriptions
**条件**: 列表中存在多种类型与已过期记录
**行为**: 使用关键字或类型筛选找到目标记录,关闭启用开关
**结果**: 对应优惠券 `show=false`,列表状态即时更新
---
## 5. 技术决策
### admin-frontend-coupon-management#D001: 优惠券列表采用真实接口 + 本地搜索/筛选/分页
**日期**: 2026-04-24
**状态**: ✅采纳
**背景**: 后端 `/coupon/fetch` 支持分页,但关键字筛选以 `where like` 串联多个字段时不适合做“名称或券码”搜索。
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 完全依赖后端筛选 | 数据量更轻 | 关键字搜索能力受限,名称/券码无法自然并行匹配 |
| B: 拉取合理数量后本地搜索/筛选/分页 | 交互更贴近参考图,搜索更灵活 | 极大数据量下效率不如纯服务端 |
**决策**: 选择方案 B
**理由**: 当前后台优惠券数量通常不大,本地处理可更稳定满足截图中的使用方式。
**影响**: `CouponsView.vue``admin.ts``coupons.ts`
### admin-frontend-coupon-management#D002: 新增与编辑共用同一弹窗,并统一序列化到 `/coupon/generate`
**日期**: 2026-04-24
**状态**: ✅采纳
**背景**: 后端没有独立的 coupon save/update 表单接口,新增与编辑都通过 `generate` 处理。
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 新增/编辑拆两套表单 | 心智更直观 | 代码重复,字段校验需要维护两份 |
| B: 共用一个表单模型与序列化逻辑 | 结构更稳定,便于维护 | 需要额外处理编辑态初始值 |
**决策**: 选择方案 B
**理由**: 与当前套餐编辑抽屉模式一致,能减少重复逻辑并提高一致性。
**影响**: `CouponEditorDialog.vue``utils/coupons.ts`
### admin-frontend-coupon-management#D003: 编辑态继续采用居中弹窗而非侧边抽屉
**日期**: 2026-04-24
**状态**: ✅采纳
**背景**: 参考图中优惠券编辑器为居中对话框,且字段密度更适合聚焦式表单。
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 复用抽屉模式 | 与套餐管理统一 | 与参考图差异更大,纵向表单视觉焦点分散 |
| B: 改为居中弹窗 | 更贴近参考图,聚焦更强 | 需要单独编写对话框布局样式 |
**决策**: 选择方案 B
**理由**: 这页的主要新增价值就在参考图里的“表格 + 弹窗”组合,值得贴近还原。
**影响**: `CouponEditorDialog.vue` 及其样式
---
## 6. 成果设计
### 设计方向
- **美学基调**: Apple Admin Promotions。像 Apple 后台里的运营配置页,黑色首屏负责建立业务主题,正文工作台回到白底高密度表格,让折扣配置看起来更像精密运营台而不是营销页面。
- **记忆点**: 大标题“优惠券管理”与白色表格之间形成明显的黑白切面;弹窗内部用整齐的双列字段和轻描边区块还原截图的“精密表单感”。
- **参考**: `apple/DESIGN.md``.helloagents/DESIGN.md`、用户上传的优惠券列表与添加弹窗截图
### 视觉要素
- **配色**: 首屏 `#000000`,工作区 `#ffffff`,页面背景 `#f5f5f7`,强调色 `#0071e3`,过期提示用浅红底 `rgba(201, 52, 40, 0.08)`
- **字体**: 延续项目现有系统字体栈,标题走大字号紧行高,表格与辅助信息维持更轻的运营化层级
- **布局**: 首屏 Hero + 工具条 + 表格工作台;编辑器采用居中弹窗,字段按双列网格排布,底部操作区固定在弹窗底部
- **动效**: 保留开关切换、弹窗开合、按钮 hover 与筛选状态切换的轻量动效,不引入额外炫技动画
- **氛围**: 工作台继续使用克制阴影、圆角白底与 Apple 式留白,避免多余卡片堆叠
### 技术约束
- **可访问性**: 交互控件保留可见焦点,状态信息不只依赖颜色,危险操作保留明确文案
- **响应式**: 桌面优先;窄屏下 Hero、工具条和弹窗网格折叠为单列,确保表单仍可完整操作