147 lines
3.1 KiB
Vue
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>
|