From f94b48203f055fe2b789c6d9f2145e0f182b7c59 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Mon, 6 Apr 2026 21:22:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E7=9A=84=E9=A2=84=E7=BA=A6?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/src/components/SlotCard.vue | 414 +++++++++--------- packages/app/src/pages/booking/detail.vue | 309 +++++++++++-- packages/app/src/pages/booking/index.vue | 24 +- packages/app/src/pages/home/index.vue | 45 +- packages/app/src/pages/profile/index.vue | 22 +- packages/app/src/pages/profile/info.vue | 4 +- packages/app/src/static/courseBg.png | Bin 0 -> 1965 bytes packages/app/src/stores/booking.ts | 6 + .../src/time-slot/time-slot.controller.ts | 7 +- .../server/src/time-slot/time-slot.service.ts | 108 +++-- packages/server/tsconfig.build.tsbuildinfo | 2 +- 11 files changed, 599 insertions(+), 342 deletions(-) create mode 100644 packages/app/src/static/courseBg.png diff --git a/packages/app/src/components/SlotCard.vue b/packages/app/src/components/SlotCard.vue index 34ded69..cf2b27c 100644 --- a/packages/app/src/components/SlotCard.vue +++ b/packages/app/src/components/SlotCard.vue @@ -1,83 +1,88 @@ @@ -109,8 +108,12 @@ const props = defineProps() const emit = defineEmits<{ book: [timeSlot: TimeSlotWithBookingStatus] cancel: [timeSlot: TimeSlotWithBookingStatus] + cardTap: [timeSlot: TimeSlotWithBookingStatus] }>() +const startTimeDisplay = computed(() => props.timeSlot.startTime.slice(0, 5)) +const endTimeDisplay = computed(() => props.timeSlot.endTime.slice(0, 5)) + const durationMin = computed(() => { const [sh, sm] = props.timeSlot.startTime.split(':').map(Number) const [eh, em] = props.timeSlot.endTime.split(':').map(Number) @@ -122,7 +125,7 @@ const capacityLabel = computed(() => { if (status === TimeSlotStatus.CLOSED) return '已关闭' if (status === TimeSlotStatus.FULL) return '已约满' const remaining = capacity - bookedCount - return `剩余 ${remaining} 个名额` + return `剩余${remaining}位` }) const capacityClass = computed(() => { @@ -145,187 +148,147 @@ const isPast = computed(() => isSlotPast(props.timeSlot.date, props.timeSlot.sta diff --git a/packages/app/src/pages/booking/index.vue b/packages/app/src/pages/booking/index.vue index 6af4f6d..a7cb4ef 100644 --- a/packages/app/src/pages/booking/index.vue +++ b/packages/app/src/pages/booking/index.vue @@ -61,6 +61,7 @@ :time-slot="item" @book="onBookTap" @cancel="onCancelTap" + @card-tap="onSlotCardTap" /> @@ -150,7 +151,7 @@ updateLayout() // ─── Filtered slots ─────────────────────────────────────── const filteredSlots = computed(() => { const slots = bookingStore.slots as TimeSlotWithBookingStatus[] - if (!selectedPeriod.value) return [...slots] + if (!selectedPeriod.value) return slots const period = TIME_PERIODS[selectedPeriod.value] return slots.filter((slot) => { @@ -177,7 +178,19 @@ function onDateSelect(date: string) { } function onPeriodChange(_period: PeriodKey) { - // Filtering is done client-side via computed property + // No-op: filtering is done client-side via computed property + void _period +} + +// ─── Card tap → navigate to detail ─────────────────────── +function onSlotCardTap(slot: TimeSlotWithBookingStatus) { + if (slot.isBookedByMe && slot.myBookingId) { + // Already booked → show booking detail + uni.navigateTo({ url: `/pages/booking/detail?id=${slot.myBookingId}` }) + } else { + // Not booked → show slot preview with booking action + uni.navigateTo({ url: `/pages/booking/detail?slotId=${slot.id}&date=${slot.date}` }) + } } // ─── Book flow ──────────────────────────────────────────── @@ -213,7 +226,9 @@ async function onBookTap(slot: TimeSlotWithBookingStatus) { cancelText: '取消', success: (res) => { if (res.confirm) { - uni.navigateTo({ url: '/pages/store/index' }) + // Switch to home tab and auto-scroll to card shop + uni.$emit('scrollToCardShop') + uni.switchTab({ url: '/pages/home/index' }) } }, }) @@ -352,7 +367,7 @@ onMounted(async () => { } .skeleton-card { - height: 140rpx; + height: 220rpx; border-radius: 20rpx; background: #fff; display: flex; @@ -360,6 +375,7 @@ onMounted(async () => { align-items: center; padding: 28rpx 48rpx; gap: 20rpx; + margin: 0 24rpx; box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03); } diff --git a/packages/app/src/pages/home/index.vue b/packages/app/src/pages/home/index.vue index 3fc7194..964b63c 100644 --- a/packages/app/src/pages/home/index.vue +++ b/packages/app/src/pages/home/index.vue @@ -48,8 +48,8 @@ diff --git a/packages/app/src/pages/profile/index.vue b/packages/app/src/pages/profile/index.vue index bd99850..4386e35 100644 --- a/packages/app/src/pages/profile/index.vue +++ b/packages/app/src/pages/profile/index.vue @@ -4,25 +4,12 @@ - + - + @@ -131,7 +118,6 @@ function handleAbout() {