Files
gym-manage/gym-manage-uniapp/pages/groupCourse/detail.vue
T

1089 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="course-detail-page">
<!-- 页面头部 -->
<PageHeader title="课程详情" subtitle="团课预约" :show-back="true" />
<!-- 骨架屏 -->
<view v-if="loading" class="skeleton">
<view class="skeleton-header">
<view class="skeleton-cover skeleton-block"></view>
</view>
<view class="skeleton-content">
<view class="skeleton-name skeleton-block"></view>
<view class="skeleton-meta">
<view class="skeleton-meta-item skeleton-block"></view>
<view class="skeleton-meta-item skeleton-block"></view>
<view class="skeleton-meta-item skeleton-block"></view>
</view>
<view class="skeleton-card">
<view class="skeleton-card-title skeleton-block"></view>
<view class="skeleton-card-content">
<view class="skeleton-text-line skeleton-block"></view>
<view class="skeleton-text-line skeleton-block"></view>
<view class="skeleton-text-line skeleton-block short"></view>
</view>
</view>
<view class="skeleton-card">
<view class="skeleton-info-row">
<view class="skeleton-info-label skeleton-block"></view>
<view class="skeleton-info-value skeleton-block"></view>
</view>
<view class="skeleton-info-row">
<view class="skeleton-info-label skeleton-block"></view>
<view class="skeleton-info-value skeleton-block"></view>
</view>
<view class="skeleton-info-row">
<view class="skeleton-info-label skeleton-block"></view>
<view class="skeleton-info-value skeleton-block"></view>
</view>
<view class="skeleton-info-row">
<view class="skeleton-info-label skeleton-block"></view>
<view class="skeleton-payment">
<view class="skeleton-payment-item skeleton-block"></view>
<view class="skeleton-payment-item skeleton-block"></view>
</view>
</view>
</view>
<view class="skeleton-card">
<view class="skeleton-card-title skeleton-block"></view>
<view class="skeleton-notice">
<view class="skeleton-notice-item skeleton-block"></view>
<view class="skeleton-notice-item skeleton-block"></view>
<view class="skeleton-notice-item skeleton-block"></view>
<view class="skeleton-notice-item skeleton-block"></view>
</view>
</view>
</view>
</view>
<!-- 内容区域 -->
<view v-else>
<!-- 顶部图片区域 -->
<view class="header-section">
<!-- 课程封面图 -->
<view class="cover-image-wrapper">
<image
v-if="course.coverImage"
:src="course.coverImage"
mode="aspectFill"
class="cover-image"
/>
<view v-else class="cover-image-placeholder">
<uni-icons type="image" size="80" color="#CCCCCC" />
<text class="placeholder-text">暂无封面</text>
</view>
</view>
</view>
<!-- 课程基本信息 -->
<view class="basic-info-section">
<view class="course-name-wrapper">
<text class="course-name">{{ course.courseName }}</text>
<!-- 课程状态标签 -->
<view :class="['status-tag', statusClass]">
{{ statusText }}
</view>
<view :class="['course-type-badge', courseTypeClass]">
{{ courseTypeText }}
</view>
</view>
<view class="course-meta">
<view class="meta-item">
<uni-icons type="calendar" size="24" color="#8A99B4" />
<text class="meta-text">{{ formatDate(course.startTime) }}</text>
</view>
<view class="meta-item">
<uni-icons type="clock" size="24" color="#8A99B4" />
<text class="meta-text">{{ formatTime(course.startTime) }} - {{ formatTime(course.endTime) }}</text>
</view>
<view class="meta-item">
<uni-icons type="map-pin" size="24" color="#8A99B4" />
<text class="meta-text">{{ course.location }}</text>
</view>
</view>
<!-- 课程标签 -->
<view class="course-tags">
<view class="tags-label">
<uni-icons type="tag" size="20" color="#8A99B4" />
<text>课程标签</text>
</view>
<view class="tags-list">
<view
v-for="label in courseLabels"
:key="label.id"
class="tag-item"
:style="{ background: `${label.color}15`, color: label.color }"
>
<text>{{ label.labelName }}</text>
</view>
<view v-if="courseLabels.length === 0" class="no-tags">
<text>暂无标签</text>
</view>
</view>
</view>
</view>
<!-- 课程详情卡片 -->
<view class="detail-card">
<view class="card-header">
<uni-icons type="info" size="24" color="#FF6B35" />
<text class="card-title">课程详情</text>
</view>
<view class="card-content">
<text class="description-text">{{ course.description }}</text>
</view>
</view>
<!-- 课程信息卡片 -->
<view class="info-card">
<view class="info-row">
<view class="info-label">
<uni-icons type="users" size="24" color="#5E6F8D" />
<text>课程容量</text>
</view>
<text class="info-value">{{ course.currentMembers }} / {{ course.maxMembers }}</text>
</view>
<view class="info-row">
<view class="info-label">
<uni-icons type="time" size="24" color="#5E6F8D" />
<text>课程时长</text>
</view>
<text class="info-value">{{ formatDuration(course.startTime, course.endTime) }}</text>
</view>
<view class="info-row">
<view class="info-label">
<uni-icons type="star" size="24" color="#5E6F8D" />
<text>教练</text>
</view>
<text class="info-value">ID: {{ course.coachId }}</text>
</view>
</view>
<!-- 预约须知 -->
<view class="notice-card">
<view class="card-header">
<uni-icons type="alert-circle" size="24" color="#F39C12" />
<text class="card-title">预约须知</text>
</view>
<view class="notice-list">
<view class="notice-item">
<text class="notice-dot"></text>
<text>请在课程开始前30分钟到达教室</text>
</view>
<view class="notice-item">
<text class="notice-dot"></text>
<text>如需取消预约请提前2小时操作</text>
</view>
<view class="notice-item">
<text class="notice-dot"></text>
<text>课程开始后将无法取消预约</text>
</view>
<view class="notice-item">
<text class="notice-dot"></text>
<text>迟到超过15分钟将无法入场</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<!-- 左侧支付方式选择包含价格 -->
<view v-if="hasMultiplePayment" class="payment-card">
<!-- 左侧滑块支付选择 -->
<view class="payment-slider" @click="togglePayment">
<view class="slider-bg" :class="{ right: selectedPayment === 'pointCard' }"></view>
<view class="tab-item" :class="{ active: selectedPayment === 'storedValue' }">
<text class="tab-label">储值卡</text>
</view>
<view class="tab-item" :class="{ active: selectedPayment === 'pointCard' }">
<text class="tab-label">次卡</text>
</view>
</view>
<!-- 右侧当前选中价格 -->
<view class="payment-price">
<text class="price" :class="selectedPrice.type">
<text class="currency">¥</text><text class="amount">{{ course.storedValueAmount }}</text>
<text class="point-text"><text class="point-icon"></text>{{ course.pointCardAmount }}</text>
</text>
</view>
</view>
<!-- 免费课程或单一支付方式 -->
<view v-else class="price-display">
<text v-if="course.storedValueAmount === 0 && course.pointCardAmount === 0" class="price free">免费</text>
<text v-else-if="course.storedValueAmount > 0" class="price">
<text class="currency">¥</text>{{ course.storedValueAmount }}
</text>
<text v-else class="price">
<text class="point-icon"></text>
<text>{{ course.pointCardAmount }}</text>
</text>
</view>
<!-- 右侧预约按钮 -->
<view :class="['booking-btn', { disabled: !canBook }]" @click="handleBooking">
<text>{{ canBook ? '立即预约' : (course.currentMembers >= course.maxMembers ? '已满员' : statusText) }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { groupCourseService } from '@/request_api/groupCourse.mock.js'
import PageHeader from '@/components/index/PageHeader.vue'
// 加载状态
const loading = ref(true)
// 课程详情数据
const course = ref({
id: '',
courseName: '',
courseType: '',
startTime: '',
endTime: '',
maxMembers: 0,
currentMembers: 0,
status: '',
location: '',
coverImage: '',
description: '',
coachId: '',
pointCardAmount: 0,
storedValueAmount: 0
})
// 课程标签数据
const courseLabels = ref([])
// 课程状态文本
const statusText = computed(() => {
const status = course.value.status
if (status === '0') return '进行中'
if (status === '1') return '已取消'
if (status === '2') return '已结束'
return '未知'
})
// 课程状态样式
const statusClass = computed(() => {
const status = course.value.status
if (status === '0') return 'active'
if (status === '1') return 'canceled'
if (status === '2') return 'ended'
return ''
})
// 课程类型文本
const courseTypeText = computed(() => {
const type = course.value.courseType
if (type === '1') return '瑜伽/普拉提'
if (type === '2') return '有氧训练'
if (type === '3') return '力量训练'
return '其他'
})
// 课程类型样式
const courseTypeClass = computed(() => {
const type = course.value.courseType
if (type === '1') return 'yoga'
if (type === '2') return 'cardio'
if (type === '3') return 'strength'
return ''
})
// 是否可以预约
const canBook = computed(() => {
const status = course.value.status
const isFull = course.value.currentMembers >= course.value.maxMembers
return status === '0' && !isFull
})
// 是否有多种支付方式
const hasMultiplePayment = computed(() => {
return course.value.storedValueAmount > 0 && course.value.pointCardAmount > 0
})
// 当前选中的支付方式
const selectedPayment = ref('storedValue')
// 当前选中的支付方式价格显示
const selectedPrice = computed(() => {
if (selectedPayment.value === 'storedValue') {
return { type: 'storedValue', amount: course.value.storedValueAmount }
} else if (selectedPayment.value === 'pointCard') {
return { type: 'pointCard', amount: course.value.pointCardAmount }
} else if (course.value.storedValueAmount > 0 && course.value.pointCardAmount > 0) {
// 默认优先显示储值卡
return { type: 'storedValue', amount: course.value.storedValueAmount }
} else if (course.value.storedValueAmount > 0) {
return { type: 'storedValue', amount: course.value.storedValueAmount }
} else if (course.value.pointCardAmount > 0) {
return { type: 'pointCard', amount: course.value.pointCardAmount }
}
return { type: 'free', amount: 0 }
})
// 选择/切换支付方式
const selectPayment = (type) => {
selectedPayment.value = type
}
// 切换支付方式(滑块点击)
const togglePayment = () => {
selectedPayment.value = selectedPayment.value === 'storedValue' ? 'pointCard' : 'storedValue'
}
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return ''
const date = new Date(dateStr)
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
const weekDay = weekDays[date.getDay()]
return `${year}${month}${day}${weekDay}`
}
// 格式化时间
const formatTime = (dateStr) => {
if (!dateStr) return ''
const date = new Date(dateStr)
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
return `${hours}:${minutes}`
}
// 格式化时长
const formatDuration = (startStr, endStr) => {
if (!startStr || !endStr) return ''
const start = new Date(startStr)
const end = new Date(endStr)
const minutes = Math.floor((end.getTime() - start.getTime()) / 60000)
return `${minutes}分钟`
}
// 预约处理
const handleBooking = () => {
if (!canBook.value) return
uni.showModal({
title: '确认预约',
content: `确定要预约「${course.value.courseName}」吗?`,
success: (res) => {
if (res.confirm) {
uni.showLoading({ title: '预约中...' })
groupCourseService.book({
courseId: course.value.id,
memberId: '1' // 模拟会员ID
}).then(() => {
uni.hideLoading()
uni.showToast({
title: '预约成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}).catch(() => {
uni.hideLoading()
uni.showToast({
title: '预约失败',
icon: 'none'
})
})
}
}
})
}
// 获取课程详情
const fetchCourseDetail = async (id) => {
try {
const result = await groupCourseService.getDetail(id)
course.value = result
console.log('[detail.vue] 课程详情获取成功:', course.value)
// 获取课程类型标签
await fetchCourseLabels(course.value.courseType)
} catch (error) {
console.error('[detail.vue] 获取课程详情失败:', error)
uni.showToast({
title: '获取课程详情失败',
icon: 'none'
})
} finally {
loading.value = false
}
}
// 获取课程标签
const fetchCourseLabels = async (courseType) => {
try {
const result = await groupCourseService.getLabelsByCourseType(courseType)
if (result.code === 0) {
courseLabels.value = result.data
}
console.log('[detail.vue] 课程标签获取成功:', courseLabels.value)
} catch (error) {
console.error('[detail.vue] 获取课程标签失败:', error)
}
}
// 页面挂载时获取课程详情
onMounted(() => {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = currentPage.options || {}
const courseId = options.id || '1'
console.log('[detail.vue] 页面挂载,课程ID:', courseId)
fetchCourseDetail(courseId)
})
</script>
<style lang="scss" scoped>
.course-detail-page {
min-height: 100vh;
background: #F5F7FA;
padding-bottom: calc(140rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
}
/* 顶部图片区域 */
.header-section {
width: 100%;
background: #FFFFFF;
position: relative;
}
.cover-image-wrapper {
width: 100%;
height: 480rpx;
background: #F5F7FA;
}
.cover-image {
width: 100%;
height: 100%;
}
.cover-image-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #F3F4F6 0%, #E5E7EB 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16rpx;
}
.placeholder-text {
font-size: 28rpx;
color: #9CA3AF;
}
/* 课程基本信息 */
.basic-info-section {
background: #ffffff;
padding: 32rpx;
margin-top: -40rpx;
border-radius: 32rpx 32rpx 0 0;
position: relative;
z-index: 10;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.course-name-wrapper {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 24rpx;
}
.course-name {
font-size: 40rpx;
font-weight: 700;
color: #1E2A3A;
flex: 1;
line-height: 1.4;
}
/* 课程基本信息区域的状态标签 */
.status-tag {
padding: 8rpx 20rpx;
border-radius: 16rpx;
font-size: 22rpx;
font-weight: 500;
&.active {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: #ffffff;
}
&.canceled {
background: linear-gradient(135deg, #9CA3AF 0%, #6B7280 100%);
color: #ffffff;
}
&.ended {
background: linear-gradient(135deg, #D1D5DB 0%, #9CA3AF 100%);
color: #ffffff;
}
}
.course-type-badge {
padding: 8rpx 20rpx;
border-radius: 16rpx;
font-size: 22rpx;
font-weight: 500;
&.yoga {
background: linear-gradient(135deg, #D1FAE5 0%, #A7F3D0 100%);
color: #065F46;
}
&.cardio {
background: linear-gradient(135deg, #FEF3C7 0%, #FDE68A 100%);
color: #92400E;
}
&.strength {
background: linear-gradient(135deg, #DBEAFE 0%, #93C5FD 100%);
color: #1E40AF;
}
}
.course-meta {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.meta-item {
display: flex;
align-items: center;
gap: 12rpx;
padding: 16rpx 20rpx;
background: linear-gradient(135deg, #F9FAFB 0%, #F3F4F6 100%);
border-radius: 16rpx;
}
.meta-text {
font-size: 28rpx;
color: #4B5563;
flex: 1;
}
/* 课程标签 */
.course-tags {
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 1rpx solid #F3F4F6;
}
.tags-label {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 26rpx;
color: #6B7280;
margin-bottom: 16rpx;
}
.tags-list {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.tag-item {
display: inline-flex;
align-items: center;
padding: 10rpx 24rpx;
border-radius: 24rpx;
font-size: 24rpx;
font-weight: 500;
}
.no-tags {
font-size: 24rpx;
color: #9CA3AF;
}
/* 详情卡片 */
.detail-card {
background: #ffffff;
margin: 24rpx;
border-radius: 24rpx;
padding: 28rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.card-header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 20rpx;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #1E2A3A;
}
.card-content {
padding-top: 8rpx;
}
.description-text {
font-size: 28rpx;
color: #5E6F8D;
line-height: 1.8;
text-align: justify;
}
/* 信息卡片 */
.info-card {
background: #ffffff;
margin: 0 24rpx 24rpx;
border-radius: 24rpx;
padding: 28rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.info-row {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #F3F4F6;
&:last-child {
border-bottom: none;
}
}
.info-label {
display: flex;
align-items: center;
gap: 12rpx;
width: 200rpx;
font-size: 28rpx;
color: #5E6F8D;
}
.info-value {
font-size: 28rpx;
color: #1E2A3A;
font-weight: 500;
flex: 1;
text-align: right;
}
.payment-options {
display: flex;
gap: 20rpx;
flex: 1;
justify-content: flex-end;
}
.payment-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16rpx 28rpx;
background: linear-gradient(135deg, #F9FAFB 0%, #F3F4F6 100%);
border-radius: 16rpx;
border: 2rpx solid transparent;
position: relative;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:active {
transform: scale(0.97);
}
&.active {
background: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%);
border-color: #FF6B35;
.payment-label,
.payment-value {
color: #ffffff;
}
}
&.free {
background: linear-gradient(135deg, #ECFDF5 0%, #D1FAE5 100%);
}
}
.payment-label {
font-size: 20rpx;
color: #6B7280;
}
.payment-value {
font-size: 28rpx;
font-weight: 600;
color: #FF6B35;
.free & {
color: #059669;
}
}
.payment-check {
position: absolute;
top: -8rpx;
right: -8rpx;
width: 36rpx;
height: 36rpx;
background: #059669;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
/* 预约须知 */
.notice-card {
background: #ffffff;
margin: 0 24rpx 24rpx;
border-radius: 24rpx;
padding: 28rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.notice-list {
padding-top: 8rpx;
}
.notice-item {
display: flex;
gap: 12rpx;
padding: 12rpx 0;
font-size: 26rpx;
color: #5E6F8D;
line-height: 1.6;
}
.notice-dot {
color: #F39C12;
font-size: 24rpx;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #ffffff;
padding: 16rpx 24rpx;
padding-bottom: calc(16rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
}
/* 支付卡片(包含选择器和价格) */
.payment-card {
display: flex;
align-items: center;
background: linear-gradient(135deg, #FFF7F5 0%, #FFF0EC 100%);
border-radius: 52rpx;
padding: 8rpx;
height: 88rpx;
flex: 1;
min-width: 0;
border: 2rpx solid rgba(255, 107, 53, 0.15);
}
/* 支付滑块选择器 */
.payment-slider {
display: flex;
align-items: center;
background: #F5F7FA;
border-radius: 44rpx;
padding: 6rpx;
position: relative;
overflow: hidden;
flex: 1;
height: 72rpx;
}
/* 滑动背景指示器 */
.payment-slider .slider-bg {
position: absolute;
top: 6rpx;
left: 6rpx;
width: calc(50% - 6rpx);
height: calc(100% - 12rpx);
background: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%);
border-radius: 38rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.35);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 0;
}
.payment-slider .slider-bg.right {
transform: translateX(100%);
}
.payment-slider .tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
border-radius: 38rpx;
transition: all 0.3s ease;
position: relative;
z-index: 1;
}
.payment-slider .tab-item.active .tab-label {
color: #ffffff;
}
.payment-slider .tab-label {
font-size: 24rpx;
font-weight: 600;
color: #6B7280;
transition: color 0.3s ease;
}
/* 支付卡片中的价格 */
.payment-price {
min-width: 140rpx;
padding: 0 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.payment-price .price {
font-size: 34rpx;
font-weight: 700;
color: #FF6B35;
display: flex;
align-items: baseline;
gap: 4rpx;
white-space: nowrap;
justify-content: center;
}
.payment-price .price .currency,
.payment-price .price .amount,
.payment-price .price .point-text {
display: none;
}
.payment-price .price.storedValue .currency,
.payment-price .price.storedValue .amount {
display: inline;
}
.payment-price .price.pointCard .point-text {
display: inline;
}
.payment-price .currency {
font-size: 22rpx;
font-weight: 600;
}
.payment-price .point-icon {
font-size: 22rpx;
}
/* 价格展示(单一支付方式或免费) */
.price-display {
flex: 1;
display: flex;
align-items: center;
padding-left: 8rpx;
}
.price-display .price {
font-size: 40rpx;
font-weight: 700;
display: flex;
align-items: baseline;
gap: 4rpx;
}
.price-display .price.free {
color: #10B981;
}
.price-display .price:not(.free) {
color: #FF6B35;
}
.price-display .currency {
font-size: 26rpx;
font-weight: 600;
}
.price-display .point-icon {
font-size: 26rpx;
}
/* 预约按钮 */
.booking-btn {
padding: 24rpx 64rpx;
background: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%);
border-radius: 52rpx;
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.3);
flex-shrink: 0;
}
.booking-btn.disabled {
background: linear-gradient(135deg, #D1D5DB 0%, #9CA3AF 100%);
color: #ffffff;
box-shadow: none;
}
/* 骨架屏样式 */
.skeleton {
padding-bottom: calc(140rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
}
.skeleton-header {
background: #ffffff;
padding-top: 20rpx;
}
.skeleton-cover {
width: 100%;
height: 480rpx;
border-radius: 0;
}
.skeleton-content {
margin-top: -40rpx;
padding: 0 24rpx;
}
.skeleton-block {
background: linear-gradient(90deg, #F0F0F0 25%, #E0E0E0 50%, #F0F0F0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
border-radius: 16rpx;
}
@keyframes skeleton-loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.skeleton-name {
width: 50%;
height: 48rpx;
margin-bottom: 20rpx;
}
.skeleton-meta {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-bottom: 24rpx;
}
.skeleton-meta-item {
width: 70%;
height: 48rpx;
}
.skeleton-card {
background: #ffffff;
border-radius: 24rpx;
padding: 28rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.skeleton-card-title {
width: 30%;
height: 36rpx;
margin-bottom: 20rpx;
}
.skeleton-card-content {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.skeleton-text-line {
height: 32rpx;
&.short {
width: 60%;
}
}
.skeleton-info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #F3F4F6;
&:last-child {
border-bottom: none;
}
}
.skeleton-info-label {
width: 160rpx;
height: 32rpx;
}
.skeleton-info-value {
width: 120rpx;
height: 32rpx;
}
.skeleton-payment {
display: flex;
gap: 16rpx;
}
.skeleton-payment-item {
width: 100rpx;
height: 60rpx;
}
.skeleton-notice {
display: flex;
flex-direction: column;
gap: 16rpx;
padding-top: 8rpx;
}
.skeleton-notice-item {
height: 32rpx;
width: 90%;
}
</style>