|
|
@@ -0,0 +1,240 @@
|
|
|
+# 家庭管理「优惠券数量」列 + 统一优惠券页 —— 架构设计文档
|
|
|
+
|
|
|
+> 首席架构师:高见远 · 2026-11-01
|
|
|
+> 项目:cfc-coupon-family(cfc-backend + cfc-web)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 0. 决策摘要(TL;DR)
|
|
|
+
|
|
|
+| 决策点 | 结论 |
|
|
|
+|--------|------|
|
|
|
+| 前端框架 | **Vue 2.6.14 + Element UI 2.15.13**(现有栈,不引入新框架) |
|
|
|
+| 后端框架 | **Spring Boot(Java 8)+ MyBatis-Plus 3.5.3.1**(现有栈,不引入新框架) |
|
|
|
+| 图标库(P0 锁定) | **`@ant-design/icons-vue@5.1.9`**(Vue 2 兼容的真实 SVG 图标库) |
|
|
|
+| 后端变更 | 仅 `Family` 实体 + `AdminController.getFamilies` 增量回填 `couponCount`,**3 行代码** |
|
|
|
+| 数据库变更 | **无**(`family_coupon` 表已存在,`idx_family_coupon(family_id, coupon_id)` 已覆盖统计查询) |
|
|
|
+| API 变更 | **复用现有端点**,零新增后端端点(统一优惠券页前端 Tabs 调 4 个既有接口) |
|
|
|
+| 路由变更 | 新增/合并 1 个前端路由承载统一优惠券页,接受 `familyId` query 参数 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 1. 技术栈选型矩阵
|
|
|
+
|
|
|
+### 1.1 前端(cfc-web,现役)
|
|
|
+
|
|
|
+| 层 | 依赖 | 版本 | 说明 |
|
|
|
+|----|------|------|------|
|
|
|
+| 框架 | `vue` | ^2.6.14 | 现有,Vue 2 Options API |
|
|
|
+| 路由 | `vue-router` | ^3.5.1 | 现有 |
|
|
|
+| 状态 | `vuex` | ^3.6.2 | 现有 |
|
|
|
+| UI 组件库 | `element-ui` | ^2.15.13 | 现有,Element UI 2.x(Vue 2 版) |
|
|
|
+| HTTP | `axios` | ^1.6.0 | 现有,封装于 `src/utils/request` |
|
|
|
+| 图表 | `echarts` | ^6.1.0 | 现有 |
|
|
|
+| 富文本 | `@wangeditor/editor(-for-vue)` | ^5.x | 现有 |
|
|
|
+| **图标库(P0 锁定)** | `@ant-design/icons-vue` | **5.1.9** | **新增锁定依赖**(见 §5) |
|
|
|
+
|
|
|
+### 1.2 后端(cfc-backend,现役)
|
|
|
+
|
|
|
+| 层 | 依赖 | 版本 | 说明 |
|
|
|
+|----|------|------|------|
|
|
|
+| 框架 | `spring-boot-starter-*` | parent(Java 8) | 现有 |
|
|
|
+| ORM | `mybatis-plus-boot-starter` | 3.5.3.1 | 现有 |
|
|
|
+| 数据库 | MySQL(InnoDB / utf8mb4) | — | 现有 |
|
|
|
+| 认证 | `spring-boot-starter-security` | — | 现有,JWT + 角色 |
|
|
|
+| Web | `spring-boot-starter-web` | — | 现有 |
|
|
|
+
|
|
|
+### 1.3 部署(现役,未变更)
|
|
|
+
|
|
|
+| 维度 | 方案 |
|
|
|
+|------|------|
|
|
|
+| 前端 | `vue-cli-service build` 产物静态部署 |
|
|
|
+| 后端 | Spring Boot 可执行 Jar |
|
|
|
+| 环境 | dev / test / prod |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 核心功能可行性验证结论
|
|
|
+
|
|
|
+### 2.1 后端 `couponCount` 回填 —— ✅ 可行,增量 3 行
|
|
|
+
|
|
|
+**依据**(已核实源码):
|
|
|
+
|
|
|
+1. **`Family` 实体已采用 `@TableField(exist = false)` 模式**(`Family.java` L40-50):
|
|
|
+ - `memberCount` / `reportCount` / `planCount` / `taskCount` 四个统计字段均为该注解的瞬态字段。
|
|
|
+ - 新增 `couponCount` 完全复用同一模式,**无需 DB Schema 变更**。
|
|
|
+ - MyBatis-Plus 3.5.3.1 官方支持 `@TableField(exist = false)` 标记非表字段。
|
|
|
+
|
|
|
+2. **`AdminController.countByFamilyIds(BaseMapper, ids, extraCond)` 为泛型方法**(`AdminController.java` L843):
|
|
|
+ - 内部 `QueryWrapper.select("family_id as k", "count(*) as v").in("family_id", ids).groupBy("family_id")` + `mapper.selectMaps(qw)`。
|
|
|
+ - 可传入**任意** `BaseMapper<T>`,`FamilyCouponMapper extends BaseMapper<FamilyCoupon>` 完全兼容。
|
|
|
+ - `getFamilies` 已在 L825-828 对 member/report/plan/task 调用该方法,L829-834 回填。
|
|
|
+
|
|
|
+3. **增量代码**(`AdminController.getFamilies`,约 3 行):
|
|
|
+
|
|
|
+ ```java
|
|
|
+ // 注入字段(类顶部 @Resource)
|
|
|
+ @Resource
|
|
|
+ private FamilyCouponMapper familyCouponMapper;
|
|
|
+
|
|
|
+ // getFamilies 内,L828 之后追加
|
|
|
+ Map<Long, Long> couponCnt = countByFamilyIds(familyCouponMapper, ids, null);
|
|
|
+ // 回填循环内追加
|
|
|
+ f.setCouponCount(couponCnt.getOrDefault(f.getId(), 0L));
|
|
|
+ ```
|
|
|
+
|
|
|
+ - `extraCond = null`:统计该家庭在 `family_coupon` 表的**全部记录数**(含 AVAILABLE/USED/RESERVED/EXPIRED/REVOKED),与需求「统计 family_coupon 表中该家庭的记录数」一致。
|
|
|
+
|
|
|
+### 2.2 统一优惠券页 —— ✅ 可行,零新增后端端点
|
|
|
+
|
|
|
+现有 `AdminCouponController`(`@RequestMapping("/api/admin/coupon")`)已提供全部四个 Tabs 的数据源,且均支持 `familyId` 过滤:
|
|
|
+
|
|
|
+| Tab | 现有端点 | 是否支持 familyId 过滤 | 说明 |
|
|
|
+|-----|----------|------------------------|------|
|
|
|
+| 模板管理 | `POST /api/admin/coupon/list` | 否(全局模板列表) | 复用;家庭维度无需过滤 |
|
|
|
+| 发放记录 | `POST /api/admin/coupon/grant-log` | ✅ `familyId` | L193-215 已验证,已批量填充 `familyName` |
|
|
|
+| 使用记录 | `POST /api/admin/coupon/family-coupon-usage` | ✅ `familyId` | L314-364 已验证 |
|
|
|
+| 转赠记录 | `POST /api/admin/coupon/gift-transfers` | ✅ `giverFamilyId` / `receiverFamilyId` | L238-251 已验证 |
|
|
|
+
|
|
|
+> 结论:**不新增后端端点**。前端统一页用 Tabs 切换,调用上表 4 个既有接口并带 `familyId`,满足「点击家庭优惠券数量 → 进入该家庭优惠券页(默认展示发放/使用记录)」。
|
|
|
+
|
|
|
+### 2.3 路由重复检查 —— ✅ 无真实冲突
|
|
|
+
|
|
|
+- 对 `cfc-backend/src/main/java/com/etotem/cfc/controller/admin/` 全量 `@*Mapping` 做「类级前缀 + 方法级路径」拼接去重:
|
|
|
+ - 未发现真实重复路径(子路径如 `/list`、`/delete` 多次出现,但类级 `@RequestMapping` 前缀不同,属正常)。
|
|
|
+ - 未发现任何 `families/coupon` 或 `coupon/families` 路径。
|
|
|
+- **无路径冲突**,即使未来新增端点也不会撞车;本次采用复用方案更无需新增。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. API 端点清单
|
|
|
+
|
|
|
+### 3.1 后端(全部复用,无新增/废弃)
|
|
|
+
|
|
|
+| 方法 | 路径 | 状态 | 用途 |
|
|
|
+|------|------|------|------|
|
|
|
+| POST | `/api/admin/families` | **增强(回填 couponCount)** | 家庭分页列表,新增 `couponCount` 统计列 |
|
|
|
+| POST | `/api/admin/coupon/list` | 复用 | 统一页「模板管理」Tab |
|
|
|
+| POST | `/api/admin/coupon/grant-log` | 复用 | 统一页「发放记录」Tab(`familyId`) |
|
|
|
+| POST | `/api/admin/coupon/family-coupon-usage` | 复用 | 统一页「使用记录」Tab(`familyId`) |
|
|
|
+| POST | `/api/admin/coupon/gift-transfers` | 复用 | 统一页「转赠记录」Tab(`giverFamilyId`/`receiverFamilyId`) |
|
|
|
+
|
|
|
+> 响应统一为 `{ code, data, message }`。`/families` 的 `data.records[]` 每项新增 `couponCount` 字段(Long)。
|
|
|
+> 复用端点无需变更契约,前端仅按既有返回结构消费。
|
|
|
+
|
|
|
+### 3.2 前端路由
|
|
|
+
|
|
|
+| 路由 | 组件 | 状态 |
|
|
|
+|------|------|------|
|
|
|
+| `/admin/coupon` | `CouponUnified.vue`(合并新页) | **新增/改造**,接受 `query.familyId` |
|
|
|
+| `/admin/coupon-grant-log` | — | **废弃**(并入统一页 Tab) |
|
|
|
+| `/admin/coupon-gift-transfers` | — | **废弃**(并入统一页 Tab) |
|
|
|
+
|
|
|
+> 统一页默认高亮 Tab 依据 `familyId`:有 `familyId` 时默认「发放记录」,无则「模板管理」。
|
|
|
+> 菜单中 3 个独立入口收敛为 1 个「优惠券管理」,`CouponGrantLog` / `CouponGiftTransfers` 两个 .vue 文件迁移为统一页的 Tab 子组件或内联。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. 数据库变更
|
|
|
+
|
|
|
+| 对象 | 变更 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `family_coupon` 表 | **无变更** | 已存在(`schema.sql` L4712),含 `idx_family_coupon(family_id, coupon_id)` 索引,`COUNT(*) WHERE family_id IN (...)` 走该索引 |
|
|
|
+| `Family` 实体 | **新增瞬态字段** | `@TableField(exist = false) private Long couponCount;`(同 memberCount 模式) |
|
|
|
+| 其他表 | 无变更 | — |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. 图标库锁定(P0 绝对规则)
|
|
|
+
|
|
|
+### 5.1 选型结论:`@ant-design/icons-vue@5.1.9`
|
|
|
+
|
|
|
+- **为何选它**:
|
|
|
+ 1. **真实 SVG 图标**(path-based,非字体),满足「SVG 图标库」P0 要求。
|
|
|
+ 2. **Vue 2 兼容**:官方版本矩阵确认 v5.x 支持 Vue 2,v6.x+ 要求 Vue ≥3.0.3(本项目 Vue 2.6.14),故锁定 **5.1.9**(5.x 末版)。
|
|
|
+ 3. **可锁定 npm 依赖**,满足「前端依赖随之锁定」。
|
|
|
+ 4. **免 webpack 改造**:组件库按需 import,无需 svg-sprite-loader / require.context / SvgIcon 组件,符合「MVP 恰到好处、不做过度设计」。
|
|
|
+- **备选方案对比**(供 PM 权衡):
|
|
|
+ - `svg-sprite-loader + 本地 SVG + SvgIcon`(vue-element-admin 模式):更贴近 Element UI 视觉,但需 webpack 配置 + 组件 + 资源目录,基建成本高,MVP 不取。
|
|
|
+ - `@element-plus/icons-vue`:**Vue 3 only**,不可用。
|
|
|
+ - `el-icon-*`(Element UI 字体图标):非 SVG 体系,不满足「SVG 图标库」字面要求。
|
|
|
+
|
|
|
+### 5.2 使用约定
|
|
|
+
|
|
|
+1. **本特性新增的交互图标**必须从 `@ant-design/icons-vue@5.1.9` 引入(如 `TicketOutlined` / `FileTextOutlined` / `TeamOutlined` / `CalendarOutlined`)。
|
|
|
+2. **禁止 emoji 作为功能图标**(P0)。
|
|
|
+3. **全项目统一不混用**:被本特性触达的组件(`Families.vue`、合并后的 `CouponUnified.vue` 及迁移的 GrantLog/GiftTransfers 子组件)内,将 `el-icon-*` 使用迁移为 SVG 图标库,保证「触碰面 = 一致」。
|
|
|
+
|
|
|
+### 5.3 技术债提示(非阻塞,交 PM 排期)
|
|
|
+
|
|
|
+> 全项目约百余个 `.vue` 仍使用 `el-icon-*`。将「全量 `el-icon-*` → SVG 图标库」列为**后续专项**(不在本 MVP 范围),避免一次性大重构引入回归风险。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. 技术约束与风险
|
|
|
+
|
|
|
+| 类别 | 约束/风险 | 等级 | 应对 |
|
|
|
+|------|-----------|------|------|
|
|
|
+| 兼容性 | `@ant-design/icons-vue` 必须锁 5.1.9,**严禁**升到 6.x+(Vue 3 only) | 高 | package.json 锁定精确版本 + package-lock 提交 |
|
|
|
+| 性能 | `countByFamilyIds` 对 `family_coupon` 做 `GROUP BY family_id`,当 `family_coupon` 达百万级且 `family_id` 高基数时,单页 100 家庭可能偏慢 | 中 | `idx_family_coupon(family_id, coupon_id)` 覆盖;MVP 数据量下可接受;超阈值再考虑预聚合/计数表 |
|
|
|
+| 权限 | `getFamilies` 已做 B-5 行级隔离(platform/butler/bound),回填统计不影响权限模型 | 低 | 无变更,统计与列表同一数据范围 |
|
|
|
+| 分页 | 统计仅针对**当前页** `ids`(非全量),避免全表聚合 | 低 | 既有设计,保持 |
|
|
|
+| 前端 | 3 个旧优惠券页合并为 1 页,需保证既有功能(发券、模板 CRUD、转赠过滤)不回归 | 中 | 拆分 Tab 子组件复用原逻辑,PM 补充回归用例 |
|
|
|
+| 路由 | 废弃 `/coupon-grant-log`、`/coupon-gift-transfers` 需同步菜单与 `perm` 权限点 | 低 | 保留 `marketing:coupon` 权限点,仅收敛路由 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 7. 分层架构(ASCII)
|
|
|
+
|
|
|
+```
|
|
|
+┌──────────────────────────────────────────────────────────────────┐
|
|
|
+│ cfc-web 表现层(Vue2 + Element UI 2.x + @ant-design/icons-vue) │
|
|
|
+│ Families.vue(家庭列表) │
|
|
|
+│ ├─ 新增「优惠券数量」列(couponCount) ──点击──▶ /admin/coupon?familyId=xx │
|
|
|
+│ ├─ member/report/plan/task 计数列改可点击 ──▶ 对应明细列表 │
|
|
|
+│ └─ 下拉菜单移除「查看信息」项 │
|
|
|
+│ CouponUnified.vue(统一优惠券页, 4 Tabs) │
|
|
|
+│ ├─ 模板管理 Tab → POST /coupon/list │
|
|
|
+│ ├─ 发放记录 Tab → POST /coupon/grant-log (familyId) │
|
|
|
+│ ├─ 使用记录 Tab → POST /coupon/family-coupon-usage (familyId) │
|
|
|
+│ └─ 转赠记录 Tab → POST /coupon/gift-transfers (familyId) │
|
|
|
+└──────────────┬───────────────────────────────────────────────────┘
|
|
|
+ │ axios + JWT(src/utils/request)
|
|
|
+┌──────────────▼───────────────────────────────────────────────────┐
|
|
|
+│ cfc-backend 业务层(Spring Boot + MyBatis-Plus 3.5.3.1) │
|
|
|
+│ AdminController.getFamilies │
|
|
|
+│ ├─ familyMapper.selectPage(分页 + B-5 行级隔离) │
|
|
|
+│ └─ countByFamilyIds(ids) × {member,report,plan,task,coupon} │
|
|
|
+│ └─ FamilyCouponMapper(新增注入, couponCount 回填 3 行) │
|
|
|
+│ AdminCouponController(复用): list/grant-log/ │
|
|
|
+│ family-coupon-usage/gift-transfers(均支持 familyId) │
|
|
|
+└──────────────┬───────────────────────────────────────────────────┘
|
|
|
+ │ MyBatis-Plus
|
|
|
+┌──────────────▼───────────────────────────────────────────────────┐
|
|
|
+│ 数据层(MySQL InnoDB) │
|
|
|
+│ families / family_coupon / family_coupon_grant_log / │
|
|
|
+│ coupon / coupon_gift_transfer │
|
|
|
+│ family_coupon: idx_family_coupon(family_id, coupon_id) │
|
|
|
+└──────────────────────────────────────────────────────────────────┘
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 8. 变更文件清单(预估)
|
|
|
+
|
|
|
+| 文件 | 变更 | 预估 |
|
|
|
+|------|------|------|
|
|
|
+| `cfc-backend/.../entity/Family.java` | 新增 `couponCount` 瞬态字段 | +1 字段 |
|
|
|
+| `cfc-backend/.../controller/admin/AdminController.java` | 注入 `FamilyCouponMapper` + 回填 2 行 | +3 行 |
|
|
|
+| `cfc-web/src/api/admin.js`(或 `coupon.js`) | 无新增(复用) | 0 |
|
|
|
+| `cfc-web/src/views/Families.vue` | 新增 couponCount 列 + 计数列可点击 + 移除查看信息 | 中 |
|
|
|
+| `cfc-web/src/views/admin/CouponUnified.vue` | 新建统一优惠券页(4 Tabs) | 新文件 |
|
|
|
+| `cfc-web/src/router/index.js` | 收敛 3 路由 → 1,支持 `familyId` | 小 |
|
|
|
+| `cfc-web/src/views/admin/CouponGrantLog.vue` / `CouponGiftTransfers.vue` | 迁移为统一页 Tab 子组件 | 中 |
|
|
|
+| `cfc-web/package.json` | 锁定 `@ant-design/icons-vue@5.1.9` | 依赖 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 9. 机器可读产出物
|
|
|
+
|
|
|
+- `docs/api/openapi.yaml`:OpenAPI 3.0 契约(含 `/api/admin/families` 新增 `couponCount` 字段 + 4 个复用端点),前后端联调唯一依据。
|