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,105 @@
<template>
<view class="profile-page">
<view class="placeholder">
<text>我的 - 待实现</text>
</view>
<!-- User card: always visible -->
<UserCard
:logged-in="loggedIn"
:user="user"
:loading="loginLoading"
@login="handleLogin"
/>
<!-- Logged-in content -->
<template v-if="loggedIn">
<!-- Training stats: overlaps bottom of UserCard -->
<TrainingStats :stats="stats" />
<!-- Menu section -->
<ProfileMenu :is-admin="isAdmin" />
<!-- Logout button -->
<view class="profile-page__logout-wrap">
<button class="profile-page__logout-btn" @tap="handleLogout">退出登录</button>
</view>
</template>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { useUserStore } from '../../stores/user'
import UserCard from '../../components/UserCard.vue'
import TrainingStats from '../../components/TrainingStats.vue'
import ProfileMenu from '../../components/ProfileMenu.vue'
const userStore = useUserStore()
const { loggedIn, user, stats, isAdmin } = userStore
const loginLoading = ref(false)
onShow(async () => {
if (loggedIn) {
await Promise.all([userStore.fetchProfile(), userStore.fetchStats()])
}
})
async function handleLogin() {
if (loginLoading.value) return
loginLoading.value = true
try {
await userStore.login()
// After login, fetch stats immediately
await Promise.all([userStore.fetchProfile(), userStore.fetchStats()])
} catch {
uni.showToast({ title: '登录失败,请重试', icon: 'none' })
} finally {
loginLoading.value = false
}
}
function handleLogout() {
uni.showModal({
title: '退出登录',
content: '确定要退出登录吗?',
confirmText: '退出',
confirmColor: '#ff4d4f',
success(res) {
if (res.confirm) {
userStore.logout()
}
},
})
}
</script>
<style lang="scss" scoped>
.profile-page {
min-height: 100vh;
}
.placeholder {
display: flex;
align-items: center;
justify-content: center;
height: 400rpx;
color: #999;
background: $bg-page;
// Content area below the dark header card
// UserCard has its own dark bg, content sits on $bg-page
&__logout-wrap {
margin: $spacing-xl $spacing-lg $spacing-lg;
}
&__logout-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: $bg-card;
color: $error-color;
font-size: 30rpx;
font-weight: 500;
border: none;
border-radius: $radius-lg;
text-align: center;
&::after {
border: none;
}
}
}
</style>