Files
gym-manage/gym-manage-uniapp/pages/recommendCourses/index.vue
T
2026-06-15 18:03:42 +08:00

230 lines
5.8 KiB
Vue

<!-- pages/recommendCourses/index.vue -->
<template>
<!-- 页面容器 -->
<view class="recommend-page">
<!-- 页面标题 -->
<PageHeader title="推荐课程" subtitle="精选热门团课,等你来练" :show-back="true" />
<!-- 骨架屏 -->
<view v-if="loading" class="skeleton-container">
<view class="skeleton-card" v-for="i in 3" :key="i">
<view class="skeleton-header"></view>
<view class="skeleton-body">
<view class="skeleton-img"></view>
<view class="skeleton-text-group">
<view class="skeleton-text"></view>
<view class="skeleton-text-short"></view>
</view>
</view>
</view>
</view>
<!-- 推荐课程列表 -->
<scroll-view v-else class="courses-container" scroll-y="true">
<view class="courses-list">
<RecommendCourseCard
v-for="recommend in recommendCourses"
:key="recommend.id"
:recommend="recommend"
@click="handleCardClick(recommend)"
/>
</view>
<!-- 空状态 -->
<view v-if="!loading && recommendCourses.length === 0" class="empty-state">
<text class="empty-text">暂无推荐课程</text>
</view>
<!-- 底部占位 -->
<view class="bottom-placeholder"></view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import PageHeader from '@/components/index/PageHeader.vue'
import RecommendCourseCard from '@/components/recommendCourses/RecommendCourseCard.vue'
import { getGroupCourseRecommendList } from '@/api/groupCourse.js'
// 推荐课程列表数据
const recommendCourses = ref([])
// 加载状态
const loading = ref(true)
// 从缓存加载数据
function loadFromCache() {
try {
const cached = uni.getStorageSync('recommend_courses_cache')
if (cached && Date.now() - cached.time < 5 * 60 * 1000) {
recommendCourses.value = cached.data
loading.value = false
return true
}
} catch (e) {
console.error('[Recommend Courses Page] 读取缓存失败', e)
}
return false
}
// 从网络加载数据
async function loadFromNetwork() {
loading.value = true
console.log('[Recommend Courses Page] 开始从后端获取团课推荐列表...')
try {
// 调用获取所有团课推荐接口
console.log('[Recommend Courses Page] 发起 API 请求: GET /groupCourse/recommend/list')
const res = await getGroupCourseRecommendList({
sortBy: 'priority',
sortOrder: 'desc'
})
console.log('[Recommend Courses Page] API 响应数据:', res)
if (res && Array.isArray(res)) {
recommendCourses.value = res
console.log('[Recommend Courses Page] 获取到', res.length, '条推荐课程数据')
// 更新缓存
uni.setStorageSync('recommend_courses_cache', {
data: res,
time: Date.now()
})
} else {
console.log('[Recommend Courses Page] API 响应为空或格式不正确')
recommendCourses.value = []
}
} catch (err) {
console.error('[Recommend Courses Page] 加载失败:', err)
uni.showToast({ title: '加载失败', icon: 'none' })
recommendCourses.value = []
} finally {
loading.value = false
console.log('[Recommend Courses Page] 数据加载完成,loading:', loading.value)
}
}
// 获取推荐课程列表
async function fetchRecommendCourses() {
// 优先从缓存加载
const hasCache = loadFromCache()
if (!hasCache) {
// 从网络加载
await loadFromNetwork()
} else {
// 后台静默更新
setTimeout(() => {
loadFromNetwork()
}, 100)
}
}
// 处理卡片点击
const handleCardClick = (recommend) => {
if (!recommend.groupCourse) {
uni.showToast({ title: '课程信息不完整', icon: 'none' })
return
}
const status = recommend.groupCourse.status
if (status === 2) {
uni.showToast({ title: '课程已结束', icon: 'none' })
return
}
if (status === 1) {
uni.showToast({ title: '课程已取消', icon: 'none' })
return
}
const current = recommend.groupCourse.currentMembers || 0
const max = recommend.groupCourse.maxMembers || 0
if (current >= max) {
uni.showToast({ title: '课程已满员', icon: 'none' })
return
}
// 跳转到课程详情页
uni.navigateTo({ url: `/pages/groupCourse/detail?id=${recommend.groupCourse.id}` })
}
onMounted(() => {
fetchRecommendCourses()
})
</script>
<style lang="scss" scoped>
.recommend-page {
min-height: 100vh;
background: #F5F7FA;
}
.courses-container {
height: calc(100vh - 160rpx);
padding: 0;
}
.courses-list {
padding: 0 24rpx;
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
padding: 120rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #8CA0B0;
}
.bottom-placeholder {
height: 40rpx;
}
/* 骨架屏样式 */
.skeleton-container {
padding: 0 24rpx;
}
.skeleton-card {
background: #fff;
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 8rpx 28rpx rgba(45, 74, 90, 0.08);
margin-bottom: 24rpx;
}
.skeleton-img {
width: 100%;
height: 280rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}
.skeleton-text {
height: 32rpx;
margin: 20rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
border-radius: 8rpx;
}
.skeleton-text-short {
height: 24rpx;
margin: 0 20rpx 20rpx;
width: 60%;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
border-radius: 8rpx;
}
@keyframes skeleton-loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>