Files
gym-manage/gym-manage-uniapp/components/index/RecommendCourses.vue
T

361 lines
10 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="recommend-courses">
<!-- 区域标题栏 -->
<view class="section-header">
<!-- 区域标题 -->
<text class="section-title">推荐课程</text>
<!-- 查看更多按钮 -->
<view class="view-more">
<text>查看更多</text>
<text class="arrow">
<uni-icons type="right" size="20" color="#8CA0B0"/>
</text>
</view>
</view>
<!-- 课程横向滚动容器 -->
<scroll-view class="courses-scroll" scroll-x="true" :show-scrollbar="false">
<!-- 课程列表 -->
<view class="courses-list">
<!-- 课程卡片 -->
<view
v-for="(course, index) in courses"
:key="course.id || index"
class="course-card"
>
<!-- 课程图片区域 -->
<view class="course-image">
<!-- 课程封面图片 -->
<image :src="course.image" mode="aspectFill" class="img" />
<!-- 图片渐变遮罩 -->
<view class="course-overlay"></view>
<!-- 课程标签 -->
<text :class="['course-tag', course.tagType]">{{ course.tag }}</text>
<!-- 课程信息区域 -->
<view class="course-info">
<!-- 课程名称 -->
<text class="course-name">{{ course.name }}</text>
<!-- 课程元信息时长难度 -->
<view class="course-meta">
<!-- 时长信息 -->
<view class="meta-item">
<text class="meta-icon">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/time.png"/>
</text>
<text>{{ course.duration }}</text>
</view>
<!-- 难度信息 -->
<view class="meta-item">
<text class="meta-icon">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/intensity.png"/>
</text>
<text>{{ course.level }}</text>
</view>
</view>
</view>
</view>
<!-- 课程底部区域 -->
<view class="course-footer">
<!-- 参与人数信息 -->
<view class="participants">
<text class="fire-icon">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/hot.png"/>
</text>
<text>{{ course.participants }}人参与</text>
</view>
<!-- 去参与按钮 -->
<view class="join-btn" @click="handleJoinCourse(course)">
<text>去参与</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getGroupCoursePage } from '@/api/main.js'
// 推荐课程数据列表
const courses = ref([])
// 课程类型映射(用于显示标签)
const getCourseTypeName = (type) => {
const typeMap = {
'1': '瑜伽',
'2': '搏击',
'3': '塑形'
}
return typeMap[type] || '课程'
}
// 根据课程信息获取标签文本
const getTag = (course) => {
if (course.currentMembers >= course.maxMembers) return '已满员'
if (course.status === '2') return '已结束'
if (course.currentMembers / course.maxMembers >= 0.8) return '热门'
return getCourseTypeName(course.courseType)
}
// 根据课程信息获取标签样式类型
const getTagType = (course) => {
if (course.currentMembers >= course.maxMembers) return 'full'
if (course.status === '2') return 'ended'
if (course.currentMembers / course.maxMembers >= 0.8) return 'hot'
return 'default'
}
// 计算课程时长
const calculateDuration = (startTime, endTime) => {
if (!startTime || !endTime) return '60分钟'
const start = new Date(startTime)
const end = new Date(endTime)
const durationMinutes = Math.floor((end - start) / (1000 * 60))
return `${durationMinutes}分钟`
}
// 获取课程难度
const getCourseLevel = (course) => {
if (course.courseType === '2') return '中级'
if (course.courseType === '3') return '高级'
if (course.courseType === '1') return '初级'
return '初级'
}
// 处理图片URL
const getImageUrl = (coverImage) => {
if (!coverImage) return 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=400&q=80'
if (coverImage.startsWith('http')) return coverImage
return `https://your-domain.com${coverImage}`
}
// 获取推荐课程
const fetchRecommendCourses = async () => {
try {
const res = await getGroupCoursePage({
page: 0, size: 5, sort: 'current_members', order: 'desc'
}, { cache: true, cacheTime: 5 * 60 * 1000 })
if (res && res.content && Array.isArray(res.content)) {
courses.value = res.content.map(course => ({
id: course.id, image: getImageUrl(course.coverImage), tag: getTag(course),
tagType: getTagType(course), name: course.courseName || '未知课程',
duration: calculateDuration(course.startTime, course.endTime),
level: getCourseLevel(course), participants: course.currentMembers || 0, rawData: course
}))
} else { useFallbackData() }
} catch (err) { useFallbackData() }
}
const useFallbackData = () => {
const fallbackContent = [
{ id: "3", courseName: "燃脂搏击", courseType: "2", startTime: "2026-06-10T18:30:00", endTime: "2026-06-10T19:30:00", maxMembers: 20, currentMembers: 20, status: "0", coverImage: "/images/kickboxing.jpg", description: "高强度间歇训练" },
{ id: "2", courseName: "清晨流瑜伽", courseType: "1", startTime: "2026-06-12T09:00:00", endTime: "2026-06-12T10:30:00", maxMembers: 15, currentMembers: 5, status: "0", coverImage: "/images/yoga_flow.jpg", description: "流畅体式" },
{ id: "4", courseName: "哈他瑜伽", courseType: "1", startTime: "2026-06-01T15:20:00", endTime: "2026-06-01T16:50:00", maxMembers: 12, currentMembers: 3, status: "0", coverImage: "/images/hatha_yoga.jpg", description: "基础瑜伽" },
{ id: "6", courseName: "蜜桃臀塑造", courseType: "3", startTime: "2026-05-30T19:00:00", endTime: "2026-05-30T20:00:00", maxMembers: 10, currentMembers: 8, status: "2", coverImage: "/images/glute.jpg", description: "臀部训练" },
{ id: "7", courseName: "午间冥想放松", courseType: "1", startTime: "2026-05-31T12:00:00", endTime: "2026-05-31T13:00:00", maxMembers: 15, currentMembers: 6, status: "2", coverImage: "/images/meditation_noon.jpg", description: "冥想" }
]
courses.value = fallbackContent.map(course => ({
id: course.id, image: getImageUrl(course.coverImage), tag: getTag(course),
tagType: getTagType(course), name: course.courseName || '未知课程',
duration: calculateDuration(course.startTime, course.endTime),
level: getCourseLevel(course), participants: course.currentMembers || 0, rawData: course
}))
}
const handleJoinCourse = (course) => {
if (course.rawData.status === '2') { uni.showToast({ title: '课程已结束', icon: 'none' }); return }
if (course.rawData.currentMembers >= course.rawData.maxMembers) { uni.showToast({ title: '课程已满员', icon: 'none' }); return }
uni.navigateTo({ url: `/pages/course/detail?id=${course.id}` })
}
onMounted(() => { fetchRecommendCourses() })
</script>
<style lang="scss">
.recommend-courses {
padding: 0 24rpx;
margin-bottom: 32rpx;
position: relative;
z-index: 1;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.section-title {
font-size: 34rpx;
font-weight: 700;
color: #2D4A5A;
}
.view-more {
display: flex;
align-items: center;
gap: 4rpx;
font-size: 26rpx;
color: #8AABBB;
}
.arrow {
font-size: 32rpx;
}
.courses-scroll {
white-space: nowrap;
}
.courses-list {
display: inline-flex;
gap: 48rpx;
}
.course-card {
width: 320rpx;
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 8rpx 28rpx rgba(120, 185, 215, 0.18);
border: 1rpx solid rgba(255, 255, 255, 0.6);
display: inline-block;
vertical-align: top;
}
.course-image {
height: 280rpx;
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 20rpx;
}
.img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.course-overlay {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: linear-gradient(to top, rgba(45, 74, 90, 0.7) 0%, transparent 60%);
}
.course-tag {
position: absolute;
top: 16rpx;
right: 16rpx;
padding: 8rpx 16rpx;
border-radius: 12rpx;
font-size: 20rpx;
font-weight: 600;
color: #ffffff;
background: linear-gradient(135deg, #7AB5CC, #9CCFDF);
z-index: 2;
&.hot {
background: linear-gradient(135deg, #6BA8C0, #8CC5D5);
}
&.new {
background: linear-gradient(135deg, #6DB5C8, #90CEDD);
}
&.free {
background: linear-gradient(135deg, #7AB5CC, #9CCFDF);
}
&.full {
background: linear-gradient(135deg, #A0B8C8, #B8CCD8);
}
&.ended {
background: linear-gradient(135deg, #B0C0CC, #C4D2DC);
}
&.default {
background: linear-gradient(135deg, #7AB5CC, #9CCFDF);
}
}
.course-info {
position: relative;
z-index: 2;
}
.course-name {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
margin-bottom: 8rpx;
}
.course-meta {
display: flex;
gap: 16rpx;
align-items: center;
}
.meta-item {
display: flex;
align-items: end;
gap: 6rpx;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.8);
}
.meta-icon {
font-size: 20rpx;
image{
display: flex;
align-items: center;
width: 25rpx;
height: 25rpx;
}
}
.course-footer {
padding: 16rpx 10rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.participants {
display: flex;
align-items: center;
gap: 6rpx;
font-size: 22rpx;
color: #8AABBB;
}
.fire-icon {
font-size: 24rpx;
image{
display: flex;
align-items: center;
width: 30rpx;
height: 30rpx;
}
}
.join-btn {
padding: 12rpx 28rpx;
background: linear-gradient(135deg, #7AB5CC 0%, #9CCFDF 100%);
border: none;
border-radius: 9999rpx;
font-size: 22rpx;
font-weight: 600;
color: #ffffff;
box-shadow: 0 6rpx 16rpx rgba(122, 181, 204, 0.35);
}
</style>