# 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 ``` 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 ``` 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`.