重写会员端,初步完善首页

This commit is contained in:
2026-07-15 17:26:07 +08:00
parent cacc8997ec
commit 7e45ecd144
26 changed files with 3405 additions and 199 deletions
@@ -0,0 +1,202 @@
<template>
<view class="detail-root">
<view class="detail-page">
<view class="header-wrap" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-bar" :style="{ height: navBarHeight + 'px' }">
<text class="back-btn" @click="goBack">&#8249;</text>
<text class="nav-title">课程详情</text>
</view>
</view>
<view class="cover-area">
<view class="cover-bg">
<text class="cover-text">{{ course.typeChar }}</text>
</view>
<view class="status-tag" v-if="course.isFull">已约满</view>
</view>
<scroll-view class="content" scroll-y :style="{ height: 'calc(100vh - ' + totalHeaderHeight + 'px)' }">
<view class="content-inner">
<view class="course-header">
<view class="header-top">
<text class="course-title">{{ course.courseName }}</text>
<view class="difficulty-badge" :class="'diff-' + course.difficultyLevel">
{{ course.difficultyLabel }}
</view>
</view>
<view class="header-meta">
<text class="meta-type">{{ course.tag }}</text>
</view>
</view>
<view class="info-card">
<view class="info-row">
<view class="info-item">
<text class="info-label">教练</text>
<text class="info-value">{{ course.coachName }}</text>
</view>
<view class="info-item">
<text class="info-label">地点</text>
<text class="info-value">{{ course.location }}</text>
</view>
</view>
<view class="divider"></view>
<view class="info-row">
<view class="info-item">
<text class="info-label">日期时间</text>
<text class="info-value">{{ course.startTime }} - {{ course.endTime }}</text>
</view>
</view>
<view class="divider"></view>
<view class="info-row">
<view class="info-item">
<text class="info-label">时长</text>
<text class="info-value">{{ course.duration }} 分钟</text>
</view>
<view class="info-item">
<text class="info-label">人数</text>
<text class="info-value">
{{ course.currentMembers }} / {{ course.maxMembers }}
<text v-if="course.maxMembers - course.currentMembers > 0" class="remain-text">
({{ course.maxMembers - course.currentMembers }})
</text>
</text>
</view>
</view>
</view>
<view class="section-card">
<text class="section-title">&#8226; 课程介绍</text>
<text class="desc-text">{{ course.description }}</text>
</view>
<view class="section-card">
<text class="section-title">&#8226; 课程信息</text>
<view class="detail-grid">
<view class="detail-item">
<text class="detail-label">课程类型</text>
<text class="detail-value">{{ course.typeName || '团课' }}</text>
</view>
<view class="detail-item">
<text class="detail-label">基础难度</text>
<view class="stars">
<text v-for="i in 10" :key="i" class="star" :class="{ filled: i <= course.difficulty }">
{{ i <= course.difficulty ? '&#9733;' : '&#9734;' }}
</text>
</view>
</view>
<view class="detail-item">
<text class="detail-label">分类</text>
<text class="detail-value">{{ course.categoryLabel || '综合' }}</text>
</view>
<view class="detail-item">
<text class="detail-label">储值消耗</text>
<text class="detail-value price">{{ course.storedValueAmount > 0 ? course.storedValueAmount + '元' : '免费' }}</text>
</view>
</view>
</view>
<view class="bottom-safe"></view>
</view>
</scroll-view>
</view>
<view class="bottom-bar">
<view class="bottom-info">
<text class="bottom-price" :class="{ free: course.storedValueAmount === 0 }">
{{ course.storedValueAmount > 0 ? course.storedValueAmount : '免费' }}
</text>
<text class="bottom-desc">/ </text>
</view>
<button class="booking-btn" :class="{ disabled: course.isFull }" :disabled="course.isFull"
@click="handleBooking">
{{ course.isFull ? '已约满' : '立即预约' }}
</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
statusBarHeight: 0,
navBarHeight: 44,
totalHeaderHeight: 44,
course: { id: 1, courseName: '综合力量训练', coachName: '张教练', location: '力量区', typeChar: '力', tag: '热门', category: 'strength', categoryLabel: '力量训练', typeName: '自由重量杠铃/哑铃', startTime: '2026-07-15 10:00', endTime: '2026-07-15 10:45', duration: 45, maxMembers: 20, currentMembers: 8, isFull: false, difficulty: 5, difficultyLevel: 'mid', difficultyLabel: '中等', storedValueAmount: 0, description: '综合力量训练课程,涵盖杠铃、哑铃等自由重量训练,适合有一定基础的学员。' }
}
},
onLoad(options) {
const systemInfo = uni.getSystemInfoSync()
const statusBarHeight = systemInfo.statusBarHeight || 20
let navBarHeight = 44
// #ifdef MP-WEIXIN
const menuButton = uni.getMenuButtonBoundingClientRect()
if (menuButton) {
navBarHeight = (menuButton.top - statusBarHeight) * 2 + menuButton.height
}
// #endif
this.statusBarHeight = statusBarHeight
this.navBarHeight = navBarHeight
this.totalHeaderHeight = statusBarHeight + navBarHeight
if (options.id) this.loadCourseDetail(options.id)
},
methods: {
loadCourseDetail(id) {
const m = { 1: { courseName: '综合力量训练', coachName: '张教练', location: '力量区', typeChar: '力', tag: '热门', category: 'strength', categoryLabel: '力量训练', typeName: '自由重量杠铃/哑铃', startTime: '2026-07-15 10:00', endTime: '2026-07-15 10:45', duration: 45, maxMembers: 20, currentMembers: 8, isFull: false, difficulty: 5, difficultyLevel: 'mid', difficultyLabel: '中等', description: '综合力量训练课程,涵盖杠铃、哑铃等自由重量训练,适合有一定基础的学员。课程包括热身、主要力量训练组和拉伸放松三个环节,全面提升力量水平和身体协调性。' }, 2: { courseName: '流瑜伽 · 中级', coachName: '李教练', location: '3号教室', typeChar: '瑜', tag: '瑜伽', category: 'yoga', categoryLabel: '瑜伽', typeName: '柔韧与平衡类', startTime: '2026-07-15 14:00', endTime: '2026-07-15 15:00', duration: 60, maxMembers: 15, currentMembers: 15, isFull: true, difficulty: 5, difficultyLevel: 'mid', difficultyLabel: '中等', description: '流瑜伽中级课程,适合有一定瑜伽基础的学员。通过连贯的体式流动,提升身体柔韧性、核心力量和平衡能力。' }, 3: { courseName: '动感单车 · 冲刺', coachName: '王教练', location: '单车房', typeChar: '车', tag: '有氧', category: 'cardio', categoryLabel: '有氧运动', typeName: '基础有氧与热身', startTime: '2026-07-15 18:00', endTime: '2026-07-15 18:40', duration: 40, maxMembers: 25, currentMembers: 12, isFull: false, difficulty: 4, difficultyLevel: 'low', difficultyLabel: '入门', description: '高强度动感单车课程,结合节奏音乐,在教练的带领下进行冲刺、爬坡、耐力等训练。' }, 4: { courseName: 'HIIT训练', coachName: '赵教练', location: '多功能厅', typeChar: 'HI', tag: '有氧', category: 'cardio', categoryLabel: '有氧运动', typeName: '高强度与爆发力', startTime: '2026-07-15 12:00', endTime: '2026-07-15 12:30', duration: 30, maxMembers: 15, currentMembers: 10, isFull: false, difficulty: 7, difficultyLevel: 'high', difficultyLabel: '困难', description: '高强度间歇训练(HIIT),通过短时间高强度运动和短暂休息交替进行,高效燃脂并提升心肺功能。' }, 5: { courseName: '普拉提', coachName: '李教练', location: '2号教室', typeChar: '普', tag: '瑜伽', category: 'yoga', categoryLabel: '瑜伽', typeName: '柔韧与平衡类', startTime: '2026-07-15 16:00', endTime: '2026-07-15 16:50', duration: 50, maxMembers: 12, currentMembers: 5, isFull: false, difficulty: 4, difficultyLevel: 'low', difficultyLabel: '入门', description: '普拉提课程注重核心力量和身体控制,通过精准的动作改善体态、增强核心稳定性和身体柔韧性。' }, 6: { courseName: '搏击操', coachName: '刘教练', location: '搏击区', typeChar: '搏', tag: '有氧', category: 'cardio', categoryLabel: '有氧运动', typeName: '高强度与爆发力', startTime: '2026-07-15 07:00', endTime: '2026-07-15 07:45', duration: 45, maxMembers: 20, currentMembers: 6, isFull: false, difficulty: 5, difficultyLevel: 'mid', difficultyLabel: '中等', description: '搏击操结合拳击、踢拳等动作元素,在动感音乐下进行高强度有氧训练。' } }
if (m[id]) this.course = { ...this.course, ...m[id], id: parseInt(id) }
},
goBack() { uni.navigateBack() },
handleBooking() { if (this.course.isFull) return; uni.showToast({ title: '预约成功', icon: 'success' }); setTimeout(() => { uni.navigateBack() }, 1000) }
}
}
</script>
<style scoped>
.detail-page { min-height: 100vh; background: #F5F7FA; overflow-x: hidden; }
.header-wrap { background: #1A1A1A; }
.nav-bar { background: #1A1A1A; padding: 0 20px; display: flex; align-items: center; }
.back-btn { font-size: 26px; color: #FFFFFF; font-weight: 700; margin-right: 10px; line-height: 1; }
.nav-title { font-size: 18px; font-weight: 700; color: #FFFFFF; }
.cover-area { height: 180px; position: relative; overflow: hidden; }
.cover-bg { width: 100%; height: 100%; background: linear-gradient(135deg, #00C853, #00BFA5); display: flex; align-items: center; justify-content: center; }
.cover-text { font-size: 56px; font-weight: 700; color: rgba(255,255,255,0.9); }
.status-tag { position: absolute; top: 14px; right: 16px; background: #FF5252; color: #FFFFFF; font-size: 12px; font-weight: 600; padding: 4px 12px; border-radius: 40px; }
.content-inner { padding-bottom: 100px; }
.course-header { background: #FFFFFF; padding: 20px 16px 16px; border-radius: 20px 20px 0 0; margin-top: -16px; position: relative; z-index: 1; }
.header-top { display: flex; justify-content: space-between; align-items: center; }
.course-title { font-size: 22px; font-weight: 700; color: #1E1E1E; }
.difficulty-badge { padding: 4px 12px; border-radius: 40px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.diff-low { background: rgba(0,230,118,0.15); color: #00C853; }
.diff-mid { background: rgba(255,165,2,0.15); color: #FFA502; }
.diff-high { background: rgba(255,82,82,0.15); color: #FF5252; }
.header-meta { margin-top: 8px; }
.meta-type { font-size: 13px; color: #00C853; background: rgba(0,230,118,0.15); padding: 3px 10px; border-radius: 20px; font-weight: 500; }
.info-card { background: #FFFFFF; margin: 0 16px; border-radius: 20px; padding: 16px; box-shadow: 0 4px 12px rgba(0,0,0,0.06); margin-top: 12px; }
.info-row { display: flex; justify-content: space-between; }
.info-item { display: flex; flex-direction: column; flex: 1; }
.info-label { font-size: 12px; color: #7A7E84; margin-bottom: 4px; }
.info-value { font-size: 14px; font-weight: 600; color: #1E1E1E; }
.remain-text { font-size: 12px; color: #00C853; font-weight: 500; }
.divider { height: 1px; background: #EEEEEE; margin: 14px 0; }
.section-card { background: #FFFFFF; margin: 12px 16px; border-radius: 20px; padding: 16px; box-shadow: 0 4px 12px rgba(0,0,0,0.06); }
.section-title { font-size: 16px; font-weight: 700; color: #1E1E1E; display: block; margin-bottom: 12px; }
.desc-text { font-size: 14px; color: #7A7E84; line-height: 1.7; }
.detail-grid { display: flex; flex-wrap: wrap; }
.detail-item { width: 50%; display: flex; flex-direction: column; margin-bottom: 14px; }
.detail-label { font-size: 12px; color: #7A7E84; }
.detail-value { font-size: 14px; font-weight: 600; color: #1E1E1E; margin-top: 4px; }
.detail-value.price { color: #00C853; }
.stars { display: flex; flex-wrap: wrap; }
.star { font-size: 13px; color: #E0E0E0; }
.star.filled { color: #FFA502; }
.bottom-safe { height: 30px; }
.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #FFFFFF; padding: 12px 16px; padding-bottom: calc(12px + env(safe-area-inset-bottom)); display: flex; align-items: center; justify-content: space-between; box-shadow: 0 -2px 12px rgba(0,0,0,0.06); z-index: 100; }
.bottom-info { display: flex; align-items: baseline; }
.bottom-price { font-size: 22px; font-weight: 700; color: #00C853; }
.bottom-price.free { color: #00C853; }
.bottom-desc { font-size: 13px; color: #7A7E84; margin-left: 2px; }
.booking-btn { background: #00E676; color: #1A1A1A; border: none; padding: 12px 32px; border-radius: 40px; font-weight: 700; font-size: 16px; box-shadow: 0 4px 14px rgba(0,230,118,0.35); line-height: 1.2; }
.booking-btn::after { border: none; }
.booking-btn.disabled { background: #E0E0E0; color: #BDBDBD; box-shadow: none; }
</style>