feat(app): implement home, booking, and profile pages
Home: brand banner, studio info swiper, smart quick entries based on membership status, upcoming bookings, card shop horizontal scroll Booking: 7-day date selector, time period filter, slot cards with status, booking confirm popup with membership picker Profile: user card with login, training stats, menu with admin entry 8 reusable components: BrandBanner, StudioInfo, QuickEntry, UpcomingBooking, CardShop, DateSelector, SlotCard, BookingConfirmPopup, TimePeriodFilter, UserCard, TrainingStats, ProfileMenu
This commit is contained in:
@@ -1,23 +1,108 @@
|
||||
<template>
|
||||
<view class="home-page">
|
||||
<view class="placeholder">
|
||||
<text>首页 - 待实现</text>
|
||||
</view>
|
||||
<!-- Pull-to-refresh wrapper -->
|
||||
<scroll-view
|
||||
class="page-scroll"
|
||||
scroll-y
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="handleRefresh"
|
||||
@refresherrestore="refreshing = false"
|
||||
>
|
||||
<!-- ① Brand Banner (custom nav) -->
|
||||
<BrandBanner :studio-info="studioStore.studioInfo" />
|
||||
|
||||
<!-- ② Studio Info (swiper + address + phone) -->
|
||||
<StudioInfo :studio-info="studioStore.studioInfo" />
|
||||
|
||||
<!-- ③ Quick Entry (login / trial / book / renew) -->
|
||||
<QuickEntry @scroll-to-card-shop="scrollToCardShop" />
|
||||
|
||||
<!-- ④ Upcoming Bookings -->
|
||||
<UpcomingBooking />
|
||||
|
||||
<!-- ⑤ Card Shop (horizontal scroll) -->
|
||||
<view :id="cardShopAnchorId">
|
||||
<CardShop ref="cardShopRef" />
|
||||
</view>
|
||||
|
||||
<!-- Bottom padding for tab bar -->
|
||||
<view class="bottom-padding" />
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
|
||||
import BrandBanner from '../../components/BrandBanner.vue'
|
||||
import StudioInfo from '../../components/StudioInfo.vue'
|
||||
import QuickEntry from '../../components/QuickEntry.vue'
|
||||
import UpcomingBooking from '../../components/UpcomingBooking.vue'
|
||||
import CardShop from '../../components/CardShop.vue'
|
||||
|
||||
import { useUserStore } from '../../stores/user'
|
||||
import { useStudioStore } from '../../stores/studio'
|
||||
import { useBookingStore } from '../../stores/booking'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const studioStore = useStudioStore()
|
||||
const bookingStore = useBookingStore()
|
||||
|
||||
const refreshing = ref(false)
|
||||
const cardShopRef = ref<InstanceType<typeof CardShop> | null>(null)
|
||||
const cardShopAnchorId = 'card-shop-anchor'
|
||||
|
||||
// Refresh all data on every show
|
||||
onShow(async () => {
|
||||
await refreshData()
|
||||
})
|
||||
|
||||
async function refreshData() {
|
||||
const tasks: Promise<unknown>[] = [studioStore.fetchStudioInfo()]
|
||||
|
||||
if (userStore.loggedIn) {
|
||||
tasks.push(
|
||||
userStore.fetchMemberships(),
|
||||
bookingStore.fetchUpcomingBookings(),
|
||||
)
|
||||
}
|
||||
|
||||
await Promise.allSettled(tasks)
|
||||
|
||||
// Also refresh card shop
|
||||
cardShopRef.value?.fetchCardTypes()
|
||||
}
|
||||
|
||||
async function handleRefresh() {
|
||||
refreshing.value = true
|
||||
try {
|
||||
await refreshData()
|
||||
} finally {
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToCardShop() {
|
||||
uni.pageScrollTo({
|
||||
selector: `#${cardShopAnchorId}`,
|
||||
duration: 300,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
|
||||
.page-scroll {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.bottom-padding {
|
||||
height: 120rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user