feat: 完善课程订阅

This commit is contained in:
richarjiang
2026-04-06 08:38:05 +08:00
parent f71ff968ad
commit 3a9982209f
21 changed files with 2301 additions and 94 deletions

View File

@@ -56,8 +56,9 @@
v-for="booking in upcomingBookings"
:key="booking.id"
class="booking-card"
@tap="goDetail(booking)"
>
<view class="booking-stripe stripe--confirmed" />
<view class="booking-stripe" :class="stripeClass(booking.status)" />
<view class="booking-content">
<view class="booking-header">
<view class="booking-datetime">
@@ -66,19 +67,26 @@
{{ booking.timeSlot.startTime.slice(0, 5) }} - {{ booking.timeSlot.endTime.slice(0, 5) }}
</text>
</view>
<view class="status-badge badge--confirmed">
<text class="status-text">已预约</text>
<view class="status-badge" :class="statusBadgeClass(booking.status)">
<text class="status-text">{{ statusLabel(booking.status) }}</text>
</view>
</view>
<view class="booking-footer">
<view v-if="booking.status !== 'PENDING_CONFIRMATION'" class="booking-footer">
<view class="booking-meta">
<text class="meta-label">使用卡种</text>
<text class="meta-value">{{ booking.membership.cardType.name }}</text>
</view>
<view class="cancel-btn" @tap="handleCancel(booking)">
<view class="cancel-btn" @tap.stop="handleCancel(booking)">
<text class="cancel-text">取消预约</text>
</view>
</view>
<view v-else class="booking-footer">
<view class="booking-meta">
<text class="meta-label">使用卡种</text>
<text class="meta-value">{{ booking.membership.cardType.name }}</text>
</view>
<text class="pending-hint">等待老师确认</text>
</view>
</view>
</view>
</view>
@@ -121,6 +129,7 @@
v-for="booking in historyBookings"
:key="booking.id"
class="booking-card"
@tap="goDetail(booking)"
>
<view class="booking-stripe" :class="stripeClass(booking.status)" />
<view class="booking-content">
@@ -190,7 +199,9 @@ const today = computed(() => formatDate(new Date()))
const upcomingBookings = computed<BookingWithDetails[]>(() => {
return safeBookings()
.filter(
(b) => b.status === BookingStatus.CONFIRMED && toDateStr(b.timeSlot.date) >= today.value,
(b) =>
(b.status === BookingStatus.PENDING_CONFIRMATION || b.status === BookingStatus.CONFIRMED) &&
toDateStr(b.timeSlot.date) >= today.value,
)
.sort((a, b) => {
const dateA = toDateStr(a.timeSlot.date)
@@ -222,36 +233,39 @@ const historyBookings = computed<BookingWithDetails[]>(() => {
const upcomingCount = computed(() => upcomingBookings.value.length)
// ─── Helpers ──────────────────────────────────────────────
const STATUS_LABELS: Record<BookingStatus, string> = {
const STATUS_LABELS: Record<string, string> = {
[BookingStatus.PENDING_CONFIRMATION]: '待确认',
[BookingStatus.CONFIRMED]: '已预约',
[BookingStatus.CANCELLED]: '已取消',
[BookingStatus.COMPLETED]: '已完成',
[BookingStatus.NO_SHOW]: '未出席',
}
const STATUS_BADGE_CLASSES: Record<BookingStatus, string> = {
const STATUS_BADGE_CLASSES: Record<string, string> = {
[BookingStatus.PENDING_CONFIRMATION]: 'badge--pending',
[BookingStatus.CONFIRMED]: 'badge--confirmed',
[BookingStatus.CANCELLED]: 'badge--cancelled',
[BookingStatus.COMPLETED]: 'badge--completed',
[BookingStatus.NO_SHOW]: 'badge--noshow',
}
const STATUS_STRIPE_CLASSES: Record<BookingStatus, string> = {
const STATUS_STRIPE_CLASSES: Record<string, string> = {
[BookingStatus.PENDING_CONFIRMATION]: 'stripe--pending',
[BookingStatus.CONFIRMED]: 'stripe--confirmed',
[BookingStatus.CANCELLED]: 'stripe--cancelled',
[BookingStatus.COMPLETED]: 'stripe--completed',
[BookingStatus.NO_SHOW]: 'stripe--noshow',
}
function statusLabel(status: BookingStatus): string {
function statusLabel(status: string): string {
return STATUS_LABELS[status] ?? status
}
function statusBadgeClass(status: BookingStatus): string {
function statusBadgeClass(status: string): string {
return STATUS_BADGE_CLASSES[status] ?? ''
}
function stripeClass(status: BookingStatus): string {
function stripeClass(status: string): string {
return STATUS_STRIPE_CLASSES[status] ?? ''
}
@@ -297,6 +311,10 @@ function goBooking() {
uni.switchTab({ url: '/pages/booking/index' })
}
function goDetail(booking: BookingWithDetails) {
uni.navigateTo({ url: `/pages/booking/detail?id=${booking.id}` })
}
async function handleCancel(booking: BookingWithDetails) {
const dateLabel = formatDateDisplay(booking.timeSlot.date)
const timeLabel = booking.timeSlot.startTime.slice(0, 5)
@@ -535,6 +553,7 @@ onMounted(() => {
width: 8rpx;
flex-shrink: 0;
&.stripe--pending { background: #f59e0b; }
&.stripe--confirmed { background: $primary-dark; }
&.stripe--completed { background: #66bb6a; }
&.stripe--cancelled { background: #e0e0e0; }
@@ -580,6 +599,7 @@ onMounted(() => {
border-radius: 20rpx;
flex-shrink: 0;
&.badge--pending { background: rgba(245, 158, 11, 0.12); }
&.badge--confirmed { background: rgba(201, 168, 124, 0.12); }
&.badge--completed { background: rgba(102, 187, 106, 0.12); }
&.badge--cancelled { background: rgba(0, 0, 0, 0.04); }
@@ -590,6 +610,7 @@ onMounted(() => {
font-size: 22rpx;
font-weight: 600;
.badge--pending & { color: #f59e0b; }
.badge--confirmed & { color: $primary-dark; }
.badge--completed & { color: #66bb6a; }
.badge--cancelled & { color: #bbb; }
@@ -650,6 +671,12 @@ onMounted(() => {
font-weight: 500;
}
.pending-hint {
font-size: 24rpx;
color: #f59e0b;
font-weight: 500;
}
/* ── Spacer ──────────────────────────────────────────── */
.scroll-bottom-spacer {
height: 48rpx;