docs: add comprehensive frontend implementation plan

This commit is contained in:
gqt
2026-07-12 19:09:22 +08:00
parent 5d38e435e8
commit 0941114600
@@ -0,0 +1,479 @@
# 综合场馆新增业务前端对接 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 完成综合场馆排课接口迁移、商城储蓄卡与次卡购买、已有次卡可约排课、个人中心入口和支付规则,并移除两个场馆首页的近期课程区块。
**Architecture:** 延续现有 uni-app Vue 3 的 `pages/comprehensive` 页面层与 `modules/comprehensive/api` 接口映射层。会员端课程统一使用排课 `session`,商城复用现有订单支付页,普通课程预约与次卡预约保持独立数据模型;不新增依赖、不改后端、不重构冰场馆其他业务。
**Tech Stack:** uni-app、Vue 3 `<script setup>`、Pinia、JavaScript、Node.js 静态验证脚本、npm
## Global Constraints
- 有效前端目录仅为 `场馆小程序/miniprogram`,不得修改两个已废弃的 `冰场馆小程序/miniprogram``综合场馆小程序/miniprogram`
- 产品界面使用“储蓄卡”称谓;代码、接口和后端模型沿用 `storedValue``stored_value` 和“储值卡”。
- 排课详情和预约使用 `session.id``course_id` 仅表示课程模板,不能用于预约。
- 储蓄卡订单仅允许微信支付;次卡套餐订单允许余额支付和微信支付。
- 次卡预约不创建支付订单,预约时不扣次数,后台核销时才扣减。
- 普通预约和次卡预约的嵌套 `session` 不保证包含 `courts`,相关页面不得依赖该字段。
- 综合场馆与冰场馆首页均不再展示近期课程,但课程列表和详情入口必须保留。
- 不修改场地页面视觉、不实现后台锁场、不修改冰场馆其他业务。
- 不新增 npm 依赖;继续使用现有请求封装、登录 store、样式变量和分页约定。
---
## 文件结构与职责
### 新建文件
| 文件 | 职责 |
|---|---|
| `src/modules/comprehensive/api/membership.js` | 次卡套餐、持卡、流水、可约排课和次卡预约的请求与映射 |
| `src/modules/comprehensive/api/storedValue.js` | 储蓄卡商品和购买订单的请求与映射 |
| `src/pages/comprehensive/mall/index.vue` | 商城分区展示次卡套餐和储蓄卡,创建订单并跳转支付 |
| `src/pages/comprehensive/membership/cards.vue` | 我的次卡分页列表 |
| `src/pages/comprehensive/membership/card-detail.vue` | 次卡信息、适用课程和使用流水 |
| `src/pages/comprehensive/membership/available-sessions.vue` | 当前已有次卡能够预约的排课列表与选卡预约 |
| `src/pages/comprehensive/membership/booking-detail.vue` | 次卡预约详情 |
| `scripts/verify-comprehensive-session-contract.mjs` | 验证排课 URL、`session_id``booking.session` 和首页移除课程 |
| `scripts/verify-comprehensive-commerce.mjs` | 验证商城、次卡页面、支付限制、个人中心入口和路由注册 |
### 修改文件
| 文件 | 修改职责 |
|---|---|
| `src/modules/comprehensive/api/course.js` | `/api/sessions``{ session_id }` |
| `src/modules/comprehensive/api/content.js` | 首页不再映射近期排课 |
| `src/modules/comprehensive/api/booking.js` | 普通预约改读 `session_id``session` |
| `src/pages/comprehensive/courses/*.vue` | 页面变量和提交语义改为排课 |
| `src/pages/comprehensive/bookings/index.vue` | 普通预约/次卡预约切换展示 |
| `src/pages/comprehensive/bookings/course-detail.vue` | 普通预约详情读取 `booking.session`,不依赖场地字段 |
| `src/pages/comprehensive/profile/index.vue` | 余额、充值、我的次卡、流水和消费记录入口 |
| `src/pages/comprehensive/profile/orders.vue` | 新增会员卡、储值卡订单筛选 |
| `src/pages/comprehensive/pay/index.vue` | 储值卡订单强制微信支付并复查订单状态 |
| `src/pages/comprehensive/home/index.vue` | 移除近期课程区块和相关状态 |
| `src/pages/ice/home/index.vue` | 移除近期课程区块和相关状态 |
| `src/modules/ice/api/home.js` | 首页请求不再传课程参数、不再映射近期课程 |
| `src/pages.json` | 注册商城和次卡页面 |
| `package.json` | 注册两条新的验证命令 |
---
### Task 1: 排课接口与普通课程预约迁移
**Files:**
- Create: `scripts/verify-comprehensive-session-contract.mjs`
- Modify: `src/modules/comprehensive/api/course.js`
- Modify: `src/modules/comprehensive/api/booking.js`
- Modify: `src/pages/comprehensive/courses/index.vue`
- Modify: `src/pages/comprehensive/courses/detail.vue`
- Modify: `src/pages/comprehensive/courses/confirm.vue`
- Modify: `src/pages/comprehensive/bookings/index.vue`
- Modify: `src/pages/comprehensive/bookings/course-detail.vue`
- Modify: `package.json`
**Interfaces:**
- Produces: `toSession(backendSession): SessionViewModel`
- Produces: `courseApi.getSessions(params)``courseApi.getSessionDetail(sessionId)``courseApi.createCourseBooking(sessionId)`
- Produces: `toCourseBooking()` 返回 `{ session_id, session, order, ...statusFields }`
- Consumes: 现有 `request()``toPagination()``toPageQuery()`、日期和金额工具
- [ ] **Step 1: 先建立会失败的排课契约验证**
新增验证脚本,至少断言:
```js
expectIncludes(courseApi, "url: '/api/sessions'", 'session list endpoint')
expectIncludes(courseApi, 'url: `/api/sessions/${id}`', 'session detail endpoint')
expectIncludes(courseApi, 'data: { session_id: sessionId }', 'booking payload')
expectExcludes(courseApi, "url: '/api/courses'", 'legacy course endpoint')
expectIncludes(bookingApi, 'session_id: backendBooking.session_id', 'booking session id')
expectIncludes(bookingApi, 'session: toBookingSession(backendBooking.session)', 'booking session mapper')
expectExcludes(bookingApi, 'course: toBookingCourse(backendBooking.course)', 'legacy booking course')
```
`package.json` 增加:
```json
"verify:comprehensive-session": "node scripts/verify-comprehensive-session-contract.mjs"
```
运行 `npm run verify:comprehensive-session`,预期因旧 `/api/courses``course_id``booking.course` 而失败。
- [ ] **Step 2: 完成 API 映射与页面迁移**
`course.js` 的核心接口固定为:
```js
export const toSession = (backendSession = {}) => ({
id: backendSession.id ?? null,
course_id: backendSession.course_id ?? null,
title: backendSession.title || '',
cover_image: backendSession.cover_image || '',
coach_id: backendSession.coach_id ?? null,
coach_name: backendSession.coach_name || '',
start_at: backendSession.start_at || '',
end_at: backendSession.end_at || '',
capacity: toNumber(backendSession.capacity),
booked_count: toNumber(backendSession.booked_count),
remaining_count: toNumber(backendSession.remaining_count),
price: backendSession.price || '0.00',
status: backendSession.status || '',
status_label: backendSession.status_label || backendSession.status || '',
courts: toArray(backendSession.courts).map(toCourseCourt),
detail_html: backendSession.detail_html || '',
created_at: backendSession.created_at || '',
updated_at: backendSession.updated_at || ''
})
export const courseApi = {
getSessions(params = {}) {
return request({ url: '/api/sessions', data: toSessionQuery(params) })
.then((data) => toPagination(data, toSession))
},
getSessionDetail(id) {
return request({ url: `/api/sessions/${id}` }).then(toSession)
},
createCourseBooking(sessionId) {
return request({
url: '/api/bookings/course',
method: 'POST',
data: { session_id: sessionId }
})
}
}
```
普通预约映射改读 `backendBooking.session`;嵌套 session 映射允许 `courts` 缺失并返回空数组。课程列表、详情、确认页内部变量改为 `session/sessions`,确认页提交 `session.id`。预约列表和详情改读 `item.session``booking.session`,并删除对预约嵌套对象 `courts.map(...)` 的强依赖。
- [ ] **Step 3: 验证并提交这一条完整链路**
运行:
```bash
npm run verify:comprehensive-session
npm run build:mp-weixin
```
预期:验证脚本打印 `Comprehensive session contract verification passed.`;微信小程序构建退出码为 0。随后提交:
```bash
git add package.json scripts/verify-comprehensive-session-contract.mjs src/modules/comprehensive/api/course.js src/modules/comprehensive/api/booking.js src/pages/comprehensive/courses src/pages/comprehensive/bookings
git commit -m "feat: migrate comprehensive courses to sessions"
```
---
### Task 2: 商城、次卡套餐与储蓄卡购买
**Files:**
- Create: `src/modules/comprehensive/api/membership.js`
- Create: `src/modules/comprehensive/api/storedValue.js`
- Create: `src/pages/comprehensive/mall/index.vue`
- Create: `scripts/verify-comprehensive-commerce.mjs`
- Modify: `src/pages.json`
- Modify: `package.json`
**Interfaces:**
- Produces: `membershipApi.getPlans()``membershipApi.createMembershipOrder(planId)`
- Produces: `storedValueApi.getCards()``storedValueApi.createOrder(cardId)`
- Produces: 商城购买成功统一跳转 `/pages/comprehensive/pay/index?order_id={id}`
- Consumes: `request()``authStorage`、综合场馆 session store、现有支付页
- [ ] **Step 1: 扩展静态验证,先覆盖商城路由和接口契约**
验证脚本检查以下精确契约:
```js
expectIncludes(membershipApi, "url: '/api/membership/plans'", 'membership plans')
expectIncludes(membershipApi, "url: '/api/membership/orders'", 'membership order')
expectIncludes(membershipApi, 'data: { plan_id: planId }', 'membership payload')
expectIncludes(storedValueApi, "url: '/api/stored-value/cards'", 'stored value cards')
expectIncludes(storedValueApi, "url: '/api/stored-value/orders'", 'stored value order')
expectIncludes(storedValueApi, 'data: { card_id: cardId }', 'stored value payload')
expectIncludes(pagesJson, 'pages/comprehensive/mall/index', 'mall route')
expectIncludes(mallPage, '储蓄卡', 'stored value UI copy')
expectIncludes(mallPage, 'face_value', 'face value display')
```
增加命令:
```json
"verify:comprehensive-commerce": "node scripts/verify-comprehensive-commerce.mjs"
```
运行 `npm run verify:comprehensive-commerce`,预期因文件和路由尚不存在而失败。
- [ ] **Step 2: 实现两个 API 模块和商城页面**
`membership.js` 的套餐和订单接口返回稳定视图模型:
```js
const toPlan = (item = {}) => ({
id: item.id ?? null,
name: item.name || '',
price: item.price || '0.00',
total_count: toNumber(item.total_count),
is_active: item.is_active === true,
courses: toArray(item.courses).map((course) => ({
id: course.id ?? null,
title: course.title || ''
})),
created_at: item.created_at || ''
})
```
`storedValue.js` 映射 `id/name/price/face_value/is_active/created_at`。商城并行加载套餐和储蓄卡,分区展示;储蓄卡卡片必须同时显示“支付 ¥price”和“到账 ¥face_value”。购买前检查登录,创建请求期间禁用对应按钮,成功后只使用后端返回的 `order.id` 跳转支付页。
`pages.json` 注册商城路由,标题使用“商城”。
- [ ] **Step 3: 验证商城并提交**
运行:
```bash
npm run verify:comprehensive-commerce
npm run build:mp-weixin
```
预期两条命令退出码均为 0。提交:
```bash
git add package.json scripts/verify-comprehensive-commerce.mjs src/modules/comprehensive/api/membership.js src/modules/comprehensive/api/storedValue.js src/pages/comprehensive/mall/index.vue src/pages.json
git commit -m "feat: add comprehensive membership and stored value mall"
```
---
### Task 3: 我的次卡、可约排课和次卡预约
**Files:**
- Create: `src/pages/comprehensive/membership/cards.vue`
- Create: `src/pages/comprehensive/membership/card-detail.vue`
- Create: `src/pages/comprehensive/membership/available-sessions.vue`
- Create: `src/pages/comprehensive/membership/booking-detail.vue`
- Modify: `src/modules/comprehensive/api/membership.js`
- Modify: `src/pages/comprehensive/bookings/index.vue`
- Modify: `src/pages.json`
- Modify: `scripts/verify-comprehensive-commerce.mjs`
**Interfaces:**
- Produces: `getCards(params)``getUsageRecords(cardId, params)``getAvailableSessions(params)`
- Produces: `createCardBooking({ cardId, sessionId })``getCardBookings(params)``getCardBookingDetail(bookingId)`
- Produces: `AvailableSession.available_card_ids: number[]`
- Consumes: Task 2 的 `getPlans()`,用于按 `plan_id` 在次卡详情补充适用课程;不得调用不存在的单卡详情接口
- [ ] **Step 1: 先把持卡和预约契约加入验证脚本**
加入以下断言后运行验证,预期失败:
```js
expectIncludes(membershipApi, "url: '/api/membership/cards'", 'member cards')
expectIncludes(membershipApi, 'cards/${cardId}/usage-records', 'card usage records')
expectIncludes(membershipApi, "url: '/api/membership/available-courses'", 'available sessions')
expectIncludes(membershipApi, "url: '/api/membership/bookings'", 'card bookings')
expectIncludes(membershipApi, 'data: { card_id: cardId, session_id: sessionId }', 'card booking payload')
expectIncludes(availablePage, 'available_card_ids', 'available card selection')
expectExcludes(bookingDetail, 'booking.order', 'card booking has no order')
```
- [ ] **Step 2: 实现次卡 API 与四个页面**
`membership.js` 统一输出:
```js
createCardBooking({ cardId, sessionId }) {
return request({
url: '/api/membership/bookings',
method: 'POST',
data: { card_id: cardId, session_id: sessionId }
}).then(toCardBooking)
}
```
页面规则:
- `cards.vue` 分页显示 `plan_name/total_count/remaining_count/status_label`
- `card-detail.vue` 并行请求 `getPlans()` 和该卡使用流水,以 `plan_id` 匹配套餐并展示适用课程;找不到套餐时仍展示卡和流水。
- `available-sessions.vue` 只展示 `/available-courses` 返回的数据。从卡详情带 `card_id` 进入时过滤不含该 ID 的排课;未指定卡时,单个 `available_card_ids` 自动选中,多个 ID 让用户选择。
- 创建次卡预约后直接跳 `membership/booking-detail?id={booking.id}`,不进入支付页。
- `booking-detail.vue` 展示 `booking.session``booking.card` 和状态时间,不展示订单与自行取消按钮,也不依赖 `session.courts`
- `bookings/index.vue` 在课程业务下增加“普通预约/次卡预约”切换,分别请求 `/bookings/courses``/membership/bookings`;次卡预约金额区域显示“次卡预约”。
`pages.json` 注册四个页面及明确的导航标题。
- [ ] **Step 3: 验证持卡预约全链路并提交**
运行:
```bash
npm run verify:comprehensive-commerce
npm run verify:comprehensive-session
npm run build:mp-weixin
```
预期全部退出码为 0。提交:
```bash
git add scripts/verify-comprehensive-commerce.mjs src/modules/comprehensive/api/membership.js src/pages/comprehensive/membership src/pages/comprehensive/bookings/index.vue src/pages.json
git commit -m "feat: add membership card course booking flow"
```
---
### Task 4: 个人中心、消费记录与储蓄卡支付规则
**Files:**
- Modify: `src/pages/comprehensive/profile/index.vue`
- Modify: `src/pages/comprehensive/profile/orders.vue`
- Modify: `src/pages/comprehensive/pay/index.vue`
- Modify: `scripts/verify-comprehensive-commerce.mjs`
**Interfaces:**
- Consumes: `memberApi.getBalance()`、商城路由、我的次卡路由、现有订单 API
- Produces: 储值卡订单 `business_type === 'stored_value'` 时只能调用 `payOrderByWechat()`
- Produces: 订单筛选增加 `membership``stored_value`
- [ ] **Step 1: 先增加个人中心和支付限制验证**
```js
expectIncludes(profilePage, '/pages/comprehensive/mall/index', 'mall entry')
expectIncludes(profilePage, '/pages/comprehensive/membership/cards', 'my cards entry')
expectIncludes(profilePage, '/pages/comprehensive/profile/balance', 'balance records entry')
expectIncludes(profilePage, '/pages/comprehensive/profile/orders', 'consumption records entry')
expectIncludes(orderPage, "value: 'membership'", 'membership filter')
expectIncludes(orderPage, "value: 'stored_value'", 'stored value filter')
expectIncludes(payPage, "order.value?.business_type === 'stored_value'", 'stored value payment guard')
```
运行 `npm run verify:comprehensive-commerce`,预期新断言失败。
- [ ] **Step 2: 扩展个人中心、订单和支付页**
个人中心登录后请求余额并展示,菜单增加:
```js
{ title: '充值', url: '/pages/comprehensive/mall/index' }
{ title: '我的次卡', url: '/pages/comprehensive/membership/cards' }
{ title: '余额流水', url: '/pages/comprehensive/profile/balance' }
{ title: '消费记录', url: '/pages/comprehensive/profile/orders' }
```
订单筛选增加“会员卡”和“储值卡”。支付页定义:
```js
const isStoredValueOrder = computed(() => order.value?.business_type === 'stored_value')
const canUseBalance = computed(() => {
if (isStoredValueOrder.value) return false
return toAmountNumber(balance.value.balance) >= toAmountNumber(order.value?.amount)
})
```
储值卡订单隐藏余额支付项、默认 `wechat``selectMethod('balance')``pay()` 都必须有储值卡保护。微信 `uni.requestPayment` 成功后重新调用 `loadPage()` 获取后端订单状态;不在前端直接增加余额。
- [ ] **Step 3: 验证并提交个人中心与支付改造**
运行:
```bash
npm run verify:comprehensive-commerce
npm run build:mp-weixin
```
预期全部通过。提交:
```bash
git add scripts/verify-comprehensive-commerce.mjs src/pages/comprehensive/profile/index.vue src/pages/comprehensive/profile/orders.vue src/pages/comprehensive/pay/index.vue
git commit -m "feat: connect comprehensive profile and stored value payment"
```
---
### Task 5: 移除首页近期课程并完成整体回归
**Files:**
- Modify: `src/pages/comprehensive/home/index.vue`
- Modify: `src/modules/comprehensive/api/content.js`
- Modify: `src/pages/ice/home/index.vue`
- Modify: `src/modules/ice/api/home.js`
- Modify: `scripts/verify-comprehensive-session-contract.mjs`
- Modify: `scripts/verify-home-venue-switch.mjs`(仅在原断言受页面结构变化影响时调整,保留场馆切换验证)
**Interfaces:**
- 综合场馆首页继续消费 `banners/articles/venue`,忽略后端 `upcoming_sessions`
- 冰场馆首页继续消费 `banners/activities/venue`,不请求或映射 `upcoming_courses`
- 两个场馆课程 Tab 与课程页面不变
- [ ] **Step 1: 先加入首页移除课程的失败验证**
```js
expectExcludes(comprehensiveHome, '近期课程', 'comprehensive recent courses section')
expectExcludes(comprehensiveHome, 'upcomingCourses', 'comprehensive recent course state')
expectExcludes(comprehensiveContentApi, 'upcoming_courses', 'legacy comprehensive home mapping')
expectExcludes(comprehensiveContentApi, 'upcoming_sessions', 'unused comprehensive home mapping')
expectExcludes(iceHome, '近期课程', 'ice recent courses section')
expectExcludes(iceHome, 'upcomingCourses', 'ice recent course state')
expectExcludes(iceHomeApi, 'course_limit', 'ice recent course request')
expectExcludes(iceHomeApi, 'upcoming_courses', 'ice recent course mapping')
```
运行 `npm run verify:comprehensive-session`,预期因两个首页仍包含近期课程而失败。
- [ ] **Step 2: 删除两个首页的近期课程展示和无用数据流**
综合场馆首页删除近期课程模板、`upcomingCourses` 状态、`formatTimeRange` 导入和课程相关样式;`content.js` 的首页模型只返回:
```js
{
banners: toArray(backendData.banners).map(toBanner),
articles: toArray(backendData.articles).map(toArticle),
venue: { name: backendData.venue?.name || '' }
}
```
冰场馆首页删除近期课程模板、状态、格式化函数和相关样式;`home.js` 不再导入课程映射、不再发送 `course_limit/course_date`,首页模型只保留 `banners/activities/venue`。不要删除 `VenueTabBar` 的课程 Tab,也不要修改两个场馆的课程列表和详情页面。
- [ ] **Step 3: 执行完整验证、人工联调清单并提交**
自动验证:
```bash
npm run verify:tabbar
npm run verify:startup
npm run verify:home-switch
npm run verify:comprehensive-auth
npm run verify:auth-agreements
npm run verify:comprehensive-session
npm run verify:comprehensive-commerce
npm run build:mp-weixin
npm run build:h5
```
预期所有命令退出码为 0。随后在微信开发者工具连接综合场馆后端,人工验证:
1. 排课列表、详情、普通预约和支付使用同一 `session.id`
2. 商城同时展示次卡套餐和储蓄卡,储蓄卡明确显示售价与到账面值。
3. 次卡支付后出现在我的次卡;“去预约”只展示当前已有次卡能约的排课。
4. 次卡预约成功不进入支付页,后台核销/取消后刷新状态正确。
5. 储蓄卡订单只能微信支付,成功后余额和充值流水刷新正确。
6. 个人中心可进入充值、我的次卡、余额流水和消费记录。
7. 两个场馆首页均无近期课程,课程 Tab 仍可正常进入。
8. 包场、门票、订单详情、登录和场馆切换无回归。
最后提交:
```bash
git add src/pages/comprehensive/home/index.vue src/modules/comprehensive/api/content.js src/pages/ice/home/index.vue src/modules/ice/api/home.js scripts/verify-comprehensive-session-contract.mjs scripts/verify-home-venue-switch.mjs
git commit -m "feat: finalize comprehensive frontend integration"
```
---
## 完成定义
- 五个 Task 均有独立提交且验证通过。
- `npm run build:mp-weixin``npm run build:h5` 均成功。
- 新增 API 没有旧 `/api/courses` 或预约 `{ course_id }` 残留。
- 商城、持卡、可约排课、次卡预约、储蓄卡充值和个人中心形成完整可操作链路。
- 综合场馆与冰场馆首页均不展示近期课程。
- 工作区中用户原有的无关改动未被提交或覆盖。