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:
118
packages/app/src/components/BrandBanner.vue
Normal file
118
packages/app/src/components/BrandBanner.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<view class="brand-banner" :style="bannerStyle">
|
||||
<!-- Status bar spacer -->
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }" />
|
||||
|
||||
<!-- Nav area -->
|
||||
<view class="nav-bar">
|
||||
<view class="studio-name-row">
|
||||
<image
|
||||
v-if="studioInfo?.logo"
|
||||
class="logo"
|
||||
:src="studioInfo.logo"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="studio-name">{{ studioInfo?.name || '普拉提工作室' }}</text>
|
||||
</view>
|
||||
<text class="studio-slogan">专业 · 精致 · 健康</text>
|
||||
</view>
|
||||
|
||||
<!-- Decorative circles -->
|
||||
<view class="deco-circle deco-circle--1" />
|
||||
<view class="deco-circle deco-circle--2" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import type { StudioConfig } from '@mp-pilates/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
studioInfo: StudioConfig | null
|
||||
}>()
|
||||
|
||||
const statusBarHeight = ref(0)
|
||||
|
||||
onMounted(() => {
|
||||
const sysInfo = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = sysInfo.statusBarHeight ?? 20
|
||||
})
|
||||
|
||||
const bannerStyle = computed(() => {
|
||||
if (props.studioInfo?.bannerUrl) {
|
||||
return {
|
||||
backgroundImage: `url(${props.studioInfo.bannerUrl})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.brand-banner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 300rpx;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #2d2d5e 50%, #1a1a2e 100%);
|
||||
overflow: hidden;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 16rpx 40rpx 0;
|
||||
}
|
||||
|
||||
.studio-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.studio-name {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.studio-slogan {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #c9a87c;
|
||||
letter-spacing: 6rpx;
|
||||
}
|
||||
|
||||
/* Decorative blurred circles */
|
||||
.deco-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
opacity: 0.12;
|
||||
background: #c9a87c;
|
||||
}
|
||||
|
||||
.deco-circle--1 {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
top: -80rpx;
|
||||
right: -60rpx;
|
||||
}
|
||||
|
||||
.deco-circle--2 {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
bottom: -40rpx;
|
||||
right: 120rpx;
|
||||
opacity: 0.08;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user