From b4b3caac7096ebf284280ba9688f0791418e1e99 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Tue, 8 Sep 2026 15:08:29 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E8=AF=BE=E7=A8=8B?= =?UTF-8?q?=E8=A1=A5=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 7 +- deploy-server.sh | 1 + docs/lesson-supplement.md | 29 + .../src/components/LessonSupplementList.vue | 39 ++ packages/app/src/pages.json | 4 + .../app/src/pages/admin/member-detail.vue | 12 +- .../app/src/pages/admin/member-supplement.vue | 211 ++++++ packages/app/src/pages/profile/bookings.vue | 32 +- packages/app/src/pages/profile/info.vue | 511 +++----------- packages/app/src/pages/profile/membership.vue | 636 ++++-------------- packages/app/src/stores/admin.ts | 17 + packages/app/src/utils/request.ts | 9 +- .../migration.sql | 27 + packages/server/prisma/schema.prisma | 28 +- .../lesson-supplement.service.spec.ts | 205 ++++++ .../src/user/__tests__/user.service.spec.ts | 24 +- .../user/dto/create-lesson-supplement.dto.ts | 23 + .../src/user/lesson-supplement.controller.ts | 37 + .../src/user/lesson-supplement.service.ts | 115 ++++ packages/server/src/user/user.module.ts | 7 +- packages/server/src/user/user.service.ts | 21 +- packages/shared/src/index.ts | 2 + packages/shared/src/types/index.ts | 2 + packages/shared/src/types/user.ts | 20 + 24 files changed, 1074 insertions(+), 945 deletions(-) create mode 100644 docs/lesson-supplement.md create mode 100644 packages/app/src/components/LessonSupplementList.vue create mode 100644 packages/app/src/pages/admin/member-supplement.vue create mode 100644 packages/server/prisma/migrations/20260908090000_add_lesson_supplements/migration.sql create mode 100644 packages/server/src/user/__tests__/lesson-supplement.service.spec.ts create mode 100644 packages/server/src/user/dto/create-lesson-supplement.dto.ts create mode 100644 packages/server/src/user/lesson-supplement.controller.ts create mode 100644 packages/server/src/user/lesson-supplement.service.ts diff --git a/CLAUDE.md b/CLAUDE.md index b3e1bae..5ba84c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,4 +85,9 @@ pnpm deploy:server # 部署后端到生产环境 ### Admin Store (`src/stores/admin.ts`) - 聚合所有管理端 API 调用:weekTemplates、cardTypes、studioConfig、members、bookings、orders、stats 等 -- 遵循不可变更新原则:`data` 赋值使用展开运算符 `[...newData]` \ No newline at end of file +- 遵循不可变更新原则:`data` 赋值使用展开运算符 `[...newData]` +### 历史课程补录 +- `LessonSupplement` 独立记录历史累计课时,归属 user 模块;DTO 放 `user/dto`,测试放 `user/__tests__`,前端入口为 `pages/admin/member-supplement.vue`。 +- 补录不创建预约或时段;只增加累计已完成节数,不推测上课日期、天数、时长,不参与月度统计、活跃网格或邀请奖励。 +- 可选择从本人有限次会员卡扣次;补录与扣次必须事务提交,保存实际扣次快照,撤销只返还实际扣次。请求标识用于幂等重试。 +- Prisma 迁移按 `YYYYMMDDHHmmss_description/migration.sql` 存放;补录表采用增量迁移,回退说明维护在 `docs/lesson-supplement.md`,不删除审计记录。 diff --git a/deploy-server.sh b/deploy-server.sh index d843409..5ca76d6 100755 --- a/deploy-server.sh +++ b/deploy-server.sh @@ -117,6 +117,7 @@ echo_info "文件上传完成" # 6. 服务器端安装依赖并重启 echo_info "服务器部署操作..." ssh ${SERVER_USER}@${SERVER_HOST} "bash -l" << 'ENDSSH' +set -e export PATH="$HOME/.nvm/versions/node/v22.17.1/bin:$PATH" SERVER_PATH="/usr/local/web/mp-pilates-server" PM2_APP_NAME="mp-pilates-server" diff --git a/docs/lesson-supplement.md b/docs/lesson-supplement.md new file mode 100644 index 0000000..a9306ef --- /dev/null +++ b/docs/lesson-supplement.md @@ -0,0 +1,29 @@ +# 历史课程补录 + +入口:会员档案 → 上课情况 → 补录上课。一次填写 1–999 节,可备注,默认仅补记录,也可选择本人有限次会员卡扣次(含已过期卡)。不限次卡无需扣次;余额不足阻止提交。 + +一批补录展示为「系统补录 · N 节」,不是 N 个虚拟预约。累计已上课次数(个人中心、会员档案、会员列表已完成)增加 N;累计预约、月度节数/天数/时长、最近 30 天网格、邀请奖励保持正常预约口径,因为补录没有真实上课日期。记录时间明确为补录时间。 + +老师与管理员共用 ADMIN 权限;成员只能读取自己的生效记录。事务保存记录与扣次快照,请求标识唯一,网络重试不重复扣次。撤销软删除,保留操作人与时间,只返还当时实际扣次,过期卡不重新激活。若原卡已转为不限次,撤销阻止并要求先核对卡状态。 + +## 迁移与发布 + +新增 `lesson_supplements` 表及关联索引,不修改既有预约数据。按仓库发布流程先执行 `prisma migrate deploy` 并重新生成 Prisma Client、发布后端,再发布小程序。开发检查不自动触及生产数据库。 + +回退:先回退应用代码,保留新增表即可,旧代码不读取该表。若一定要移除表,须先导出审计数据并核对已扣会员卡余额;仅在未写入任何补录的环境可执行 `DROP TABLE lesson_supplements;`。MySQL DDL 不能靠事务回滚,不在生产自动执行删除。 + +## 本次验证 + +- 补录与用户统计相关 62 项测试通过,覆盖权限、数量校验、仅补记录、扣次、余额不足、过期卡、重复提交、并发冲突、撤销和统计口径。 +- 共享包与后端构建、小程序构建、vue-tsc、Prisma schema 校验通过。 +- 使用真实 Vue 页面与示例接口检查 375/320 宽度布局、十节补录、撤销、余额不足及学员历史记录;不替代微信真机与真实数据库联调。 +- 全量测试存在原有 SchedulerService 测试依赖缺失:未提供 FlashSaleService,8 项失败;其余已执行用例通过。 +- 2026-09-08 已经用户授权执行生产迁移及后端发布;小程序仍需单独发布。 + +## 生产发布记录(2026-09-08) + +- 执行 `pnpm deploy:server`,迁移 `20260908090000_add_lesson_supplements` 成功,全部 4 个迁移处于最新状态。 +- 发布前数据库及后端备份保存在服务器 `/usr/local/web/mp-pilates-backups/20260908-145852/`。 +- 新表读取成功,验收时为 0 条记录;未创建测试补录或修改学员余额。 +- 内网及公网 `https://focus.richarjiang.com/api/health` 均返回 `success: true`、`status: ok`;PM2 `mp-pilates-server` 为 online,重启计数为 0。 +- 部署脚本增加远端 `set -e`,使依赖安装或迁移失败时停止发布。 diff --git a/packages/app/src/components/LessonSupplementList.vue b/packages/app/src/components/LessonSupplementList.vue new file mode 100644 index 0000000..c17d786 --- /dev/null +++ b/packages/app/src/components/LessonSupplementList.vue @@ -0,0 +1,39 @@ + + + diff --git a/packages/app/src/pages.json b/packages/app/src/pages.json index b00b98e..dc7e828 100644 --- a/packages/app/src/pages.json +++ b/packages/app/src/pages.json @@ -111,6 +111,10 @@ "navigationStyle": "custom" } }, + { + "path": "pages/admin/member-supplement", + "style": { "navigationStyle": "custom" } + }, { "path": "pages/admin/member-arrange", "style": { diff --git a/packages/app/src/pages/admin/member-detail.vue b/packages/app/src/pages/admin/member-detail.vue index 9ac6de9..2dc8f87 100644 --- a/packages/app/src/pages/admin/member-detail.vue +++ b/packages/app/src/pages/admin/member-detail.vue @@ -41,7 +41,10 @@ - + + + + {{ detail.stats.totalBookings }} @@ -49,7 +52,7 @@ {{ detail.stats.completedBookings }} - 已完成 + 累计上课 {{ detail.stats.cancelledBookings }} @@ -221,6 +224,10 @@ async function loadDetail() { } } +function goSupplement() { + if (userId.value) uni.navigateTo({ url: `/pages/admin/member-supplement?userId=${userId.value}` }) +} + function goEdit() { if (!userId.value) return uni.navigateTo({ url: `/pages/admin/member-edit?userId=${userId.value}` }) @@ -321,6 +328,7 @@ onShow(() => { .section--last { padding-bottom: 24rpx; } .section-label { display: block; font-size: 28rpx; font-weight: 500; margin-bottom: 20rpx; } .section-heading { display: flex; justify-content: space-between; align-items: baseline; gap: 16rpx; } +.supplement-entry { margin: 0; padding: 8rpx 18rpx; line-height: 1.5; font-size: 23rpx; border-radius: 999rpx; background: #edf2ec; color: #617d73; &::after { border: none; } } .section-note { font-size: 22rpx; color: var(--muted); } .stats-strip { display: flex; padding: 26rpx 0; diff --git a/packages/app/src/pages/admin/member-supplement.vue b/packages/app/src/pages/admin/member-supplement.vue new file mode 100644 index 0000000..a980406 --- /dev/null +++ b/packages/app/src/pages/admin/member-supplement.vue @@ -0,0 +1,211 @@ +