Files
gym-manage/gym-manage-uniapp/components/index/RecommendCourses.vue
T
2026-06-29 13:35:29 +08:00

333 lines
8.5 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" @click="handleViewMore">
<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"
@click="handleJoinCourse(course)"
>
<!-- 课程图片区域 -->
<view class="course-image">
<!-- 课程封面图片 -->
<image :src="course.image" mode="aspectFill" class="img" />
<!-- 图片渐变遮罩 -->
<view class="course-overlay"></view>
<!-- 课程信息区域 -->
<view class="course-info">
<!-- 课程名称 -->
<text class="course-name">{{ course.name }}</text>
<!-- 课程元信息时长难度 -->
<view class="course-meta">
<view class="meta-left">
<!-- 时长信息 -->
<view class="meta-item time-tag">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/time.png" class="meta-icon" />
<text>{{ course.duration }}</text>
</view>
<!-- 难度信息 -->
<view class="meta-item level-tag">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/intensity.png" class="meta-icon" />
<text>{{ course.level }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 课程底部区域 -->
<view class="course-footer">
<!-- 参与人数信息 -->
<view class="participants">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/hot.png" class="fire-icon" />
<text>{{ course.participants }}人参与</text>
</view>
<!-- 去参与按钮 -->
<view class="join-btn" @click.stop="handleJoinCourse(course)">
<text>去参与</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getActiveRecommendCourses } from '@/api/groupCourse.js'
console.log('[RecommendCourses] 组件脚本已加载')
// 推荐课程数据列表
const courses = ref([])
// 计算课程时长
const calculateDuration = (startTime, endTime) => {
if (!startTime || !endTime) return '60分钟'
const start = new Date(startTime)
const end = new Date(endTime)
const diffMs = end - start
if (diffMs <= 0) return ''
const durationMinutes = Math.floor(diffMs / (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 () => {
console.log('[RecommendCourses] fetchRecommendCourses 开始请求')
try {
const res = await getActiveRecommendCourses()
console.log('[RecommendCourses] API 返回:', JSON.stringify(res).substring(0, 200))
if (res && Array.isArray(res)) {
// 提取推荐中的 groupCourse,只排除已满员的课程(已结束的仍展示,点击时拦截)
const filteredCourses = res.filter(recommend => {
const course = recommend.groupCourse
if (!course) return false
const current = Number(course.currentMembers) || 0
const max = Number(course.maxMembers) || 999
return current < max
})
// 只取前3个符合条件的推荐课程
courses.value = filteredCourses.slice(0, 3).map(recommend => {
const course = recommend.groupCourse
return {
id: course.id,
image: getImageUrl(course.coverImage),
name: course.courseName || '未知课程',
duration: calculateDuration(course.startTime, course.endTime),
level: getCourseLevel(course),
participants: course.currentMembers || 0,
rawData: course
}
})
console.log('[RecommendCourses] 最终展示 courses.value 数量:', courses.value.length)
} else {
console.warn('[RecommendCourses] API 返回格式异常:', res)
}
} catch (err) {
console.error('获取推荐课程失败:', err)
}
}
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/groupCourse/detail?id=${course.id}` })
}
const handleViewMore = () => {
uni.navigateTo({ url: '/pages/recommendCourses/index' })
}
onMounted(() => { console.log('[RecommendCourses] onMounted 触发'); fetchRecommendCourses() })
onShow(() => {
console.log('[RecommendCourses] onShow 触发,刷新推荐课程')
fetchRecommendCourses()
})
defineExpose({ 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: var(--tabbar-text-inactive);
}
.arrow {
font-size: 32rpx;
}
.courses-scroll {
white-space: nowrap;
}
.courses-list {
display: inline-flex;
gap: 24rpx;
padding-right: 24rpx;
}
.course-card {
width: 338rpx;
background: rgba(255, 255, 255, 0.9);
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 8rpx 28rpx var(--shadow-blue-light);
border: 1rpx solid rgba(255, 255, 255, 0.6);
display: inline-block;
vertical-align: top;
}
.course-image {
height: 260rpx;
position: relative;
}
.img {
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-info {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 16rpx 20rpx;
z-index: 2;
}
.course-name {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.course-meta {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8rpx;
}
.meta-left {
display: flex;
gap: 8rpx;
}
.meta-item {
display: flex;
align-items: center;
gap: 6rpx;
font-size: 20rpx;
color: rgba(255, 255, 255, 0.9);
padding: 6rpx 10rpx;
border-radius: 6rpx;
background: rgba(255, 255, 255, 0.2);
line-height: 1;
.meta-icon {
vertical-align: middle;
}
}
.time-tag {
background: rgba(255, 255, 255, 0.25);
}
.level-tag {
background: rgba(255, 255, 255, 0.25);
}
.meta-icon {
width: 22rpx;
height: 22rpx;
display: inline-block;
vertical-align: middle;
}
.course-footer {
padding: 16rpx 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.participants {
display: flex;
align-items: center;
gap: 6rpx;
font-size: 22rpx;
color: var(--tabbar-text-inactive);
}
.fire-icon {
width: 32rpx;
height: 32rpx;
}
.join-btn {
padding: 12rpx 28rpx;
background: rgba(130, 220, 130, 0.9);
border: none;
border-radius: 9999rpx;
font-size: 22rpx;
font-weight: 600;
color: #ffffff;
box-shadow: 0 6rpx 16rpx rgba(130, 220, 130, 0.35);
}
</style>