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,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>
|
||||
|
||||
Reference in New Issue
Block a user