feat: add merged venue navigation

This commit is contained in:
gqt
2026-06-08 21:42:32 +08:00
parent 845385c3b9
commit 4c20371c6d
13 changed files with 245 additions and 8 deletions
+112
View File
@@ -0,0 +1,112 @@
<template>
<view class="venue-tabbar-wrap">
<view class="venue-tabbar-spacer" />
<view class="venue-tabbar">
<view
v-for="item in items"
:key="item.key"
class="venue-tabbar__item"
:class="{ active: item.key === active }"
@tap="go(item)"
>
<image
class="venue-tabbar__icon"
:src="item.key === active ? item.activeIcon : item.icon"
mode="aspectFit"
/>
<text class="venue-tabbar__label">{{ item.label }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue'
import { setStoredVenue } from '@/utils/venue'
const props = defineProps({
venue: {
type: String,
required: true,
validator: (value) => ['ice', 'comprehensive'].includes(value)
},
active: {
type: String,
required: true,
validator: (value) => ['home', 'courses', 'bookings', 'profile'].includes(value)
}
})
const tabDefinitions = [
{ key: 'home', label: '首页', icon: 'home', path: 'home' },
{ key: 'courses', label: '课程', icon: 'course', path: 'courses' },
{ key: 'bookings', label: '预约', icon: 'booking', path: 'bookings' },
{ key: 'profile', label: '我的', icon: 'mine', path: 'profile' }
]
const items = computed(() => {
return tabDefinitions.map((item) => ({
...item,
icon: `/static/${props.venue}/tab/${item.icon}.png`,
activeIcon: `/static/${props.venue}/tab/${item.icon}-active.png`,
url: `/pages/${props.venue}/${item.path}/index`
}))
})
const go = (item) => {
setStoredVenue(props.venue)
if (item.key === props.active) return
uni.reLaunch({ url: item.url })
}
</script>
<style lang="scss" scoped>
.venue-tabbar-spacer {
height: calc(124rpx + env(safe-area-inset-bottom));
}
.venue-tabbar {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
padding: 10rpx 0 calc(10rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid $border-color;
background: rgba(255, 255, 255, 0.98);
box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.06);
}
.venue-tabbar__item {
min-width: 0;
height: 104rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6rpx;
color: $text-color-light;
font-size: 22rpx;
font-weight: 700;
line-height: 1;
}
.venue-tabbar__item.active {
color: $text-color-main;
}
.venue-tabbar__icon {
width: 46rpx;
height: 46rpx;
}
.venue-tabbar__label {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
@@ -31,6 +31,7 @@
<view v-if="isLoading" class="load-more-tip">加载中...</view>
</view>
</block>
<VenueTabBar venue="comprehensive" active="bookings" />
</view>
</template>
@@ -40,6 +41,7 @@ import { bookingApi } from '@/modules/comprehensive/api/booking'
import { ticketApi } from '@/modules/comprehensive/api/ticket'
import { useComprehensiveSessionStore } from '@/modules/comprehensive/stores/session'
import { formatDateTime, formatTimeRange } from '@/modules/comprehensive/utils/date'
import VenueTabBar from '@/components/VenueTabBar.vue'
const session = useComprehensiveSessionStore()
const businessType = ref('course')
@@ -41,6 +41,7 @@
<view v-else-if="courses.length === 0" class="empty-state">当前筛选条件下暂无课程</view>
<view v-else-if="!hasMore" class="load-more-tip">已加载全部</view>
</view>
<VenueTabBar venue="comprehensive" active="courses" />
</view>
</template>
@@ -50,6 +51,7 @@ import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
import { courseApi } from '@/modules/comprehensive/api/course'
import { venueApi } from '@/modules/comprehensive/api/venue'
import { formatTimeRange, todayString } from '@/modules/comprehensive/utils/date'
import VenueTabBar from '@/components/VenueTabBar.vue'
const selectedDate = ref(todayString())
const selectedCoachIndex = ref(0)
@@ -62,6 +62,7 @@
</view>
<view v-else class="empty-placeholder">暂无公告活动</view>
</view>
<VenueTabBar venue="comprehensive" active="home" />
</view>
</template>
@@ -70,6 +71,7 @@ import { ref, onMounted } from 'vue'
import { onPullDownRefresh } from '@dcloudio/uni-app'
import { contentApi } from '@/modules/comprehensive/api/content'
import { formatDate, formatTimeRange } from '@/modules/comprehensive/utils/date'
import VenueTabBar from '@/components/VenueTabBar.vue'
const banners = ref([])
const upcomingCourses = ref([])
@@ -66,12 +66,14 @@
</view>
</view>
</block>
<VenueTabBar venue="comprehensive" active="profile" />
</view>
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { useComprehensiveSessionStore } from '@/modules/comprehensive/stores/session'
import VenueTabBar from '@/components/VenueTabBar.vue'
const session = useComprehensiveSessionStore()
const avatarInitial = computed(() => (session.member?.nickname || '用户').slice(0, 1))
@@ -81,7 +83,7 @@ const menuItems = [
{ title: '篮球门票', url: '/pages/comprehensive/tickets/index', theme: 'ticket-theme', icon: 'ticket' },
{ title: '我的订单', url: '/pages/comprehensive/profile/orders', theme: 'order-theme', icon: 'order' },
{ title: '个人信息', url: '/pages/comprehensive/profile/detail', theme: 'profile-theme', icon: 'profile' },
{ title: '切换场馆', url: '/pages/venue-select/index', theme: 'profile-theme', icon: 'profile' }
{ title: '切换场馆', url: '/pages/venue-select/index?mode=switch', theme: 'profile-theme', icon: 'profile' }
]
onMounted(async () => {
@@ -111,6 +111,7 @@
/>
</view>
</block>
<VenueTabBar venue="ice" active="bookings" />
</view>
</template>
@@ -120,6 +121,7 @@ import { onPullDownRefresh, onReachBottom, onShow } from '@dcloudio/uni-app'
import { bookingApi } from '@/modules/ice/api/booking'
import { authStorage } from '@/modules/ice/api/member'
import { errorMessage, getMemberSession, loginAndStoreToken, sessionStatusFromError, SESSION_STATUS } from '@/modules/ice/api/session'
import VenueTabBar from '@/components/VenueTabBar.vue'
import StatePanel from '@/components/ice/StatePanel.vue'
const isLoggedIn = ref(false)
@@ -90,6 +90,7 @@
compact
/>
</view>
<VenueTabBar venue="ice" active="courses" />
</view>
</template>
@@ -98,6 +99,7 @@ import { ref, computed, onMounted } from 'vue'
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
import { courseApi } from '@/modules/ice/api/course'
import { errorMessage } from '@/modules/ice/api/session'
import VenueTabBar from '@/components/VenueTabBar.vue'
import StatePanel from '@/components/ice/StatePanel.vue'
const selectedDate = ref('')
+2
View File
@@ -84,6 +84,7 @@
</view>
</view>
</block>
<VenueTabBar venue="ice" active="home" />
</view>
</template>
@@ -92,6 +93,7 @@ import { ref, onMounted } from 'vue'
import { onPullDownRefresh } from '@dcloudio/uni-app'
import { homeApi } from '@/modules/ice/api/home'
import { errorMessage } from '@/modules/ice/api/session'
import VenueTabBar from '@/components/VenueTabBar.vue'
import StatePanel from '@/components/ice/StatePanel.vue'
const todayStr = ref('')
+3 -1
View File
@@ -181,7 +181,7 @@
</view>
<!-- 切换场馆条目 -->
<view class="menu-item" @tap="navigateTo('/pages/venue-select/index')">
<view class="menu-item" @tap="navigateTo('/pages/venue-select/index?mode=switch')">
<view class="item-left">
<view class="item-icon-container card-theme">
<view class="svg-card-minimal">
@@ -212,6 +212,7 @@
</view>
</view>
</block>
<VenueTabBar venue="ice" active="profile" />
</view>
</template>
@@ -220,6 +221,7 @@ import { ref, computed } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { api, authStorage } from '@/modules/ice/api/member'
import { errorMessage, getMemberSession, loginAndStoreToken, SESSION_STATUS } from '@/modules/ice/api/session'
import VenueTabBar from '@/components/VenueTabBar.vue'
import StatePanel from '@/components/ice/StatePanel.vue'
const isLoggedIn = ref(false)
+15 -5
View File
@@ -1,5 +1,5 @@
<template>
<view class="venue-select-page">
<view v-if="isReady" class="venue-select-page">
<view class="header">
<text class="title">选择场馆</text>
<text class="subtitle">进入对应场馆后会员预约订单和支付数据彼此独立</text>
@@ -32,17 +32,27 @@
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useVenueStore } from '@/stores/venue'
import { VENUE_OPTIONS } from '@/utils/venue'
import { VENUE_OPTIONS, navigateToVenueHome } from '@/utils/venue'
const venueStore = useVenueStore()
const venues = VENUE_OPTIONS
const isReady = ref(false)
const currentVenue = computed(() => venueStore.currentVenue)
const selectedVenue = computed(() => venueStore.currentVenueOption)
onMounted(() => {
venueStore.init()
onLoad((options) => {
const storedVenue = venueStore.init()
const isSwitchMode = options?.mode === 'switch'
if (storedVenue && !isSwitchMode) {
navigateToVenueHome(storedVenue)
return
}
isReady.value = true
})
const selectVenue = (venueKey) => {