# 会员卡编辑功能设计 **日期:** 2026-04-07 **状态:** 已批准 --- ## 1. 概述 在管理后台会员列表中,点击会员 item 时弹出的详情弹窗增加"编辑"能力。管理员可编辑指定用户的卡种、卡有效期、剩余次数,也可清空/解除会员卡。 **前置约束:** - 一个用户只能拥有一张会员卡 - 支持清空卡(解除会员资格) --- ## 2. 交互设计 ### 2.1 弹窗结构 ``` ┌─────────────────────────────────┐ │ 会员详情 [×] │ ├──────────┬──────────────────────┤ │ 详情 │ 编辑 │ ← Tab 切换 ├──────────┴──────────────────────┤ │ 头像 / 昵称 / OpenID / 手机号 │ │ ───────────────────────────── │ │ 会员卡信息区(根据 Tab 显示) │ │ 预约统计(总/完成/取消) │ │ ───────────────────────────── │ │ [关闭] │ └─────────────────────────────────┘ ``` ### 2.2 无卡用户交互 - 详情 Tab 显示"暂无会员卡"提示 - 显示"去开卡"按钮,点击切换到编辑 Tab - 编辑 Tab 中卡种 picker 默认选中列表第一项,有效期自动计算 ### 2.3 状态说明 | 场景 | Tab 初始显示 | 编辑表单状态 | |------|-------------|-------------| | 有卡用户 | 详情 Tab | 预填充当前数据 | | 无卡用户 | 详情 Tab(提示无卡) | 空白表单,卡种默认选中第一项 | --- ## 3. 表单字段 | 字段 | 组件 | 条件 | 说明 | |------|------|------|------| | 卡种 | picker | 必填 | 从 `CardType` 表读取,按 sortOrder 排序 | | 剩余次数 | input (number) | TIMES / TRIAL 类型 | 默认填充卡种的 totalTimes | | 生效日期 | date picker | 必填 | 默认当天 | | 有效期至 | date picker | 必填 | 自动计算,支持手动调整 | ### 3.1 卡种切换逻辑 切换卡种时: 1. `remainingTimes` 自动填充新卡种的 `totalTimes`(TIMES/TRIAL 类型) 2. `expireDate` 按新卡种 `durationDays` 重新计算起始日期 3. 编辑器内手动修改过 `expireDate` 时,不再自动覆盖 --- ## 4. API 设计 ### 4.1 获取用户会员卡 ``` GET /admin/members/:userId/membership ``` **响应:** ```json { "userId": "uuid", "membership": { "id": "uuid", "cardTypeId": "uuid", "remainingTimes": 10, "startDate": "2026-01-01", "expireDate": "2026-04-01", "status": "ACTIVE", "cardType": { ... } } | null } ``` ### 4.2 创建或更新会员卡 ``` PUT /admin/members/:userId/membership ``` **请求体:** ```json { "cardTypeId": "uuid", "remainingTimes": 10, "startDate": "2026-04-07", "expireDate": "2026-07-07" } ``` **业务逻辑:** - 若该用户已有 membership → 更新 - 若该用户无 membership → 创建新的 - `status` 由后端根据 expireDate 和 remainingTimes 自动计算 ### 4.3 解除会员卡 ``` DELETE /admin/members/:userId/membership ``` **业务逻辑:** - 将 membership 的 `status` 标记为 `EXPIRED`(软删除,便于审计) - 不物理删除记录 --- ## 5. 数据模型 ### 5.1 会员卡状态自动计算规则 ``` if (expireDate < now) → EXPIRED else if (remainingTimes === 0) → USED_UP else → ACTIVE ``` ### 5.2 变更日志 通过现有的 `BookingStatusHistory` 表记录操作(tbd: 是否需要独立 `MembershipChangeLog` 表,待实现时确认)。 --- ## 6. 错误处理 | 场景 | 处理 | |------|------| | 卡种不存在 | 后端返回 404,前端提示"卡种不存在" | | 有效期早于生效日期 | 前端表单校验失败,提示"有效期不能早于生效日期" | | 次数为负数 | 前端表单校验失败,提示"次数不能为负数" | | 网络错误 | Toast 提示"网络错误,请重试" | | 清空卡确认 | 弹出确认对话框,提示"确定要解除该用户的会员卡吗?" | --- ## 7. 涉及改动 ### 后端 - `packages/server/src/membership/membership.controller.ts` — 新增三个 admin 接口 - `packages/server/src/membership/membership.service.ts` — 新增 `getUserMembership` / `updateUserMembership` / `deleteUserMembership` - `packages/server/src/user/user.controller.ts` — 无改动(列表接口保持不变) ### 前端 - `packages/app/src/stores/admin.ts` — 新增三个 store action - `packages/app/src/pages/admin/members.vue` — 将详情弹窗改造为 Tab 模式,新增编辑表单 --- ## 8. UI 细节 ### 8.1 Tab 切换 - 详情/编辑 Tab 横向排列,激活态有下划线指示 - 无卡用户点击"去开卡"后,自动切到编辑 Tab ### 8.2 编辑表单提交 - 保存成功后 Toast 提示"保存成功" - 自动切回详情 Tab 并刷新数据 - 保存按钮点击后置灰,防止重复提交 ### 8.3 清空卡 - 需二次确认 - 成功后弹窗关闭,列表自动刷新