From b4cfb109ebfc464832f293a010f82d2f7a9f0475 Mon Sep 17 00:00:00 2001 From: gqt <3217233537@qq.com> Date: Mon, 8 Jun 2026 20:22:46 +0800 Subject: [PATCH] docs: add merged venue miniapp plan --- .../plans/2026-06-08-merged-venue-miniapp.md | 1683 +++++++++++++++++ 1 file changed, 1683 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-08-merged-venue-miniapp.md diff --git a/docs/superpowers/plans/2026-06-08-merged-venue-miniapp.md b/docs/superpowers/plans/2026-06-08-merged-venue-miniapp.md new file mode 100644 index 0000000..33850ac --- /dev/null +++ b/docs/superpowers/plans/2026-06-08-merged-venue-miniapp.md @@ -0,0 +1,1683 @@ +# 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`.