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:
richarjiang
2026-04-02 14:35:17 +08:00
parent 554fc30954
commit 3a29aca0db
26 changed files with 7766 additions and 74 deletions

View File

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