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