Files
venue-miniapp/miniprogram/src/pages/comprehensive/courses/detail.vue
T
2026-06-08 20:54:25 +08:00

147 lines
3.1 KiB
Vue

<template>
<view class="detail-page page-bottom-safe" v-if="course">
<image :src="course.cover_image" mode="aspectFill" class="hero-image" />
<view class="content">
<view class="title-card card">
<view class="title-row">
<text class="course-title">{{ course.title }}</text>
</view>
<view class="price-row">
<text class="price-text">¥{{ course.price }}</text>
</view>
</view>
<view class="info-card card">
<view v-for="item in infoItems" :key="item.label" class="info-row">
<text class="info-label">{{ item.label }}</text>
<text class="info-value">{{ item.value }}</text>
</view>
</view>
<view class="section-header">
<text class="section-title">课程详情</text>
</view>
<view class="rich-card card">
<rich-text :nodes="course.detail_html"></rich-text>
</view>
</view>
<view class="fixed-action-bar">
<button class="primary-button submit-btn" :disabled="!canBook" @tap="goConfirm">
{{ canBook ? '立即预约' : '暂不可约' }}
</button>
</view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { courseApi } from '@/modules/comprehensive/api/course'
import { formatTimeRange } from '@/modules/comprehensive/utils/date'
const course = ref(null)
const canBook = computed(() => {
return course.value && course.value.remaining_count > 0 && course.value.status === 'published'
})
const infoItems = computed(() => {
if (!course.value) return []
return [
{ label: '上课时间', value: formatTimeRange(course.value.start_at, course.value.end_at) },
{ label: '授课教练', value: course.value.coach_name },
{ label: '上课场地', value: course.value.courts.map((item) => item.name).join('、') }
]
})
onLoad(async (query) => {
course.value = await courseApi.getCourseDetail(query.id)
})
const goConfirm = () => {
if (!canBook.value) return
uni.navigateTo({ url: `/pages/comprehensive/courses/confirm?id=${course.value.id}` })
}
</script>
<style lang="scss" scoped>
.detail-page {
min-height: 100vh;
background: $bg-color-soft;
}
.hero-image {
width: 100%;
height: 420rpx;
background: $bg-color-hover;
}
.content {
padding: $spacing-md;
}
.title-card {
margin-top: -54rpx;
position: relative;
}
.title-row,
.price-row,
.info-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: $spacing-md;
}
.course-title {
flex: 1;
color: $text-color-main;
font-size: 38rpx;
font-weight: 900;
line-height: 1.3;
}
.price-row {
margin-top: $spacing-md;
}
.info-label {
color: $text-color-muted;
font-size: 24rpx;
}
.info-card,
.rich-card {
margin-top: $spacing-md;
}
.info-row {
min-height: 72rpx;
border-bottom: 1rpx solid $bg-color-hover;
}
.info-row:last-child {
border-bottom: none;
}
.info-value {
flex: 1;
color: $text-color-main;
font-size: 26rpx;
font-weight: 700;
text-align: right;
}
.rich-card {
color: $text-color-regular;
font-size: 28rpx;
line-height: 1.8;
}
.submit-btn {
flex: 1;
}
</style>