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

50 KiB
Raw Blame History

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:

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:

{
  "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:

VITE_ICE_API_BASE_URL=https://venue.test.chatgqt.top
VITE_COMPREHENSIVE_API_BASE_URL=https://venue2.test.chatgqt.top

Create miniprogram/.env.example:

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:

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:

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:

{
  "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:

@use "@/styles/variables.scss" as *;
@import "uview-plus/theme.scss";

Create miniprogram/src/styles/variables.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:

<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:

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:

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:

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:

<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:

{
  "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:

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:

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:

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:

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:

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:

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:

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:

{ 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:

{
  "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:

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:

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:

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:

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:

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:

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:

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:

<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:

<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:

{
  "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:

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:

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:

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:

<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:

const selectedVenue = computed(() => venueStore.currentVenueOption)

const continueCurrentVenue = () => {
  if (!venueStore.currentVenue) return
  venueStore.enterVenue(venueStore.currentVenue)
}

Add styles:

.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:

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:

@/modules/ice/api/<file>
@/modules/comprehensive/api/<file>
@/modules/comprehensive/utils/<file>
@/components/ice/StatePanel.vue
  • Step 3: Run stale route checks

Run:

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:

cd '/Volumes/ExternalStorageA/project/2026.5/体育场馆/场馆小程序/miniprogram'
rg -n "VITE_API_BASE_URL|const API_BASE_URL" src/modules

Expected output includes only:

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:

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:

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:

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:

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:

/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:

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:

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:

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:

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:

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:

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:

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:

# 合并版场馆小程序验证记录

## 构建

- 命令:`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:

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:

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:

miniprogram/node_modules/
miniprogram/dist/
.DS_Store

Commit .gitignore with:

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.