feat: migrate comprehensive venue module
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<view class="bookings-page">
|
||||
<view v-if="!session.isLoggedIn" class="unlogged">
|
||||
<text class="brand">星河综合运动中心</text>
|
||||
<text class="desc">登录后查看课程预约、包场预约与篮球门票权益。</text>
|
||||
<button class="primary-button login-btn" @tap="login">微信一键登录</button>
|
||||
</view>
|
||||
|
||||
<block v-else>
|
||||
<view class="sticky-tabs">
|
||||
<view class="business-tabs">
|
||||
<view v-for="tab in businessTabs" :key="tab.value" class="business-tab" :class="{ active: businessType === tab.value }" @tap="selectBusiness(tab.value)">
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="list">
|
||||
<view v-for="item in bookedItems" :key="`${businessType}-${item.id}`" class="booking-card card" @tap="goDetail(item)">
|
||||
<view class="card-top">
|
||||
<text class="item-title">{{ getItemTitle(item) }}</text>
|
||||
</view>
|
||||
<text class="item-meta primary">{{ getItemTime(item) }}</text>
|
||||
<text class="item-meta">{{ getItemDesc(item) }}</text>
|
||||
<view class="card-bottom">
|
||||
<text class="item-meta">订单金额</text>
|
||||
<text class="amount">¥{{ item.order?.amount || '--' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!isLoading && bookedItems.length === 0" class="empty-state">暂无已预约记录</view>
|
||||
<view v-if="isLoading" class="load-more-tip">加载中...</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { bookingApi } from '@/modules/comprehensive/api/booking'
|
||||
import { ticketApi } from '@/modules/comprehensive/api/ticket'
|
||||
import { useComprehensiveSessionStore } from '@/modules/comprehensive/stores/session'
|
||||
import { formatDateTime, formatTimeRange } from '@/modules/comprehensive/utils/date'
|
||||
|
||||
const session = useComprehensiveSessionStore()
|
||||
const businessType = ref('course')
|
||||
const items = ref([])
|
||||
const isLoading = ref(false)
|
||||
|
||||
const businessTabs = [
|
||||
{ label: '课程', value: 'course' },
|
||||
{ label: '包场', value: 'private' },
|
||||
{ label: '门票', value: 'ticket' }
|
||||
]
|
||||
|
||||
const bookedItems = computed(() => {
|
||||
return items.value.filter((item) => ['booked', 'available'].includes(item.status))
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await session.init()
|
||||
if (session.isLoggedIn) loadItems()
|
||||
})
|
||||
|
||||
const login = async () => {
|
||||
await session.login()
|
||||
loadItems()
|
||||
}
|
||||
|
||||
const selectBusiness = (value) => {
|
||||
businessType.value = value
|
||||
loadItems()
|
||||
}
|
||||
|
||||
const loadItems = async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
if (businessType.value === 'course') {
|
||||
const data = await bookingApi.getCourseBookings({ page: 1, pageSize: 50 })
|
||||
items.value = data.items
|
||||
} else if (businessType.value === 'private') {
|
||||
const data = await bookingApi.getPrivateBookings({ page: 1, pageSize: 50 })
|
||||
items.value = data.items
|
||||
} else {
|
||||
const data = await ticketApi.getTicketAccount({ page: 1, pageSize: 50 })
|
||||
items.value = data.records.map((record) => ({
|
||||
...record,
|
||||
order: {
|
||||
id: record.order_id,
|
||||
order_no: record.order_no,
|
||||
amount: record.order_id ? '80.00' : '--'
|
||||
}
|
||||
}))
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getItemTitle = (item) => {
|
||||
if (businessType.value === 'course') return item.course?.title || '课程预约'
|
||||
if (businessType.value === 'private') return item.courts?.map((court) => court.name).join('、') || '包场预约'
|
||||
return `篮球门票 ${item.order_no}`
|
||||
}
|
||||
|
||||
const getItemTime = (item) => {
|
||||
if (businessType.value === 'course') return formatTimeRange(item.course?.start_at, item.course?.end_at)
|
||||
if (businessType.value === 'private') return formatTimeRange(item.start_at, item.end_at)
|
||||
return item.checked_in_at ? `核销时间 ${formatDateTime(item.checked_in_at)}` : `生成时间 ${formatDateTime(item.created_at)}`
|
||||
}
|
||||
|
||||
const getItemDesc = (item) => {
|
||||
if (businessType.value === 'course') return `${item.course?.coach_name || ''} · ${item.course?.courts?.map((court) => court.name).join('、') || ''}`
|
||||
if (businessType.value === 'private') return `共 ${item.courts?.length || 0} 个场地`
|
||||
return item.status === 'available' ? '可到馆核销使用' : item.status_label
|
||||
}
|
||||
|
||||
const goDetail = (item) => {
|
||||
if (businessType.value === 'course') {
|
||||
uni.navigateTo({ url: `/pages/comprehensive/bookings/course-detail?id=${item.id}` })
|
||||
} else if (businessType.value === 'private') {
|
||||
uni.navigateTo({ url: `/pages/comprehensive/bookings/private-detail?id=${item.id}` })
|
||||
} else if (item.order_id) {
|
||||
uni.navigateTo({ url: `/pages/comprehensive/orders/detail?id=${item.order_id}` })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bookings-page {
|
||||
min-height: 100vh;
|
||||
background: $bg-color-soft;
|
||||
}
|
||||
|
||||
.unlogged {
|
||||
min-height: 80vh;
|
||||
padding: 180rpx $spacing-lg 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.brand {
|
||||
color: $text-color-main;
|
||||
font-size: 42rpx;
|
||||
font-weight: 900;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: $spacing-md;
|
||||
color: $text-color-muted;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 520rpx;
|
||||
margin-top: 100rpx;
|
||||
}
|
||||
|
||||
.sticky-tabs {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
padding: $spacing-md $spacing-md $spacing-sm;
|
||||
background: $bg-color-main;
|
||||
border-bottom: 1rpx solid $border-color;
|
||||
}
|
||||
|
||||
.business-tabs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: $spacing-sm;
|
||||
}
|
||||
|
||||
.business-tab {
|
||||
height: 64rpx;
|
||||
border-radius: $border-radius-base;
|
||||
background: $bg-color-hover;
|
||||
color: $text-color-muted;
|
||||
font-size: 24rpx;
|
||||
font-weight: 800;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.business-tab.active {
|
||||
background: $text-color-main;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.list {
|
||||
padding: $spacing-md;
|
||||
}
|
||||
|
||||
.booking-card {
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
.card-top,
|
||||
.card-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: $spacing-md;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
flex: 1;
|
||||
color: $text-color-main;
|
||||
font-size: 30rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.item-meta {
|
||||
display: block;
|
||||
margin-top: $spacing-xs;
|
||||
color: $text-color-muted;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.primary {
|
||||
margin-top: $spacing-md;
|
||||
color: $color-primary;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.card-bottom {
|
||||
margin-top: $spacing-md;
|
||||
padding-top: $spacing-md;
|
||||
border-top: 1rpx solid $bg-color-hover;
|
||||
}
|
||||
|
||||
.amount {
|
||||
color: $text-color-main;
|
||||
font-size: 30rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user