perf: 个人中心支持展示约课数量
This commit is contained in:
@@ -48,6 +48,7 @@ const props = defineProps<{
|
|||||||
isAdmin: boolean
|
isAdmin: boolean
|
||||||
requireAuth?: boolean
|
requireAuth?: boolean
|
||||||
activeMembershipCount?: number
|
activeMembershipCount?: number
|
||||||
|
upcomingBookingCount?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -60,6 +61,9 @@ const menuItems = computed<MenuItem[]>(() => {
|
|||||||
const membershipBadge = props.activeMembershipCount && props.activeMembershipCount > 0
|
const membershipBadge = props.activeMembershipCount && props.activeMembershipCount > 0
|
||||||
? `${props.activeMembershipCount}张`
|
? `${props.activeMembershipCount}张`
|
||||||
: undefined
|
: undefined
|
||||||
|
const bookingBadge = props.upcomingBookingCount && props.upcomingBookingCount > 0
|
||||||
|
? `${props.upcomingBookingCount}`
|
||||||
|
: undefined
|
||||||
|
|
||||||
const items: MenuItem[] = [
|
const items: MenuItem[] = [
|
||||||
{
|
{
|
||||||
@@ -75,6 +79,7 @@ const menuItems = computed<MenuItem[]>(() => {
|
|||||||
type: 'item',
|
type: 'item',
|
||||||
title: '我的预约',
|
title: '我的预约',
|
||||||
path: '/pages/profile/bookings',
|
path: '/pages/profile/bookings',
|
||||||
|
badge: bookingBadge,
|
||||||
requireAuth: true,
|
requireAuth: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,9 +21,12 @@
|
|||||||
<template v-else-if="!isSlotMode && booking">
|
<template v-else-if="!isSlotMode && booking">
|
||||||
<!-- Booking info card -->
|
<!-- Booking info card -->
|
||||||
<view class="info-card">
|
<view class="info-card">
|
||||||
<!-- Status banner -->
|
<view class="info-card-header">
|
||||||
<view class="status-banner" :class="bookingStatusBannerClass(booking.status)">
|
<view class="status-banner" :class="bookingStatusBannerClass(booking.status)">
|
||||||
<text class="status-banner-text">{{ bookingStatusLabel(booking.status) }}</text>
|
<view class="status-banner-dot" />
|
||||||
|
<text class="status-banner-text">{{ bookingStatusLabel(booking.status) }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="status-banner-hint">{{ bookingStatusHint(booking.status) }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- Course info -->
|
<!-- Course info -->
|
||||||
@@ -326,6 +329,23 @@ function formatDateTime(dateStr: string): string {
|
|||||||
return `${y}-${m}-${day} ${hh}:${mm}`
|
return `${y}-${m}-${day} ${hh}:${mm}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bookingStatusHint(status: string): string {
|
||||||
|
switch (status) {
|
||||||
|
case BookingStatus.PENDING_CONFIRMATION:
|
||||||
|
return '预约已提交,等待教练确认后生效'
|
||||||
|
case BookingStatus.CONFIRMED:
|
||||||
|
return '课程已确认,请按时到店上课'
|
||||||
|
case BookingStatus.COMPLETED:
|
||||||
|
return '本次课程已完成'
|
||||||
|
case BookingStatus.CANCELLED:
|
||||||
|
return '该预约已取消'
|
||||||
|
case BookingStatus.NO_SHOW:
|
||||||
|
return '该预约已标记为未出席'
|
||||||
|
default:
|
||||||
|
return '查看当前预约状态'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Data loading ─────────────────────────────────────────────────────────
|
// ─── Data loading ─────────────────────────────────────────────────────────
|
||||||
async function loadBookingData() {
|
async function loadBookingData() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -586,6 +606,72 @@ onLoad((query) => {
|
|||||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-card-header {
|
||||||
|
padding: 28rpx 24rpx 22rpx;
|
||||||
|
background: linear-gradient(180deg, rgba(160, 144, 128, 0.08), rgba(160, 144, 128, 0.02));
|
||||||
|
border-bottom: 1rpx solid rgba(160, 144, 128, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-banner {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10rpx;
|
||||||
|
padding: 12rpx 20rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
border: 1rpx solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-banner-dot {
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-banner-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-banner-hint {
|
||||||
|
display: block;
|
||||||
|
margin-top: 14rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #8c8175;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner--pending {
|
||||||
|
color: #c9811c;
|
||||||
|
background: rgba(245, 158, 11, 0.12);
|
||||||
|
border-color: rgba(245, 158, 11, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner--confirmed {
|
||||||
|
color: #8b6b4d;
|
||||||
|
background: rgba(160, 144, 128, 0.12);
|
||||||
|
border-color: rgba(160, 144, 128, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner--completed {
|
||||||
|
color: #2f8f67;
|
||||||
|
background: rgba(102, 187, 106, 0.12);
|
||||||
|
border-color: rgba(102, 187, 106, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner--cancelled {
|
||||||
|
color: #9b9b9b;
|
||||||
|
background: rgba(224, 224, 224, 0.42);
|
||||||
|
border-color: rgba(210, 210, 210, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner--noshow {
|
||||||
|
color: #d85b57;
|
||||||
|
background: rgba(239, 83, 80, 0.12);
|
||||||
|
border-color: rgba(239, 83, 80, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
.info-section {
|
.info-section {
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
border-bottom: 1rpx solid #f5f5f5;
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
:is-admin="isAdmin"
|
:is-admin="isAdmin"
|
||||||
:require-auth="loggedIn"
|
:require-auth="loggedIn"
|
||||||
:active-membership-count="activeMembershipCount"
|
:active-membership-count="activeMembershipCount"
|
||||||
|
:upcoming-booking-count="upcomingBookingCount"
|
||||||
@clear-cache="handleClearCache"
|
@clear-cache="handleClearCache"
|
||||||
@about="handleAbout"
|
@about="handleAbout"
|
||||||
@require-login="handleLogin"
|
@require-login="handleLogin"
|
||||||
@@ -29,6 +30,7 @@ import { ref, computed, onMounted } from 'vue'
|
|||||||
import { onShow, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
import { onShow, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useUserStore } from '../../stores/user'
|
import { useUserStore } from '../../stores/user'
|
||||||
|
import { useBookingStore } from '../../stores/booking'
|
||||||
import { getSystemLayout } from '../../utils/system'
|
import { getSystemLayout } from '../../utils/system'
|
||||||
import { getErrorMessage } from '../../utils/auth'
|
import { getErrorMessage } from '../../utils/auth'
|
||||||
import UserCard from '../../components/UserCard.vue'
|
import UserCard from '../../components/UserCard.vue'
|
||||||
@@ -36,7 +38,9 @@ import ProfileMenu from '../../components/ProfileMenu.vue'
|
|||||||
import CustomNavBar from '../../components/CustomNavBar.vue'
|
import CustomNavBar from '../../components/CustomNavBar.vue'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const bookingStore = useBookingStore()
|
||||||
const { loggedIn, hasProfile, user, stats, memberships, isAdmin } = storeToRefs(userStore)
|
const { loggedIn, hasProfile, user, stats, memberships, isAdmin } = storeToRefs(userStore)
|
||||||
|
const { upcomingBookings } = storeToRefs(bookingStore)
|
||||||
|
|
||||||
const loginLoading = ref(false)
|
const loginLoading = ref(false)
|
||||||
const navBarHeight = ref(64)
|
const navBarHeight = ref(64)
|
||||||
@@ -45,6 +49,10 @@ const activeMembershipCount = computed(
|
|||||||
() => user.value?.activeMembershipCount ?? userStore.activeMemberships.length,
|
() => user.value?.activeMembershipCount ?? userStore.activeMemberships.length,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const upcomingBookingCount = computed(
|
||||||
|
() => (loggedIn.value ? upcomingBookings.value.length : 0),
|
||||||
|
)
|
||||||
|
|
||||||
// ─── 微信分享 ───────────────────────────────────────────────
|
// ─── 微信分享 ───────────────────────────────────────────────
|
||||||
onShareAppMessage(() => {
|
onShareAppMessage(() => {
|
||||||
return {
|
return {
|
||||||
@@ -71,6 +79,7 @@ onShow(async () => {
|
|||||||
userStore.fetchProfile(),
|
userStore.fetchProfile(),
|
||||||
userStore.fetchStats(),
|
userStore.fetchStats(),
|
||||||
userStore.fetchMemberships(),
|
userStore.fetchMemberships(),
|
||||||
|
bookingStore.fetchUpcomingBookings(),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -81,7 +90,10 @@ async function handleLogin() {
|
|||||||
try {
|
try {
|
||||||
const { isNewUser } = await userStore.loginWithSetup()
|
const { isNewUser } = await userStore.loginWithSetup()
|
||||||
if (!isNewUser) {
|
if (!isNewUser) {
|
||||||
await userStore.fetchStats()
|
await Promise.all([
|
||||||
|
userStore.fetchStats(),
|
||||||
|
bookingStore.fetchUpcomingBookings(),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
uni.showToast({ title: getErrorMessage(err, '登录失败,请重试'), icon: 'none' })
|
uni.showToast({ title: getErrorMessage(err, '登录失败,请重试'), icon: 'none' })
|
||||||
|
|||||||
Reference in New Issue
Block a user