# 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
```
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
{{ venue.name }}
{{ venue.description }}
{{ venue.key === currentVenue ? '当前' : '进入' }}
```
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
```
If the page does not define `.profile-theme` or `.svg-profile-minimal`, use the existing `card-theme` icon container classes instead:
```vue
```
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
当前场馆
{{ selectedVenue.name }}
{{ venue.name }}
{{ venue.description }}
{{ venue.key === currentVenue ? '切换进入' : '进入' }}
```
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/
@/modules/comprehensive/api/
@/modules/comprehensive/utils/
@/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`.