Files
venue-miniapp/miniprogram/docs/superpowers/plans/2026-07-12-comprehensive-venue-frontend-integration.md
T

480 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 综合场馆新增业务前端对接 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 }` 残留。
- 商城、持卡、可约排课、次卡预约、储蓄卡充值和个人中心形成完整可操作链路。
- 综合场馆与冰场馆首页均不展示近期课程。
- 工作区中用户原有的无关改动未被提交或覆盖。