Files
venue-miniapp/miniprogram/docs/superpowers/plans/2026-06-08-merged-venue-miniapp.md

1684 lines
50 KiB
Markdown
Raw Permalink 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.
# Merged Venue Miniapp 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:** Build a new merged uni-app WeChat mini program under `场馆小程序/miniprogram` that lets users choose and switch between ice venue and comprehensive venue while keeping both Django backends, auth tokens, APIs, and business pages isolated.
**Architecture:** The app uses one public venue selection shell and two isolated modules: `src/modules/ice` and `src/modules/comprehensive`. Route pages live under `src/pages/ice/*` and `src/pages/comprehensive/*`, while each module imports only its own API client and state. No backend code is changed.
**Tech Stack:** uni-app, Vue 3, Vite 5, Pinia, uview-plus, dayjs, WeChat mini program target.
---
## File Structure Map
- Create `miniprogram/package.json`: merged project dependency and script manifest copied from the existing miniapp projects.
- Create `miniprogram/package-lock.json`: copied from the comprehensive project first, then refreshed by `npm install` if needed.
- Create `miniprogram/vite.config.js`: Vite + uni plugin config with two API env variables and two H5 proxy prefixes.
- Create `miniprogram/.env`: production-like default backend URLs for both venues.
- Create `miniprogram/.env.example`: local development URL examples for both venues.
- Create `miniprogram/src/main.js`: Vue app bootstrap with Pinia and uview-plus.
- Create `miniprogram/src/App.vue`: merged global styles and app lifecycle logging.
- Create `miniprogram/src/manifest.json`: WeChat app manifest using `wx516b3e0d9940c788`.
- Create `miniprogram/src/uni.scss`: shared SCSS imports.
- Create `miniprogram/src/styles/variables.scss`: shared design variables and mixins.
- Create `miniprogram/src/pages.json`: merged page registry, no global `tabBar` in first version.
- Create `miniprogram/src/stores/venue.js`: public Pinia store for current venue selection.
- Create `miniprogram/src/utils/venue.js`: venue constants, storage helpers, target home paths, and navigation helpers.
- Create `miniprogram/src/pages/venue-select/index.vue`: first-entry venue selector and switch page.
- Create `miniprogram/src/modules/comprehensive/api/*`: comprehensive backend API modules.
- Create `miniprogram/src/modules/comprehensive/stores/session.js`: comprehensive session store with dedicated store id.
- Create `miniprogram/src/modules/comprehensive/utils/*`: comprehensive business utility modules.
- Create `miniprogram/src/pages/comprehensive/*`: comprehensive venue route pages.
- Create `miniprogram/src/modules/ice/api/*`: ice backend API modules.
- Create `miniprogram/src/components/ice/StatePanel.vue`: ice module state panel component.
- Create `miniprogram/src/pages/ice/*`: ice venue route pages.
- Create `miniprogram/src/static/comprehensive/tab/*`: comprehensive tab image assets for future module navigation.
- Create `miniprogram/src/static/ice/tab/*`: ice tab image assets for future module navigation.
## Source Roots
- Existing comprehensive miniapp: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/综合场馆小程序/miniprogram`
- Existing ice miniapp: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/冰场馆小程序/miniprogram`
- New merged miniapp: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram`
## Implementation Tasks
### Task 1: Scaffold the Merged Miniapp Shell
**Files:**
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/package.json`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/package-lock.json`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/vite.config.js`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/.env`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/.env.example`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/main.js`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/App.vue`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/manifest.json`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/uni.scss`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/styles/variables.scss`
- [ ] **Step 1: Copy baseline project metadata**
Run:
```bash
cp '/Volumes/ExternalStorageA/project/2026.5/体育场馆/综合场馆小程序/miniprogram/package.json' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/package.json'
cp '/Volumes/ExternalStorageA/project/2026.5/体育场馆/综合场馆小程序/miniprogram/package-lock.json' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/package-lock.json'
```
Expected: both files exist in `场馆小程序/miniprogram`.
- [ ] **Step 2: Update merged package metadata**
Edit `miniprogram/package.json` to:
```json
{
"name": "merged-venue-miniapp",
"version": "0.1.0",
"description": "冰场馆与综合场馆合并版微信小程序端",
"type": "module",
"scripts": {
"dev:mp-weixin": "uni -p mp-weixin",
"build:mp-weixin": "uni build -p mp-weixin",
"dev:h5": "uni",
"build:h5": "uni build"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-alpha-5000120260211001",
"@dcloudio/uni-components": "3.0.0-alpha-5000120260211001",
"@dcloudio/uni-h5": "3.0.0-alpha-5000120260211001",
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-5000120260211001",
"dayjs": "^1.11.21",
"pinia": "^2.3.1",
"uview-plus": "^3.8.37",
"vue": "^3.4.0"
},
"devDependencies": {
"@dcloudio/uni-automator": "3.0.0-alpha-5000120260211001",
"@dcloudio/uni-cli-shared": "3.0.0-alpha-5000120260211001",
"@dcloudio/uni-stacktracey": "3.0.0-alpha-5000120260211001",
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-5000120260211001",
"sass": "^1.83.0",
"vite": "^5.2.0"
}
}
```
Expected: package name and description identify the merged app; dependencies remain identical to the two existing miniapps.
- [ ] **Step 3: Create environment files**
Create `miniprogram/.env`:
```dotenv
VITE_ICE_API_BASE_URL=https://venue.test.chatgqt.top
VITE_COMPREHENSIVE_API_BASE_URL=https://venue2.test.chatgqt.top
```
Create `miniprogram/.env.example`:
```dotenv
VITE_ICE_API_BASE_URL=http://127.0.0.1:8000
VITE_COMPREHENSIVE_API_BASE_URL=http://127.0.0.1:8001
```
Expected: no old single `VITE_API_BASE_URL` remains in these files.
- [ ] **Step 4: Create Vite config**
Create `miniprogram/vite.config.js`:
```js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import uniPlugin from '@dcloudio/vite-plugin-uni'
const uni = typeof uniPlugin === 'function' ? uniPlugin : uniPlugin.default
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
const iceApiBase = env.VITE_ICE_API_BASE_URL || 'http://127.0.0.1:8000'
const comprehensiveApiBase = env.VITE_COMPREHENSIVE_API_BASE_URL || 'http://127.0.0.1:8001'
return {
plugins: [uni()],
optimizeDeps: {
include: ['uview-plus'],
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler',
},
},
},
server: {
proxy: {
'/ice-api': {
target: iceApiBase,
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/ice-api/, ''),
},
'/comprehensive-api': {
target: comprehensiveApiBase,
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/comprehensive-api/, ''),
},
},
},
}
})
```
Expected: H5 development can proxy both backends; WeChat builds still use env URLs directly.
- [ ] **Step 5: Create app bootstrap**
Create `miniprogram/src/main.js`:
```js
import { createSSRApp } from 'vue'
import App from './App.vue'
import * as Pinia from 'pinia'
import uviewPlus from 'uview-plus'
import 'uview-plus/index.scss'
export function createApp() {
const app = createSSRApp(App)
app.use(Pinia.createPinia())
app.use(uviewPlus)
return {
app,
Pinia
}
}
```
Expected: Pinia and uview-plus are globally registered.
- [ ] **Step 6: Create manifest**
Create `miniprogram/src/manifest.json`:
```json
{
"name": "场馆服务",
"appid": "",
"description": "冰场馆与综合场馆合并版会员服务小程序",
"versionName": "0.1.0",
"versionCode": "100",
"transformPx": false,
"mp-weixin": {
"appid": "wx516b3e0d9940c788",
"setting": {
"urlCheck": false,
"es6": true,
"postcss": true,
"minified": true
},
"usingComponents": true
},
"vueVersion": "3"
}
```
Expected: WeChat appid is `wx516b3e0d9940c788`.
- [ ] **Step 7: Create global SCSS files**
Create `miniprogram/src/uni.scss`:
```scss
@use "@/styles/variables.scss" as *;
@import "uview-plus/theme.scss";
```
Create `miniprogram/src/styles/variables.scss`:
```scss
$bg-color-main: #FFFFFF;
$bg-color-soft: #F8FAFC;
$bg-color-hover: #F1F5F9;
$bg-color-panel: #EFF6FF;
$text-color-main: #0F172A;
$text-color-regular: #334155;
$text-color-muted: #64748B;
$text-color-light: #94A3B8;
$color-primary: #0EA5E9;
$color-primary-light: #E0F2FE;
$color-success: #10B981;
$color-success-light: #D1FAE5;
$color-warning: #F59E0B;
$color-warning-light: #FEF3C7;
$color-danger: #EF4444;
$color-danger-light: #FEE2E2;
$border-color: #E2E8F0;
$border-radius-sm: 6rpx;
$border-radius-base: 12rpx;
$border-radius-lg: 20rpx;
$spacing-xs: 8rpx;
$spacing-sm: 16rpx;
$spacing-md: 24rpx;
$spacing-lg: 32rpx;
$spacing-xl: 48rpx;
@mixin minimal-card {
background: $bg-color-main;
border: 1rpx solid $border-color;
border-radius: $border-radius-base;
padding: $spacing-md;
box-shadow: 0 2rpx 8rpx rgba(15, 23, 42, 0.03);
}
@mixin text-ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
```
Expected: SCSS variables used by both old projects are available.
- [ ] **Step 8: Create global App component**
Create `miniprogram/src/App.vue`:
```vue
<script>
export default {
onLaunch() {
console.log('Merged Venue Miniapp Launch')
},
onShow() {
console.log('Merged Venue Miniapp Show')
},
onHide() {
console.log('Merged Venue Miniapp Hide')
}
}
</script>
<style lang="scss">
page {
background-color: $bg-color-soft;
color: $text-color-main;
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, "Segoe UI", Arial, sans-serif;
font-size: 28rpx;
line-height: 1.5;
box-sizing: border-box;
-webkit-text-size-adjust: 100%;
}
view,
scroll-view,
swiper,
button,
input,
textarea,
label,
image {
box-sizing: border-box;
}
button {
margin: 0;
padding: 0;
line-height: 1;
}
button::after {
border: none;
}
.container {
min-height: 100vh;
padding: $spacing-md;
background-color: $bg-color-soft;
}
.page-bottom-safe {
padding-bottom: calc(128rpx + env(safe-area-inset-bottom));
}
.card {
@include minimal-card;
}
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 88rpx;
height: 36rpx;
padding: 0 14rpx;
border-radius: $border-radius-sm;
font-size: 20rpx;
font-weight: 700;
&.primary {
color: $color-primary;
background: $color-primary-light;
}
&.success {
color: $color-success;
background: $color-success-light;
}
&.warning {
color: $color-warning;
background: $color-warning-light;
}
&.danger {
color: $color-danger;
background: $color-danger-light;
}
&.muted {
color: $text-color-muted;
background: $bg-color-hover;
}
}
.primary-button {
height: 88rpx;
border-radius: $border-radius-base;
background: $text-color-main;
color: #FFFFFF;
font-size: 28rpx;
font-weight: 800;
display: flex;
align-items: center;
justify-content: center;
&[disabled],
&.disabled {
background: $text-color-light;
color: #FFFFFF;
}
}
.ghost-button {
height: 72rpx;
border-radius: $border-radius-base;
border: 1rpx solid $border-color;
background: $bg-color-main;
color: $text-color-regular;
font-size: 26rpx;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
}
.empty-state,
.load-more-tip {
padding: $spacing-xl $spacing-md;
text-align: center;
color: $text-color-light;
font-size: 24rpx;
}
.fixed-action-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
display: flex;
align-items: center;
gap: $spacing-md;
padding: $spacing-sm $spacing-md calc($spacing-sm + env(safe-area-inset-bottom));
background: rgba(255, 255, 255, 0.96);
border-top: 1rpx solid $border-color;
}
.price-text {
color: $text-color-main;
font-size: 34rpx;
font-weight: 900;
}
</style>
```
Expected: common global classes used by the comprehensive project remain available, and ice pages still get the shared variables.
- [ ] **Step 9: Commit shell**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序'
git add miniprogram/package.json miniprogram/package-lock.json miniprogram/vite.config.js miniprogram/.env miniprogram/.env.example miniprogram/src/main.js miniprogram/src/App.vue miniprogram/src/manifest.json miniprogram/src/uni.scss miniprogram/src/styles/variables.scss
git commit -m "feat: scaffold merged miniapp shell"
```
Expected: commit succeeds with only shell files staged.
### Task 2: Add Public Venue Selection State and Page
**Files:**
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/utils/venue.js`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/stores/venue.js`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages/venue-select/index.vue`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages.json`
- [ ] **Step 1: Create venue utility module**
Create `miniprogram/src/utils/venue.js`:
```js
export const CURRENT_VENUE_KEY = 'CURRENT_VENUE'
export const VENUE_KEYS = Object.freeze({
ICE: 'ice',
COMPREHENSIVE: 'comprehensive'
})
export const VENUE_OPTIONS = Object.freeze([
{
key: VENUE_KEYS.ICE,
name: '冰场馆',
description: '课程预约、会员卡与滑冰服务',
homePath: '/pages/ice/home/index'
},
{
key: VENUE_KEYS.COMPREHENSIVE,
name: '综合场馆',
description: '课程、包场、门票、订单与余额',
homePath: '/pages/comprehensive/home/index'
}
])
export const isVenueKey = (value) => {
return value === VENUE_KEYS.ICE || value === VENUE_KEYS.COMPREHENSIVE
}
export const getVenueOption = (venueKey) => {
return VENUE_OPTIONS.find((item) => item.key === venueKey) || null
}
export const getStoredVenue = () => {
const venue = uni.getStorageSync(CURRENT_VENUE_KEY)
return isVenueKey(venue) ? venue : ''
}
export const setStoredVenue = (venueKey) => {
if (!isVenueKey(venueKey)) {
uni.removeStorageSync(CURRENT_VENUE_KEY)
return ''
}
uni.setStorageSync(CURRENT_VENUE_KEY, venueKey)
return venueKey
}
export const navigateToVenueHome = (venueKey, options = {}) => {
const venue = getVenueOption(venueKey)
if (!venue) {
uni.reLaunch({ url: '/pages/venue-select/index' })
return
}
const navigate = options.redirect === true ? uni.redirectTo : uni.reLaunch
navigate({ url: venue.homePath })
}
```
Expected: utility has a single source of truth for venue keys and home paths.
- [ ] **Step 2: Create venue Pinia store**
Create `miniprogram/src/stores/venue.js`:
```js
import { defineStore } from 'pinia'
import { getStoredVenue, getVenueOption, navigateToVenueHome, setStoredVenue } from '@/utils/venue'
export const useVenueStore = defineStore('currentVenue', {
state: () => ({
currentVenue: ''
}),
getters: {
currentVenueOption: (state) => getVenueOption(state.currentVenue),
hasSelectedVenue: (state) => Boolean(getVenueOption(state.currentVenue))
},
actions: {
init() {
this.currentVenue = getStoredVenue()
return this.currentVenue
},
selectVenue(venueKey) {
this.currentVenue = setStoredVenue(venueKey)
return this.currentVenue
},
enterVenue(venueKey) {
const selectedVenue = this.selectVenue(venueKey)
navigateToVenueHome(selectedVenue)
}
}
})
```
Expected: selection persists and can navigate to module home.
- [ ] **Step 3: Create venue selection page**
Create `miniprogram/src/pages/venue-select/index.vue`:
```vue
<template>
<view class="venue-select-page">
<view class="header">
<text class="title">选择场馆</text>
<text class="subtitle">进入对应场馆后会员预约订单和支付数据彼此独立</text>
</view>
<view class="venue-list">
<view
v-for="venue in venues"
:key="venue.key"
class="venue-card"
:class="{ active: venue.key === currentVenue }"
@tap="selectVenue(venue.key)"
>
<view class="venue-main">
<text class="venue-name">{{ venue.name }}</text>
<text class="venue-desc">{{ venue.description }}</text>
</view>
<view class="venue-action">
<text>{{ venue.key === currentVenue ? '当前' : '进入' }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { useVenueStore } from '@/stores/venue'
import { VENUE_OPTIONS } from '@/utils/venue'
const venueStore = useVenueStore()
const venues = VENUE_OPTIONS
const currentVenue = computed(() => venueStore.currentVenue)
onMounted(() => {
venueStore.init()
})
const selectVenue = (venueKey) => {
venueStore.enterVenue(venueKey)
}
</script>
<style lang="scss" scoped>
.venue-select-page {
min-height: 100vh;
padding: 96rpx 32rpx 48rpx;
background: $bg-color-soft;
}
.header {
display: flex;
flex-direction: column;
gap: 18rpx;
margin-bottom: 56rpx;
}
.title {
color: $text-color-main;
font-size: 48rpx;
font-weight: 900;
}
.subtitle {
color: $text-color-muted;
font-size: 26rpx;
line-height: 1.6;
}
.venue-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.venue-card {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24rpx;
padding: 32rpx;
border: 1rpx solid $border-color;
border-radius: $border-radius-lg;
background: $bg-color-main;
}
.venue-card.active {
border-color: $color-primary;
background: $color-primary-light;
}
.venue-main {
display: flex;
flex: 1;
min-width: 0;
flex-direction: column;
gap: 12rpx;
}
.venue-name {
color: $text-color-main;
font-size: 34rpx;
font-weight: 900;
}
.venue-desc {
color: $text-color-muted;
font-size: 24rpx;
line-height: 1.5;
}
.venue-action {
display: flex;
align-items: center;
justify-content: center;
min-width: 96rpx;
height: 56rpx;
border-radius: $border-radius-sm;
background: $text-color-main;
color: #FFFFFF;
font-size: 24rpx;
font-weight: 800;
}
.venue-card.active .venue-action {
background: $color-primary;
}
</style>
```
Expected: first page gives two concrete choices and persists selection before navigation.
- [ ] **Step 4: Create initial pages registry**
Create `miniprogram/src/pages.json`:
```json
{
"easycom": {
"autoscan": true,
"custom": {
"^u--(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^up-(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue"
}
},
"pages": [
{
"path": "pages/venue-select/index",
"style": {
"navigationBarTitleText": "选择场馆",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationBarTextStyle": "black"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8FAFC",
"backgroundColorBottom": "#F8FAFC",
"backgroundColorTop": "#FFFFFF"
}
}
```
Expected: app has a valid first page before business modules are migrated.
- [ ] **Step 5: Verify selection shell builds**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
npm install
npm run build:mp-weixin
```
Expected: build succeeds or fails only because `/pages/ice/home/index` and `/pages/comprehensive/home/index` do not exist yet after tapping; compile-time should not require those target files until pages are registered. If compile fails for missing dependencies, fix dependency installation before continuing.
- [ ] **Step 6: Commit selection shell**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序'
git add miniprogram/src/utils/venue.js miniprogram/src/stores/venue.js miniprogram/src/pages/venue-select/index.vue miniprogram/src/pages.json miniprogram/package-lock.json
git commit -m "feat: add venue selection shell"
```
Expected: commit succeeds with public shell files and any refreshed lockfile.
### Task 3: Migrate Comprehensive Venue Module
**Files:**
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/modules/comprehensive/api/*.js`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/modules/comprehensive/stores/session.js`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/modules/comprehensive/utils/*.js`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages/comprehensive/**`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/static/comprehensive/tab/*.png`
- Modify: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages.json`
- [ ] **Step 1: Copy comprehensive source files into namespaced locations**
Run:
```bash
mkdir -p '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/modules/comprehensive'
mkdir -p '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages/comprehensive'
mkdir -p '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/static/comprehensive'
cp -R '/Volumes/ExternalStorageA/project/2026.5/体育场馆/综合场馆小程序/miniprogram/src/api' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/modules/comprehensive/api'
cp -R '/Volumes/ExternalStorageA/project/2026.5/体育场馆/综合场馆小程序/miniprogram/src/stores' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/modules/comprehensive/stores'
cp -R '/Volumes/ExternalStorageA/project/2026.5/体育场馆/综合场馆小程序/miniprogram/src/utils' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/modules/comprehensive/utils'
cp -R '/Volumes/ExternalStorageA/project/2026.5/体育场馆/综合场馆小程序/miniprogram/src/pages/.' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages/comprehensive/'
cp -R '/Volumes/ExternalStorageA/project/2026.5/体育场馆/综合场馆小程序/miniprogram/src/static/tab' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/static/comprehensive/tab'
```
Expected: comprehensive pages exist under `src/pages/comprehensive`, and API/store/utils exist under `src/modules/comprehensive`.
- [ ] **Step 2: Update comprehensive request base URL**
Edit `miniprogram/src/modules/comprehensive/api/request.js` so the top constants are:
```js
const KEY_TOKEN = 'COMPREHENSIVE_VENUE_TOKEN'
const API_BASE_URL = import.meta.env.VITE_COMPREHENSIVE_API_BASE_URL || 'http://127.0.0.1:8001'
```
Expected: `VITE_API_BASE_URL` is not used in this module.
- [ ] **Step 3: Rename comprehensive session store id and imports**
Edit `miniprogram/src/modules/comprehensive/stores/session.js`:
```js
import { defineStore } from 'pinia'
import { authApi, authStorage } from '@/modules/comprehensive/api/auth'
import { memberApi } from '@/modules/comprehensive/api/member'
export const useComprehensiveSessionStore = defineStore('comprehensiveVenueSession', {
state: () => ({
isReady: false,
isLoggedIn: false,
member: null
}),
actions: {
async init() {
this.isLoggedIn = authStorage.isLoggedIn()
if (this.isLoggedIn) {
try {
this.member = await memberApi.getMemberMe()
} catch (error) {
if ([40001, 40002, 401, 403].includes(error.code) || [401, 403].includes(error.statusCode)) {
authStorage.clear()
this.member = null
this.isLoggedIn = false
}
}
}
this.isReady = true
},
async login() {
const loginResult = await loginByWechat()
const data = await authApi.wechatLogin({ code: loginResult.code })
this.member = data.member
this.isLoggedIn = true
},
logout() {
authApi.logout()
this.member = null
this.isLoggedIn = false
},
async refreshMember() {
if (!authStorage.isLoggedIn()) return
this.member = await memberApi.getMemberMe()
this.isLoggedIn = true
}
}
})
const loginByWechat = () => new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: (res) => {
if (!res.code) {
reject(new Error('微信登录未返回 code'))
return
}
resolve(res)
},
fail: reject
})
})
```
Expected: no `@/api` imports and no `defineStore('session')` remain in this file.
- [ ] **Step 4: Rewrite comprehensive page imports**
Run these replacements from `miniprogram`:
```bash
perl -pi -e "s#@/api/#@/modules/comprehensive/api/#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#@/stores/session#@/modules/comprehensive/stores/session#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#@/utils/#@/modules/comprehensive/utils/#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#useSessionStore#useComprehensiveSessionStore#g" src/pages/comprehensive/**/*.vue
```
Expected: comprehensive pages import from `@/modules/comprehensive/*`.
- [ ] **Step 5: Rewrite comprehensive route strings**
Run these replacements from `miniprogram`:
```bash
perl -pi -e "s#/pages/home#/pages/comprehensive/home#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#/pages/courses#/pages/comprehensive/courses#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#/pages/bookings#/pages/comprehensive/bookings#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#/pages/profile#/pages/comprehensive/profile#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#/pages/activities#/pages/comprehensive/activities#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#/pages/private#/pages/comprehensive/private#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#/pages/tickets#/pages/comprehensive/tickets#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#/pages/orders#/pages/comprehensive/orders#g" src/pages/comprehensive/**/*.vue
perl -pi -e "s#/pages/pay#/pages/comprehensive/pay#g" src/pages/comprehensive/**/*.vue
```
Expected: comprehensive pages no longer navigate to unprefixed old routes.
- [ ] **Step 6: Add comprehensive switch entry to profile menu**
Edit `miniprogram/src/pages/comprehensive/profile/index.vue`.
Add one item to `menuItems` after the personal info item:
```js
{ title: '切换场馆', url: '/pages/venue-select/index', theme: 'profile-theme', icon: 'profile' }
```
Expected: logged-in comprehensive users can navigate to the venue selection page. If the page shows no menu when logged out, the venue selector remains available by manually opening the first page; Task 7 will also validate direct switching.
- [ ] **Step 7: Register comprehensive pages**
Modify `miniprogram/src/pages.json` by appending these page entries after `pages/venue-select/index`:
```json
{
"path": "pages/comprehensive/home/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true
}
},
{
"path": "pages/comprehensive/courses/index",
"style": {
"navigationBarTitleText": "课程",
"enablePullDownRefresh": true
}
},
{
"path": "pages/comprehensive/bookings/index",
"style": {
"navigationBarTitleText": "我的预约",
"enablePullDownRefresh": true
}
},
{
"path": "pages/comprehensive/profile/index",
"style": {
"navigationBarTitleText": "会员中心"
}
},
{
"path": "pages/comprehensive/activities/index",
"style": {
"navigationBarTitleText": "活动公告",
"enablePullDownRefresh": true
}
},
{
"path": "pages/comprehensive/activities/detail",
"style": {
"navigationBarTitleText": "活动详情"
}
},
{
"path": "pages/comprehensive/courses/detail",
"style": {
"navigationBarTitleText": "课程详情"
}
},
{
"path": "pages/comprehensive/courses/confirm",
"style": {
"navigationBarTitleText": "确认课程预约"
}
},
{
"path": "pages/comprehensive/private/index",
"style": {
"navigationBarTitleText": "包场预约"
}
},
{
"path": "pages/comprehensive/private/confirm",
"style": {
"navigationBarTitleText": "确认包场"
}
},
{
"path": "pages/comprehensive/tickets/index",
"style": {
"navigationBarTitleText": "篮球门票"
}
},
{
"path": "pages/comprehensive/pay/index",
"style": {
"navigationBarTitleText": "订单支付"
}
},
{
"path": "pages/comprehensive/orders/detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "pages/comprehensive/bookings/course-detail",
"style": {
"navigationBarTitleText": "课程预约详情"
}
},
{
"path": "pages/comprehensive/bookings/private-detail",
"style": {
"navigationBarTitleText": "包场预约详情"
}
},
{
"path": "pages/comprehensive/profile/balance",
"style": {
"navigationBarTitleText": "余额流水",
"enablePullDownRefresh": true
}
},
{
"path": "pages/comprehensive/profile/tickets",
"style": {
"navigationBarTitleText": "我的门票",
"enablePullDownRefresh": true
}
},
{
"path": "pages/comprehensive/profile/orders",
"style": {
"navigationBarTitleText": "我的订单",
"enablePullDownRefresh": true
}
},
{
"path": "pages/comprehensive/profile/detail",
"style": {
"navigationBarTitleText": "个人信息"
}
}
```
Expected: all comprehensive pages from the old `pages.json` are registered with the `pages/comprehensive` prefix.
- [ ] **Step 8: Verify comprehensive module references**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
rg -n "@/api|@/stores/session|@/utils/" src/pages/comprehensive src/modules/comprehensive
rg -n "'/pages/(home|courses|bookings|profile|activities|private|tickets|orders|pay)|\"/pages/(home|courses|bookings|profile|activities|private|tickets|orders|pay)" src/pages/comprehensive
rg -n "VITE_API_BASE_URL" src/modules/comprehensive
```
Expected: all three commands return no matches.
- [ ] **Step 9: Build after comprehensive migration**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
npm run build:mp-weixin
```
Expected: build succeeds, or fails only because ice pages have not been registered yet. If the failure references comprehensive imports, fix those references before continuing.
- [ ] **Step 10: Commit comprehensive module**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序'
git add miniprogram/src/modules/comprehensive miniprogram/src/pages/comprehensive miniprogram/src/static/comprehensive miniprogram/src/pages.json
git commit -m "feat: migrate comprehensive venue module"
```
Expected: commit succeeds with comprehensive module files.
### Task 4: Migrate Ice Venue Module
**Files:**
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/modules/ice/api/*.js`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/components/ice/StatePanel.vue`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages/ice/**`
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/static/ice/tab/*.png`
- Modify: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages.json`
- [ ] **Step 1: Copy ice source files into namespaced locations**
Run:
```bash
mkdir -p '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/modules/ice'
mkdir -p '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages/ice'
mkdir -p '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/components/ice'
mkdir -p '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/static/ice'
cp -R '/Volumes/ExternalStorageA/project/2026.5/体育场馆/冰场馆小程序/miniprogram/src/api' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/modules/ice/api'
cp '/Volumes/ExternalStorageA/project/2026.5/体育场馆/冰场馆小程序/miniprogram/src/components/StatePanel.vue' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/components/ice/StatePanel.vue'
cp -R '/Volumes/ExternalStorageA/project/2026.5/体育场馆/冰场馆小程序/miniprogram/src/pages/.' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages/ice/'
cp -R '/Volumes/ExternalStorageA/project/2026.5/体育场馆/冰场馆小程序/miniprogram/src/static/tab' '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/static/ice/tab'
```
Expected: ice pages exist under `src/pages/ice`, API under `src/modules/ice/api`, and state panel under `src/components/ice`.
- [ ] **Step 2: Update ice request base URL**
Edit `miniprogram/src/modules/ice/api/request.js` so the base URL constant is:
```js
const API_BASE_URL = import.meta.env.VITE_ICE_API_BASE_URL || 'http://127.0.0.1:8000'
```
Expected: `VITE_API_BASE_URL` is not used in this module.
- [ ] **Step 3: Rewrite ice page imports**
Run these replacements from `miniprogram`:
```bash
perl -pi -e "s#@/api/#@/modules/ice/api/#g" src/pages/ice/**/*.vue
perl -pi -e "s#@/components/StatePanel.vue#@/components/ice/StatePanel.vue#g" src/pages/ice/**/*.vue
```
Expected: ice pages import from `@/modules/ice/api/*` and `@/components/ice/StatePanel.vue`.
- [ ] **Step 4: Rewrite ice route strings**
Run these replacements from `miniprogram`:
```bash
perl -pi -e "s#/pages/home#/pages/ice/home#g" src/pages/ice/**/*.vue
perl -pi -e "s#/pages/courses#/pages/ice/courses#g" src/pages/ice/**/*.vue
perl -pi -e "s#/pages/bookings#/pages/ice/bookings#g" src/pages/ice/**/*.vue
perl -pi -e "s#/pages/profile#/pages/ice/profile#g" src/pages/ice/**/*.vue
perl -pi -e "s#/pages/activities#/pages/ice/activities#g" src/pages/ice/**/*.vue
```
Expected: ice pages no longer navigate to unprefixed old routes.
- [ ] **Step 5: Add ice switch entry to profile page**
Edit `miniprogram/src/pages/ice/profile/index.vue`.
Add a menu item in the logged-in menu group before the logout item:
```vue
<view class="menu-item" @tap="navigateTo('/pages/venue-select/index')">
<view class="item-left">
<view class="item-icon-container profile-theme">
<view class="svg-profile-minimal">
<view class="svg-profile-head" />
<view class="svg-profile-body" />
</view>
</view>
<text class="item-title">切换场馆</text>
</view>
<view class="item-right">
<view class="menu-arrow" />
</view>
</view>
```
If the page does not define `.profile-theme` or `.svg-profile-minimal`, use the existing `card-theme` icon container classes instead:
```vue
<view class="item-icon-container card-theme">
<view class="svg-card-minimal">
<view class="svg-card-chip" />
</view>
</view>
```
Expected: logged-in ice users can navigate to the venue selection page.
- [ ] **Step 6: Register ice pages**
Modify `miniprogram/src/pages.json` by appending these page entries after the comprehensive entries:
```json
{
"path": "pages/ice/home/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true
}
},
{
"path": "pages/ice/courses/index",
"style": {
"navigationBarTitleText": "近期课程",
"enablePullDownRefresh": true
}
},
{
"path": "pages/ice/bookings/index",
"style": {
"navigationBarTitleText": "我的预约",
"enablePullDownRefresh": true
}
},
{
"path": "pages/ice/profile/index",
"style": {
"navigationBarTitleText": "会员中心"
}
},
{
"path": "pages/ice/bookings/detail",
"style": {
"navigationBarTitleText": "预约详情"
}
},
{
"path": "pages/ice/courses/detail",
"style": {
"navigationBarTitleText": "课程详情"
}
},
{
"path": "pages/ice/profile/cards",
"style": {
"navigationBarTitleText": "我的会员卡",
"enablePullDownRefresh": true
}
},
{
"path": "pages/ice/profile/card-history",
"style": {
"navigationBarTitleText": "会员卡使用流水",
"enablePullDownRefresh": true
}
},
{
"path": "pages/ice/profile/detail",
"style": {
"navigationBarTitleText": "个人信息"
}
},
{
"path": "pages/ice/activities/detail",
"style": {
"navigationBarTitleText": "活动详情"
}
}
```
Expected: all ice pages from the old `pages.json` are registered with the `pages/ice` prefix.
- [ ] **Step 7: Verify ice module references**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
rg -n "@/api|@/components/StatePanel.vue" src/pages/ice src/modules/ice
rg -n "'/pages/(home|courses|bookings|profile|activities)|\"/pages/(home|courses|bookings|profile|activities)" src/pages/ice
rg -n "VITE_API_BASE_URL" src/modules/ice
```
Expected: all three commands return no matches.
- [ ] **Step 8: Build after ice migration**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
npm run build:mp-weixin
```
Expected: build succeeds. If build fails, fix the reported import, route registration, or SCSS variable error before continuing.
- [ ] **Step 9: Commit ice module**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序'
git add miniprogram/src/modules/ice miniprogram/src/pages/ice miniprogram/src/components/ice miniprogram/src/static/ice miniprogram/src/pages.json
git commit -m "feat: migrate ice venue module"
```
Expected: commit succeeds with ice module files.
### Task 5: Finalize Cross-Module Navigation and Safety Checks
**Files:**
- Modify: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/pages/venue-select/index.vue`
- Modify: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/src/utils/venue.js`
- Modify: module pages only if checks reveal stale routes or stale imports.
- [ ] **Step 1: Ensure venue selector can return to stored current venue**
Update `miniprogram/src/pages/venue-select/index.vue` to keep the existing card UI and add a continue action when a venue is already selected:
```vue
<template>
<view class="venue-select-page">
<view class="header">
<text class="title">选择场馆</text>
<text class="subtitle">进入对应场馆后会员预约订单和支付数据彼此独立</text>
</view>
<view v-if="selectedVenue" class="current-card">
<text class="current-label">当前场馆</text>
<text class="current-name">{{ selectedVenue.name }}</text>
<button class="primary-button continue-btn" @tap="continueCurrentVenue">继续进入</button>
</view>
<view class="venue-list">
<view
v-for="venue in venues"
:key="venue.key"
class="venue-card"
:class="{ active: venue.key === currentVenue }"
@tap="selectVenue(venue.key)"
>
<view class="venue-main">
<text class="venue-name">{{ venue.name }}</text>
<text class="venue-desc">{{ venue.description }}</text>
</view>
<view class="venue-action">
<text>{{ venue.key === currentVenue ? '切换进入' : '进入' }}</text>
</view>
</view>
</view>
</view>
</template>
```
Add script code:
```js
const selectedVenue = computed(() => venueStore.currentVenueOption)
const continueCurrentVenue = () => {
if (!venueStore.currentVenue) return
venueStore.enterVenue(venueStore.currentVenue)
}
```
Add styles:
```scss
.current-card {
display: flex;
flex-direction: column;
gap: 12rpx;
margin-bottom: 32rpx;
padding: 28rpx;
border-radius: $border-radius-lg;
background: $bg-color-main;
border: 1rpx solid $border-color;
}
.current-label {
color: $text-color-muted;
font-size: 22rpx;
font-weight: 700;
}
.current-name {
color: $text-color-main;
font-size: 32rpx;
font-weight: 900;
}
.continue-btn {
margin-top: 12rpx;
}
```
Expected: users who return to the selector can continue into the stored venue without changing it.
- [ ] **Step 2: Run stale import checks**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
rg -n "@/api|@/stores/session|@/utils/(date|money|status)|@/components/StatePanel.vue" src/pages src/modules
```
Expected: no matches. If matches appear, rewrite them to module-specific imports:
```text
@/modules/ice/api/<file>
@/modules/comprehensive/api/<file>
@/modules/comprehensive/utils/<file>
@/components/ice/StatePanel.vue
```
- [ ] **Step 3: Run stale route checks**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
rg -n "'/pages/(home|courses|bookings|profile|activities|private|tickets|orders|pay)|\"/pages/(home|courses|bookings|profile|activities|private|tickets|orders|pay)|`/pages/(home|courses|bookings|profile|activities|private|tickets|orders|pay)" src/pages
```
Expected: no matches. If matches appear, prefix with `ice` or `comprehensive` based on the file path.
- [ ] **Step 4: Run API base URL checks**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
rg -n "VITE_API_BASE_URL|const API_BASE_URL" src/modules
```
Expected output includes only:
```text
src/modules/ice/api/request.js:const API_BASE_URL = import.meta.env.VITE_ICE_API_BASE_URL || 'http://127.0.0.1:8000'
src/modules/comprehensive/api/request.js:const API_BASE_URL = import.meta.env.VITE_COMPREHENSIVE_API_BASE_URL || 'http://127.0.0.1:8001'
```
- [ ] **Step 5: Run token isolation checks**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
rg -n "ICE_MEMBER_TOKEN|ICE_MEMBER_TOKEN_EXPIRES_AT|COMPREHENSIVE_VENUE_TOKEN" src/modules
```
Expected output includes:
```text
src/modules/ice/api/request.js:const KEY_TOKEN = 'ICE_MEMBER_TOKEN'
src/modules/ice/api/request.js:const KEY_TOKEN_EXPIRES_AT = 'ICE_MEMBER_TOKEN_EXPIRES_AT'
src/modules/comprehensive/api/request.js:const KEY_TOKEN = 'COMPREHENSIVE_VENUE_TOKEN'
```
- [ ] **Step 6: Build final merged app**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
npm run build:mp-weixin
```
Expected: build succeeds and outputs `dist/build/mp-weixin`.
- [ ] **Step 7: Commit safety pass**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序'
git add miniprogram
git commit -m "fix: finalize merged venue navigation"
```
Expected: commit succeeds if files changed. If no files changed after checks, skip this commit and record that no safety-pass commit was needed.
### Task 6: Manual WeChat Mini Program Smoke Test
**Files:**
- No source files should be modified unless the smoke test finds a defect.
- [ ] **Step 1: Open generated mini program in WeChat DevTools**
Open this directory in WeChat DevTools:
```text
/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram/dist/build/mp-weixin
```
Expected: project opens with appid `wx516b3e0d9940c788`.
- [ ] **Step 2: Smoke test venue selector**
In the simulator:
```text
1. Open first page.
2. Confirm "选择场馆" appears.
3. Tap "冰场馆".
4. Confirm URL is pages/ice/home/index.
5. Navigate back to pages/venue-select/index.
6. Tap "综合场馆".
7. Confirm URL is pages/comprehensive/home/index.
```
Expected: both venue cards navigate to the correct module home page.
- [ ] **Step 3: Smoke test ice module navigation**
Open these paths in WeChat DevTools page path input:
```text
pages/ice/home/index
pages/ice/courses/index
pages/ice/bookings/index
pages/ice/profile/index
pages/ice/profile/cards
```
Expected: each page loads without a JavaScript import error. Network errors from unavailable backend are acceptable during local smoke testing if request URLs point to `https://venue.test.chatgqt.top`.
- [ ] **Step 4: Smoke test comprehensive module navigation**
Open these paths in WeChat DevTools page path input:
```text
pages/comprehensive/home/index
pages/comprehensive/courses/index
pages/comprehensive/bookings/index
pages/comprehensive/profile/index
pages/comprehensive/private/index
pages/comprehensive/tickets/index
pages/comprehensive/pay/index?order_id=1
```
Expected: each page loads without a JavaScript import error. The pay page may show an order loading error for `order_id=1`; it must not call the ice backend.
- [ ] **Step 5: Inspect request domains**
Use WeChat DevTools network panel:
```text
1. Open pages/ice/home/index.
2. Confirm requests go to https://venue.test.chatgqt.top.
3. Open pages/comprehensive/home/index.
4. Confirm requests go to https://venue2.test.chatgqt.top.
```
Expected: requests never cross backend domains.
- [ ] **Step 6: Commit smoke-test fixes**
If source fixes were required, run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序'
git add miniprogram
git commit -m "fix: address merged miniapp smoke test issues"
```
Expected: commit succeeds only if smoke testing found and fixed defects.
### Task 7: Final Verification and Delivery Notes
**Files:**
- Create: `/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/docs/merged-miniapp-verification.md`
- [ ] **Step 1: Run final command verification**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
npm run build:mp-weixin
rg -n "@/api|@/stores/session|@/utils/(date|money|status)|@/components/StatePanel.vue" src/pages src/modules
rg -n "'/pages/(home|courses|bookings|profile|activities|private|tickets|orders|pay)|\"/pages/(home|courses|bookings|profile|activities|private|tickets|orders|pay)|`/pages/(home|courses|bookings|profile|activities|private|tickets|orders|pay)" src/pages
rg -n "VITE_API_BASE_URL" src/modules .env .env.example vite.config.js
```
Expected:
```text
npm run build:mp-weixin exits 0
stale import rg exits 1 with no matches
stale route rg exits 1 with no matches
VITE_API_BASE_URL rg exits 1 with no matches
```
- [ ] **Step 2: Write verification note**
Create `docs/merged-miniapp-verification.md`:
```markdown
# 合并版场馆小程序验证记录
## 构建
- 命令:`npm run build:mp-weixin`
- 结果:通过
- 产物:`miniprogram/dist/build/mp-weixin`
## 路由隔离
- 检查旧的无前缀页面路径:通过
- 冰场馆页面前缀:`pages/ice`
- 综合场馆页面前缀:`pages/comprehensive`
## API 隔离
- 冰场馆 baseURL`VITE_ICE_API_BASE_URL`
- 综合场馆 baseURL`VITE_COMPREHENSIVE_API_BASE_URL`
-`VITE_API_BASE_URL`:未使用
## 登录态隔离
- 冰场馆 token`ICE_MEMBER_TOKEN`
- 冰场馆 token 过期时间:`ICE_MEMBER_TOKEN_EXPIRES_AT`
- 综合场馆 token`COMPREHENSIVE_VENUE_TOKEN`
## 上线前人工检查
- 微信 request 合法域名包含 `venue.test.chatgqt.top`
- 微信 request 合法域名包含 `venue2.test.chatgqt.top`
- 综合场馆支付仍由综合场馆后端生成支付参数
```
If any command fails, replace `结果:通过` with the exact failing command and actual status, then fix the issue before final delivery.
- [ ] **Step 3: Commit verification note**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序'
git add docs/merged-miniapp-verification.md
git commit -m "docs: record merged miniapp verification"
```
Expected: commit succeeds with the verification record.
- [ ] **Step 4: Final git status check**
Run:
```bash
cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序'
git status --short
```
Expected: no uncommitted files except intentionally ignored build output if `.gitignore` is added later. If build output appears and should not be committed, add a project `.gitignore` that ignores:
```gitignore
miniprogram/node_modules/
miniprogram/dist/
.DS_Store
```
Commit `.gitignore` with:
```bash
git add .gitignore
git commit -m "chore: ignore generated miniapp files"
```
Expected: working tree is clean or only contains intentionally untracked files reported to the user.
## Plan Self-Review
Spec coverage:
- New project under `场馆小程序/miniprogram`: Task 1.
- First-entry venue choice and in-app switching: Tasks 2, 5, and 6.
- Isolated Django backends and API clients: Tasks 1, 3, 4, and 5.
- Token isolation: Tasks 3, 4, 5, and 7.
- Route prefixing: Tasks 3, 4, and 5.
- No backend/admin/CI merge: all tasks operate only under `场馆小程序`.
- Build and smoke verification: Tasks 5, 6, and 7.
Placeholder scan: the plan intentionally avoids unspecified implementation slots. Each migration step includes exact copy commands, rewrite commands, file paths, expected checks, and commit commands.
Type consistency:
- Venue key values are `ice` and `comprehensive` across `VENUE_KEYS`, storage, routes, and page prefixes.
- Store exports are `useVenueStore` and `useComprehensiveSessionStore`.
- Environment variables are `VITE_ICE_API_BASE_URL` and `VITE_COMPREHENSIVE_API_BASE_URL`.
- Token keys are `ICE_MEMBER_TOKEN`, `ICE_MEMBER_TOKEN_EXPIRES_AT`, and `COMPREHENSIVE_VENUE_TOKEN`.