Files
mp-pilates/CLAUDE.md
2026-09-18 18:26:47 +08:00

8.9 KiB
Raw Blame History

CLAUDE.md

本文档为 Claude Code (claude.ai/code) 在本项目中工作时提供指导。

项目概述

这是一个普拉提预约微信小程序项目,后端采用 NestJS 框架。项目使用 pnpm monorepo 结构,包含 3 个包:

  • packages/app - Vue 3 + uni-app(微信小程序前端)
  • packages/server - NestJS(后端 API 服务)
  • packages/shared - TypeScript 类型定义、枚举、常量(前后端共用)

常用命令

# 开发
pnpm dev:server      # 启动 NestJS 后端(热重载)
pnpm dev:app         # 构建 uni-app 为微信小程序

# 构建
pnpm build:shared    # 编译共享类型
pnpm build:server    # 构建 NestJS 后端
pnpm build:app       # 构建微信小程序

# 测试与代码检查
pnpm test            # 运行所有测试(仅 server)
pnpm lint            # 运行 ESLint(仅 server)

# 数据库相关(位于 packages/server 目录)
cd packages/server
pnpm prisma:generate  # 生成 Prisma 客户端
pnpm prisma:migrate   # 执行数据库迁移
pnpm prisma:seed      # 填充测试数据
pnpm test:watch      # 监听模式运行测试

# 部署
pnpm deploy:server   # 部署后端到生产环境

架构说明

前端 (packages/app)

  • 基于 Vue 3 + uni-app 框架,主攻微信小程序平台
  • 页面目录:src/pages/(包含 home、booking、card、profile、admin 等模块)
  • 组件目录:src/components/
  • 状态管理:Pinia
  • 样式:SCSS

后端 (packages/server)

  • 框架:NestJS + Prisma ORM
  • 核心模块:auth(认证)、user(用户)、booking(预约)、membership(会员卡)、payment(支付)、studio(场馆)、time-slot(时段)、scheduler(定时任务)、admin(管理)
  • 认证:JWT + 微信登录
  • 定时任务:@nestjs/schedule
  • 数据库:SQLite(开发)/ MySQL(生产)

共享包 (packages/shared)

  • TypeScript 接口和类型定义
  • 枚举值定义
  • 前后端共用的 DTO 类型

API 结构

  • 所有接口统一前缀:/api
  • RESTful 风格接口
  • 全局拦截器:日志记录、响应包装
  • 全局过滤器:异常处理

数据库

  • Prisma schema 位于 packages/server/prisma/schema.prisma
  • 核心数据模型:User、Studio、TimeSlot、Booking、Membership、CardType、Order
  • 注意:查询会员列表时,booking 统计通过 groupBy 批量获取,避免 N+1 查询

卡类型枚举

  • CardTypeCategory (TIMES/DURATION/TRIAL) 定义在 packages/shared/src/enums.ts
  • 会员管理筛选使用特殊值 ACTIVE 表示持有 ACTIVE 状态会员卡的会员用户(页面默认),NONE 表示无卡/无有效会员(两者不在卡种枚举中)
  • 前端选项硬编码在 src/pages/admin/members.vue 的 cardTypeOptions,需与枚举保持同步

管理后台 API 模式

  • /admin/members 支持 page, limit, search, cardType 参数
  • cardType=ACTIVE → 持有任意 ACTIVE 状态会员卡;cardType=NONE → 无 ACTIVE 状态会员卡;省略参数查看全部用户;其他值对应 CardTypeCategory
  • 预约统计(total/completed/cancelled)通过 groupBy 批量查询

筛选组件模式

  • picker 筛选使用 300ms debounce 再触发加载,避免频繁请求
  • 列表分页使用 onReachBottom + hasMore 标志位实现无限滚动

Admin Store (src/stores/admin.ts)

  • 聚合所有管理端 API 调用:weekTemplates、cardTypes、studioConfig、members、bookings、orders、stats 等
  • 遵循不可变更新原则:data 赋值使用展开运算符 [...newData]

历史课程补录

  • LessonSupplement 独立记录历史累计课时,归属 user 模块;DTO 放 user/dto,测试放 user/__tests__,前端入口为 pages/admin/member-supplement.vue。
  • 补录不创建预约或时段;只增加累计已完成节数,不推测上课日期、天数、时长,不参与月度统计、活跃网格或邀请奖励。
  • 可选择从本人有限次会员卡扣次;补录与扣次必须事务提交,保存实际扣次快照,撤销只返还实际扣次。请求标识用于幂等重试。
  • Prisma 迁移按 YYYYMMDDHHmmss_description/migration.sql 存放;补录表采用增量迁移,回退说明维护在 docs/lesson-supplement.md,不删除审计记录。

月度教学统计

  • 统计归属 admin 模块,服务放 admin/teaching-analytics.service.ts,测试放 admin/__tests__;共享契约放 shared/src/types/teaching-analytics.ts,页面为 pages/admin/analytics.vue。
  • 按 TimeSlot.date 所属自然月查询,不按预约创建或核销日期;日期列以 UTC 日历值读取,中国时间用于判断课程结束。
  • 当前无老师归属字段,统计范围是工作室;operatorId 不是授课老师。COMPLETED 是系统完成状态,不代表签到。
  • 已上课程按时段去重,时长按已完成时段累加;上课人次按 COMPLETED 预约计数,学员按 userId 去重。取消、未出席、待确认、已确认独立计数。无日期补录不参与。
  • 月历、学员排行和会员卡分布统计已完成记录;明细可组合日期、学员、状态筛选。新增测试目录只放该服务的 *.spec.ts。

个人中心会员卡包

  • 个人中心资料区域以单行展示持有卡种和张数,下方每张卡以单行浅色进度槽承载卡名和用量文字,不使用大卡片或轮播;点击进入我的会员卡。OwnedMembershipCard.vue 在我的会员卡页面展示余额、已用进度和到期日。
  • 累计上课、本月上课、剩余课时集中在我的会员卡页面,个人资料卡不重复展示汇总。
  • 次数限制以 remainingTimes 是否为 null 判断,不能按卡种推断。次数进度表示已用占总次数,已用包括预约占用;不限次卡不伪造耗课数。
  • 会员卡加载失败显示重试,不当作无卡;会话变化时丢弃旧请求结果。

课后评价与成长档案

  • 评价归属 booking 模块,档案归属 user 模块;共享契约放 shared/src/types/member-care.ts,页面沿用 booking/profile/admin 目录,不新增顶层业务目录。

  • COMPLETED 后立即可评价,无 24 小时截止;完成后 24 小时仅提醒未评价预约。唯一 bookingId 防重复,提醒状态保存在 Booking 上,定时任务原子领取,未知发送结果不自动重发。

  • 星级均分与 NPS 分开:NPS 仅使用可选 0–10 推荐意愿(9–10 推荐者、0–6 贬损者),按中国自然月聚合并展示样本数。

  • 教练私密笔记必须在服务端过滤;课程批注必须属于该学员的已完成预约。体测允许缺项,不以缺项当 0;累计课时包含有效补录,里程碑为 10/30/50 节。

  • 照片仅用于学员与馆主之间的档案展示,不能用于公开宣传。学员本人按照片授权/撤回,馆主不能代授权。与馆图共用 COS 桶,对象前缀 progress/,上传为私有 ACL,读取用短时签名,禁止落库公共链接;上传凭证绑定学员及照片记录。

  • 新增迁移目录只放 migration.sql,测试沿用各模块 tests;部署配置与验收清单放 docs/member-care.md。

  • 成长档案的两端共用 components/MemberProgress.vue,仅此跨端组件直接请求 progress API,避免主包引用 admin 分包 Store;馆主评价页面仍通过 admin Store 访问。

个人身体画像

  • 线上获客归属独立 body-portrait 模块,不是成长档案的一个页面。共享契约在 packages/shared/src/types/body-portrait.ts,规格在 docs/body-portrait.md。
  • 评分只在服务端规则引擎计算;匿名测评用访问令牌哈希,不用可枚举 ID 做权限。
  • 安全分流不计分。完整报告需登录认领,体验预约需手机号。现有 TRIAL 体验卡承接转化。
  • 馆主「今日经营助手」只展示待办,不做成通用 CRM。主包不得引用 admin store。

教学案例展示

  • 归属 studio 模块:teaching-case 服务、控制器、DTO 与 tests;共享契约放 shared/src/types/teaching-case.ts。公开页面放 pages/home,管理页沿用 pages/admin,公共展示组件放 components。
  • 独立于私密成长档案;不得复用 progress/ 照片。教学案例上传使用 teaching-cases/ 私有对象、资产登记和完成校验;公开接口仅为已发布且已确认公开授权的案例签发短时图片链接。
  • 案例支持 1–9 组双图对比或已合成单图,课时为正整数(节);标题、化名、练习背景和变化说明不作疗效承诺。授权记录只对管理员可见,发布需确认学员已单独同意公开宣传。
  • 保存采用版本校验;撤回授权自动下架。游客首页优先展示最多 3 个案例,更多通过分页浏览;登录用户预约入口优先。无已发布案例时隐藏模块,故障不阻断首页。
  • 迁移目录仅放 migration.sql;发布、回退、图片清理和真机验收约定放 docs/teaching-cases.md。未引用资产保留供重试,清理前核对引用且不得删除有效案例图片。