docs: add design spec for pilates booking mini-program
Initial design document covering architecture (monorepo with pnpm), page design, data models, business flows, and API endpoints for the private Pilates lesson booking WeChat mini-program.
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
# 普拉提私教约课小程序 — 设计文档
|
||||
|
||||
## 概述
|
||||
|
||||
为单人普拉提工作室开发的微信小程序约课系统。核心功能:学员在线预约私教课、购买会员卡、查看训练记录;教练管理可约时段和会员。
|
||||
|
||||
### 约束
|
||||
|
||||
- 单一工作室、单一教练,不涉及多场馆/多教练切换
|
||||
- 仅私教课,不涉及小班课
|
||||
- 微信小程序为唯一客户端
|
||||
|
||||
---
|
||||
|
||||
## 技术架构
|
||||
|
||||
### Monorepo 结构
|
||||
|
||||
```
|
||||
mp-pilates/
|
||||
├── packages/
|
||||
│ ├── app/ # uni-app 微信小程序前端
|
||||
│ ├── server/ # NestJS 后端
|
||||
│ └── shared/ # 前后端共享类型和常量
|
||||
├── package.json # 根 workspace 配置
|
||||
├── pnpm-workspace.yaml
|
||||
└── tsconfig.base.json
|
||||
```
|
||||
|
||||
### 技术选型
|
||||
|
||||
| 层 | 选型 |
|
||||
|---|---|
|
||||
| 包管理 | pnpm workspace |
|
||||
| 前端 | uni-app + Vue 3 + TypeScript + Pinia + uni-ui |
|
||||
| 后端 | NestJS + TypeScript + Prisma ORM |
|
||||
| 数据库 | PostgreSQL |
|
||||
| 共享层 | `@mp-pilates/shared`(类型、常量、枚举) |
|
||||
|
||||
### 角色体系
|
||||
|
||||
| 角色 | 说明 |
|
||||
|------|------|
|
||||
| MEMBER | 学员(默认),约课、查看记录、购卡 |
|
||||
| ADMIN | 管理员/教练,排课、管理会员、查看订单 |
|
||||
|
||||
管理员通过数据库标记,小程序内根据角色展示不同菜单入口。
|
||||
|
||||
---
|
||||
|
||||
## 页面设计
|
||||
|
||||
### 底部 Tab 导航(3 个)
|
||||
|
||||
| Tab | 路径 | 说明 |
|
||||
|-----|------|------|
|
||||
| 首页 | /pages/home/index | 品牌展示 + 快捷入口 |
|
||||
| 课程预约 | /pages/booking/index | 私教时段 + 预约 |
|
||||
| 我的 | /pages/profile/index | 个人中心 |
|
||||
|
||||
### 1. 首页
|
||||
|
||||
**顶部**:品牌 banner(Logo + 名称 + 背景图)
|
||||
|
||||
**场馆信息**:
|
||||
- 场馆照片横滑轮播
|
||||
- 地址(点击跳转地图导航)+ 电话(点击拨打)
|
||||
|
||||
**快捷入口卡片**(根据用户状态智能展示):
|
||||
- 新用户 → 「预约体验课」醒目入口
|
||||
- 已有私教卡/时间卡 → 「一键约课」跳转课程预约页
|
||||
- 卡次即将用完 → 「续卡」提醒入口
|
||||
- 无卡用户 → 「购买会员卡」入口
|
||||
|
||||
**今日/近期课程**:已预约课程卡片,快速查看。
|
||||
|
||||
**会员卡商城**:卡种列表(私教次卡、月卡/季卡/年卡、体验课包),点击进入购买详情。
|
||||
|
||||
### 2. 课程预约页
|
||||
|
||||
**顶部日期选择器**:横向滚动,展示未来 7 天,标注星期几。
|
||||
|
||||
**筛选栏**(简化版):
|
||||
- 时段筛选(全部 / 上午 / 下午 / 晚上)
|
||||
- 单教练,无需老师筛选
|
||||
|
||||
**时段列表**:
|
||||
- 时段卡片:时间段、剩余名额、预约按钮
|
||||
- 已约满 → 灰色置灰
|
||||
- 已预约 → 显示「已预约」+「取消」
|
||||
|
||||
**预约流程**:
|
||||
1. 点击「预约」→ 检查是否有有效卡 → 无卡则引导购卡
|
||||
2. 有卡 → 确认预约弹窗(时段信息 + 扣卡信息)
|
||||
3. 确认 → 扣减卡次 / 验证时间卡有效期 → 预约成功
|
||||
|
||||
**取消规则**:
|
||||
- 开课前 N 小时可免费取消(N 在 StudioConfig 中配置,默认 2 小时)
|
||||
- 超时取消仍扣卡次
|
||||
|
||||
### 3. 个人中心
|
||||
|
||||
**顶部用户卡片**:头像 + 昵称 + 手机号 + 累计训练天数 / 训练次数
|
||||
|
||||
**本月训练统计**:训练次数 / 训练天数 / 训练时长
|
||||
|
||||
**功能入口**:
|
||||
- 我的会员卡 → 持有卡列表、有效期、剩余次数
|
||||
- 我的预约 → 预约记录(即将上课 / 历史)
|
||||
- 个人信息 → 编辑昵称、手机号
|
||||
|
||||
### 4. 管理功能(ADMIN 角色可见)
|
||||
|
||||
个人中心底部显示「管理中心」入口:
|
||||
|
||||
- **周模板设置** → 7 天网格,每天添加多个时间段,开关启用/停用
|
||||
- **临时调整** → 日历视图,点某天关闭/新增时段
|
||||
- **会员管理** → 所有会员列表、卡状态、预约记录
|
||||
- **订单管理** → 购卡订单列表、支付状态
|
||||
- **卡种管理** → 设置卡种、价格、有效期
|
||||
- **工作室设置** → 名称、Logo、地址、电话、照片
|
||||
|
||||
### 子页面清单
|
||||
|
||||
| 页面 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| 卡种详情/购买 | /pages/card/detail | 卡种信息 + 购买按钮 |
|
||||
| 预约确认 | 弹窗组件 | 确认预约信息 |
|
||||
| 我的会员卡 | /pages/profile/membership | 持有卡列表 |
|
||||
| 我的预约 | /pages/profile/bookings | 预约记录 |
|
||||
| 个人信息 | /pages/profile/info | 编辑个人信息 |
|
||||
| 管理中心 | /pages/admin/index | 管理功能入口 |
|
||||
| 周模板管理 | /pages/admin/week-template | 设置周课表 |
|
||||
| 临时时段调整 | /pages/admin/slot-adjust | 按日期调整时段 |
|
||||
| 会员管理 | /pages/admin/members | 会员列表与详情 |
|
||||
| 订单管理 | /pages/admin/orders | 订单列表 |
|
||||
| 卡种管理 | /pages/admin/card-types | 管理卡种 |
|
||||
| 工作室设置 | /pages/admin/studio | 编辑工作室信息 |
|
||||
|
||||
---
|
||||
|
||||
## 数据模型
|
||||
|
||||
### User(用户)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | UUID | 主键 |
|
||||
| openid | String | 微信 openid,唯一 |
|
||||
| unionid | String? | 微信 unionid |
|
||||
| phone | String? | 手机号(微信授权获取) |
|
||||
| nickname | String | 昵称 |
|
||||
| avatarUrl | String? | 头像 |
|
||||
| role | Enum(MEMBER, ADMIN) | 角色,默认 MEMBER |
|
||||
| createdAt | DateTime | 注册时间 |
|
||||
| updatedAt | DateTime | 更新时间 |
|
||||
|
||||
### CardType(卡种定义)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | UUID | 主键 |
|
||||
| name | String | 卡名,如「私教 10 次卡」 |
|
||||
| type | Enum(TIMES, DURATION, TRIAL) | 次卡 / 时间卡 / 体验课 |
|
||||
| totalTimes | Int? | 总次数(TIMES / TRIAL 用) |
|
||||
| durationDays | Int | 有效天数 |
|
||||
| price | Decimal | 售价(分) |
|
||||
| originalPrice | Decimal? | 原价(划线价,分) |
|
||||
| description | String? | 描述 |
|
||||
| isActive | Boolean | 是否上架,默认 true |
|
||||
| sortOrder | Int | 排序权重,默认 0 |
|
||||
| createdAt | DateTime | 创建时间 |
|
||||
| updatedAt | DateTime | 更新时间 |
|
||||
|
||||
### Membership(用户持有的卡)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | UUID | 主键 |
|
||||
| userId | UUID | 关联用户 |
|
||||
| cardTypeId | UUID | 关联卡种 |
|
||||
| remainingTimes | Int? | 剩余次数(次卡用) |
|
||||
| startDate | DateTime | 生效日期 |
|
||||
| expireDate | DateTime | 到期日期 |
|
||||
| status | Enum(ACTIVE, EXPIRED, USED_UP) | 状态 |
|
||||
| createdAt | DateTime | 创建时间 |
|
||||
| updatedAt | DateTime | 更新时间 |
|
||||
|
||||
### WeekTemplate(周模板时段)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | UUID | 主键 |
|
||||
| dayOfWeek | Int | 星期几(1=周一 ~ 7=周日) |
|
||||
| startTime | String | 开始时间,如 "09:00" |
|
||||
| endTime | String | 结束时间,如 "10:00" |
|
||||
| capacity | Int | 容量,默认 1 |
|
||||
| isActive | Boolean | 是否启用,默认 true |
|
||||
| createdAt | DateTime | 创建时间 |
|
||||
| updatedAt | DateTime | 更新时间 |
|
||||
|
||||
### TimeSlot(实际可约时段)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | UUID | 主键 |
|
||||
| date | Date | 具体日期 |
|
||||
| startTime | String | 开始时间 |
|
||||
| endTime | String | 结束时间 |
|
||||
| capacity | Int | 容量 |
|
||||
| bookedCount | Int | 已预约数,默认 0 |
|
||||
| status | Enum(OPEN, FULL, CLOSED) | 状态 |
|
||||
| source | Enum(TEMPLATE, MANUAL) | 来源:模板生成 / 手动添加 |
|
||||
| templateId | UUID? | 关联周模板(模板生成时) |
|
||||
| createdAt | DateTime | 创建时间 |
|
||||
| updatedAt | DateTime | 更新时间 |
|
||||
|
||||
**生成逻辑**:定时任务每天按周模板自动生成未来 7-14 天的 TimeSlot。教练可手动添加/关闭某天的特定时段。
|
||||
|
||||
### Booking(预约记录)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | UUID | 主键 |
|
||||
| userId | UUID | 关联用户 |
|
||||
| timeSlotId | UUID | 关联时段 |
|
||||
| membershipId | UUID | 扣的哪张卡 |
|
||||
| status | Enum(CONFIRMED, CANCELLED, COMPLETED, NO_SHOW) | 状态 |
|
||||
| cancelledAt | DateTime? | 取消时间 |
|
||||
| createdAt | DateTime | 预约时间 |
|
||||
| updatedAt | DateTime | 更新时间 |
|
||||
|
||||
### Order(购卡订单)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | UUID | 主键 |
|
||||
| userId | UUID | 关联用户 |
|
||||
| cardTypeId | UUID | 购买的卡种 |
|
||||
| orderNo | String | 订单号,唯一 |
|
||||
| amount | Decimal | 支付金额(分) |
|
||||
| status | Enum(PENDING, PAID, REFUNDED) | 支付状态 |
|
||||
| wxTransactionId | String? | 微信支付单号 |
|
||||
| paidAt | DateTime? | 支付时间 |
|
||||
| createdAt | DateTime | 下单时间 |
|
||||
| updatedAt | DateTime | 更新时间 |
|
||||
|
||||
### StudioConfig(工作室配置)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | UUID | 主键(仅一条记录) |
|
||||
| name | String | 工作室名称 |
|
||||
| logo | String? | Logo URL |
|
||||
| bannerUrl | String? | 背景图 URL |
|
||||
| address | String | 地址 |
|
||||
| phone | String | 联系电话 |
|
||||
| latitude | Decimal? | 纬度 |
|
||||
| longitude | Decimal? | 经度 |
|
||||
| cancelHoursLimit | Int | 免费取消截止小时数,默认 2 |
|
||||
| photos | Json | 场馆照片 URL 列表 |
|
||||
| updatedAt | DateTime | 更新时间 |
|
||||
|
||||
---
|
||||
|
||||
## 业务流程
|
||||
|
||||
### 用户登录
|
||||
|
||||
```
|
||||
小程序 wx.login() 获取 code
|
||||
→ 服务端用 code 换取 openid
|
||||
→ 查找/创建用户 → 签发 JWT
|
||||
→ 需要手机号时,前端 getPhoneNumber 按钮授权
|
||||
→ 服务端解密手机号 → 更新用户信息
|
||||
```
|
||||
|
||||
### 预约
|
||||
|
||||
```
|
||||
学员点击「预约」
|
||||
→ 检查登录(未登录引导登录)
|
||||
→ 检查有效会员卡
|
||||
→ 无卡 → 引导购卡
|
||||
→ 有卡 → 确认弹窗(时段 + 扣卡信息)
|
||||
→ 服务端校验(时段可约 + 卡有效 + 无重复预约)
|
||||
→ 成功 → 扣减卡次 → 预约成功
|
||||
→ 失败 → 提示原因
|
||||
```
|
||||
|
||||
### 取消预约
|
||||
|
||||
```
|
||||
学员点击「取消」
|
||||
→ 检查距开课时间
|
||||
→ >= cancelHoursLimit → 免费取消,退还卡次
|
||||
→ < cancelHoursLimit → 提示超时取消不退卡次,二次确认
|
||||
→ 更新 Booking 状态 → 释放 TimeSlot 名额
|
||||
```
|
||||
|
||||
### 购卡支付
|
||||
|
||||
```
|
||||
选择卡种 → 创建 Order(PENDING)
|
||||
→ 调用微信支付统一下单 → 返回支付参数
|
||||
→ 前端 wx.requestPayment
|
||||
→ 支付成功 → 微信回调服务端 → 验签
|
||||
→ 更新 Order 为 PAID → 创建 Membership
|
||||
→ 支付失败 → 保持 PENDING,可重新支付
|
||||
```
|
||||
|
||||
### 时段自动生成
|
||||
|
||||
```
|
||||
定时任务(每天凌晨执行)
|
||||
→ 读取所有 isActive 的 WeekTemplate
|
||||
→ 生成未来 7-14 天的 TimeSlot(跳过已存在的日期)
|
||||
→ 检查过期 Membership,标记 EXPIRED
|
||||
→ 检查已过期 TimeSlot,标记 CLOSED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 端点
|
||||
|
||||
### 认证 `/auth`
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | /auth/login | 微信 code 登录,返回 JWT |
|
||||
| POST | /auth/phone | 绑定手机号 |
|
||||
|
||||
### 用户 `/user`
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | /user/profile | 获取个人信息 |
|
||||
| PUT | /user/profile | 更新个人信息 |
|
||||
| GET | /user/stats | 训练统计(本月/累计) |
|
||||
|
||||
### 时段 `/time-slot`
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | /time-slot/available | 查询可约时段(按日期范围) |
|
||||
| GET | /time-slot/:id | 时段详情 |
|
||||
|
||||
### 预约 `/booking`
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | /booking | 创建预约 |
|
||||
| PUT | /booking/:id/cancel | 取消预约 |
|
||||
| GET | /booking/my | 我的预约列表 |
|
||||
| GET | /booking/my/upcoming | 即将上课的预约 |
|
||||
|
||||
### 会员卡 `/membership`
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | /membership/card-types | 可购买的卡种列表 |
|
||||
| GET | /membership/my | 我持有的卡列表 |
|
||||
|
||||
### 支付 `/payment`
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | /payment/create-order | 创建购卡订单 + 发起微信支付 |
|
||||
| POST | /payment/wx-notify | 微信支付回调 |
|
||||
| GET | /payment/orders | 我的订单列表 |
|
||||
|
||||
### 管理 `/admin`(ADMIN 角色)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | /admin/week-template | 获取周模板 |
|
||||
| PUT | /admin/week-template | 更新周模板(全量替换) |
|
||||
| POST | /admin/time-slot/manual | 手动添加时段 |
|
||||
| PUT | /admin/time-slot/:id/close | 关闭某个时段 |
|
||||
| GET | /admin/members | 会员列表 |
|
||||
| GET | /admin/members/:id | 会员详情 |
|
||||
| GET | /admin/bookings | 所有预约记录 |
|
||||
| GET | /admin/card-types | 卡种列表 |
|
||||
| POST | /admin/card-types | 创建卡种 |
|
||||
| PUT | /admin/card-types/:id | 更新卡种 |
|
||||
| DELETE | /admin/card-types/:id | 删除卡种 |
|
||||
| POST | /admin/generate-slots | 手动触发生成时段 |
|
||||
|
||||
### 工作室 `/studio`
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | /studio/info | 获取工作室信息 |
|
||||
| PUT | /admin/studio/info | 更新工作室信息 |
|
||||
|
||||
---
|
||||
|
||||
## 关键业务规则
|
||||
|
||||
1. **预约扣卡**:次卡 → remainingTimes - 1;时间卡 → 仅检查 expireDate 有效性;体验课 → 同次卡逻辑
|
||||
2. **取消退还**:距开课 >= cancelHoursLimit 小时 → 次卡退还次数;否则不退
|
||||
3. **时段状态**:bookedCount >= capacity → 自动标记 FULL
|
||||
4. **卡过期检测**:定时任务每天检查,过期卡标记 EXPIRED,次数用完标记 USED_UP
|
||||
5. **重复预约**:同一用户不可重复预约同一时段
|
||||
6. **金额单位**:所有金额以「分」存储,避免浮点精度问题
|
||||
|
||||
---
|
||||
|
||||
## 不做的事项(YAGNI)
|
||||
|
||||
- 多场馆切换
|
||||
- 小班课
|
||||
- 多教练筛选
|
||||
- 积分系统
|
||||
- 拼团功能
|
||||
- 美团/抖音验券
|
||||
- 体测记录
|
||||
- 签到功能
|
||||
- 代金券
|
||||
Reference in New Issue
Block a user