perf: 个人中心支持展示约课数量

This commit is contained in:
richarjiang
2026-04-12 22:27:36 +08:00
parent 6cee28bf66
commit 1f45c3dc3f
3 changed files with 107 additions and 4 deletions

View File

@@ -48,6 +48,7 @@ const props = defineProps<{
isAdmin: boolean
requireAuth?: boolean
activeMembershipCount?: number
upcomingBookingCount?: number
}>()
const emit = defineEmits<{
@@ -60,6 +61,9 @@ const menuItems = computed<MenuItem[]>(() => {
const membershipBadge = props.activeMembershipCount && props.activeMembershipCount > 0
? `${props.activeMembershipCount}`
: undefined
const bookingBadge = props.upcomingBookingCount && props.upcomingBookingCount > 0
? `${props.upcomingBookingCount}`
: undefined
const items: MenuItem[] = [
{
@@ -75,6 +79,7 @@ const menuItems = computed<MenuItem[]>(() => {
type: 'item',
title: '我的预约',
path: '/pages/profile/bookings',
badge: bookingBadge,
requireAuth: true,
},
{

View File

@@ -21,10 +21,13 @@
<template v-else-if="!isSlotMode && booking">
<!-- Booking 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-dot" />
<text class="status-banner-text">{{ bookingStatusLabel(booking.status) }}</text>
</view>
<text class="status-banner-hint">{{ bookingStatusHint(booking.status) }}</text>
</view>
<!-- Course info -->
<view class="info-section">
@@ -326,6 +329,23 @@ function formatDateTime(dateStr: string): string {
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 ─────────────────────────────────────────────────────────
async function loadBookingData() {
loading.value = true
@@ -586,6 +606,72 @@ onLoad((query) => {
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 {
padding: 24rpx;
border-bottom: 1rpx solid #f5f5f5;

View File

@@ -12,6 +12,7 @@
:is-admin="isAdmin"
:require-auth="loggedIn"
:active-membership-count="activeMembershipCount"
:upcoming-booking-count="upcomingBookingCount"
@clear-cache="handleClearCache"
@about="handleAbout"
@require-login="handleLogin"
@@ -29,6 +30,7 @@ import { ref, computed, onMounted } from 'vue'
import { onShow, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
import { storeToRefs } from 'pinia'
import { useUserStore } from '../../stores/user'
import { useBookingStore } from '../../stores/booking'
import { getSystemLayout } from '../../utils/system'
import { getErrorMessage } from '../../utils/auth'
import UserCard from '../../components/UserCard.vue'
@@ -36,7 +38,9 @@ import ProfileMenu from '../../components/ProfileMenu.vue'
import CustomNavBar from '../../components/CustomNavBar.vue'
const userStore = useUserStore()
const bookingStore = useBookingStore()
const { loggedIn, hasProfile, user, stats, memberships, isAdmin } = storeToRefs(userStore)
const { upcomingBookings } = storeToRefs(bookingStore)
const loginLoading = ref(false)
const navBarHeight = ref(64)
@@ -45,6 +49,10 @@ const activeMembershipCount = computed(
() => user.value?.activeMembershipCount ?? userStore.activeMemberships.length,
)
const upcomingBookingCount = computed(
() => (loggedIn.value ? upcomingBookings.value.length : 0),
)
// ─── 微信分享 ───────────────────────────────────────────────
onShareAppMessage(() => {
return {
@@ -71,6 +79,7 @@ onShow(async () => {
userStore.fetchProfile(),
userStore.fetchStats(),
userStore.fetchMemberships(),
bookingStore.fetchUpcomingBookings(),
])
}
})
@@ -81,7 +90,10 @@ async function handleLogin() {
try {
const { isNewUser } = await userStore.loginWithSetup()
if (!isNewUser) {
await userStore.fetchStats()
await Promise.all([
userStore.fetchStats(),
bookingStore.fetchUpcomingBookings(),
])
}
} catch (err: unknown) {
uni.showToast({ title: getErrorMessage(err, '登录失败,请重试'), icon: 'none' })