普拉提私教约课小程序 — 设计文档
概述
为单人普拉提工作室开发的微信小程序约课系统。核心功能:学员在线预约私教课、购买会员卡、查看训练记录;教练管理可约时段和会员。
约束
- 单一工作室、单一教练,不涉及多场馆/多教练切换
- 仅私教课,不涉及小班课
- 微信小程序为唯一客户端
技术架构
Monorepo 结构
技术选型
| 层 |
选型 |
| 包管理 |
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 天,标注星期几。
筛选栏(简化版):
- 时段筛选(全部 / 上午 / 下午 / 晚上)
- 单教练,无需老师筛选
时段列表:
- 时段卡片:时间段、剩余名额、预约按钮
- 已约满 → 灰色置灰
- 已预约 → 显示「已预约」+「取消」
预约流程:
- 点击「预约」→ 检查是否有有效卡 → 无卡则引导购卡
- 有卡 → 确认预约弹窗(时段信息 + 扣卡信息)
- 确认 → 扣减卡次 / 验证时间卡有效期 → 预约成功
取消规则:
- 开课前 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 |
更新时间 |
业务流程
用户登录
预约
取消预约
购卡支付
时段自动生成
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 |
更新工作室信息 |
关键业务规则
- 预约扣卡:次卡 → remainingTimes - 1;时间卡 → 仅检查 expireDate 有效性;体验课 → 同次卡逻辑
- 取消退还:距开课 >= cancelHoursLimit 小时 → 次卡退还次数;否则不退
- 时段状态:bookedCount >= capacity → 自动标记 FULL
- 卡过期检测:定时任务每天检查,过期卡标记 EXPIRED,次数用完标记 USED_UP
- 重复预约:同一用户不可重复预约同一时段
- 金额单位:所有金额以「分」存储,避免浮点精度问题
不做的事项(YAGNI)
- 多场馆切换
- 小班课
- 多教练筛选
- 积分系统
- 拼团功能
- 美团/抖音验券
- 体测记录
- 签到功能
- 代金券