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

269 lines
6.9 KiB
Vue

<!-- pages/course/index.vue -->
<template>
<!-- 滚动内容区域 -->
<scroll-view scroll-y="false" class="scroll-container">
<view class="tab-page">
<PageHeader title="课程" subtitle="精品团课 · 私教 · 线上课" />
<!-- 骨架屏 -->
<view v-if="loading" class="skeleton-container">
<view class="skeleton-item" v-for="i in 3" :key="i">
<view class="skeleton-img"></view>
<view class="skeleton-text"></view>
</view>
</view>
<!-- 真实内容 -->
<template v-else>
<RecommendCourses :data="courseData" />
<view class="tab-page__actions">
<view class="tab-page__btn" hover-class="tab-page__btn--hover" @tap="goCourseList">
<text class="tab-page__btn-text">预约课程</text>
</view>
<view class="tab-page__btn tab-page__btn--ghost" hover-class="tab-page__btn--hover" @tap="goMyCourses">
<text class="tab-page__btn-text tab-page__btn-text--ghost">我的课程</text>
</view>
</view>
</template>
<view class="bottom-placeholder"></view>
</view>
</scroll-view>
<!-- 固定 TabBar -->
<view class="tabbar-fixed">
<TabBar @update:active="handleTabActive" />
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import RecommendCourses from '@/components/index/RecommendCourses.vue'
import PageHeader from '@/components/index/PageHeader.vue'
import TabBar from '@/components/TabBar.vue'
import { PAGE, navigateToPage } from '@/common/constants/routes.js'
import { getActiveRecommendCourses } from '@/api/groupCourse.js'
const loading = ref(true)
const courseData = ref(null)
// 从缓存加载数据
function loadFromCache() {
try {
const cached = uni.getStorageSync('course_cache')
if (cached && Date.now() - cached.time < 5 * 60 * 1000) {
console.log('[Course Page] 从缓存加载数据,缓存时间:', new Date(cached.time).toLocaleString())
courseData.value = cached.data
loading.value = false
return true
}
} catch (e) {
console.error('[Course Page] 读取缓存失败', e)
}
console.log('[Course Page] 缓存不存在或已过期,准备从网络加载')
return false
}
// 从网络加载数据
async function loadFromNetwork() {
loading.value = true
console.log('[Course Page] 开始从后端获取团课推荐数据...')
try {
// 获取启用的团课推荐列表(按优先级从高到低排序)
console.log('[Course Page] 发起 API 请求: GET /groupCourse/recommend/active')
const res = await getActiveRecommendCourses()
console.log('[Course Page] API 响应数据:', res)
if (res && Array.isArray(res)) {
console.log('[Course Page] 获取到', res.length, '条团课推荐数据')
// 取优先级最高的5个团课
const top5Courses = res.slice(0, 5).map(recommend => ({
id: recommend.groupCourse.id,
courseName: recommend.groupCourse.courseName,
courseType: recommend.groupCourse.courseType,
startTime: recommend.groupCourse.startTime,
endTime: recommend.groupCourse.endTime,
maxMembers: recommend.groupCourse.maxMembers,
currentMembers: recommend.groupCourse.currentMembers,
status: recommend.groupCourse.status,
coverImage: recommend.groupCourse.coverImage,
description: recommend.groupCourse.description,
recommendTitle: recommend.recommendTitle,
recommendContent: recommend.recommendContent,
recommendReason: recommend.recommendReason,
priority: recommend.priority
}))
console.log('[Course Page] 筛选出前5个优先级最高的团课:', top5Courses)
courseData.value = { list: top5Courses }
// 更新缓存
uni.setStorageSync('course_cache', {
data: courseData.value,
time: Date.now()
})
console.log('[Course Page] 数据已缓存')
} else {
console.log('[Course Page] API 响应为空或格式不正确')
courseData.value = { list: [] }
}
} catch (err) {
console.error('[Course Page] 加载失败:', err)
uni.showToast({ title: '加载失败', icon: 'none' })
courseData.value = { list: [] }
} finally {
loading.value = false
console.log('[Course Page] 数据加载完成,loading:', loading.value)
}
}
// 页面激活时刷新数据(可选)
function handleTabActive(index) {
// Tab 切换时后台刷新数据
if (index === 1 && !loading.value) {
loadFromNetwork()
}
}
onLoad(() => {
// 优先显示缓存
const hasCache = loadFromCache()
if (!hasCache) {
loadFromNetwork()
} else {
// 后台静默更新
setTimeout(() => {
loadFromNetwork()
}, 100)
}
})
onShow(() => {
// 每次显示时尝试刷新数据(后台静默更新)
if (!loading.value) {
loadFromNetwork()
}
})
function goCourseList() {
navigateToPage(PAGE.COURSE_LIST)
}
function goMyCourses() {
navigateToPage(PAGE.MY_COURSES)
}
</script>
<style lang="scss" scoped>
.tab-page {
min-height: 100vh;
padding-bottom: 160rpx;
position: relative;
z-index: 2;
background: #F5F7FA;
}
.tab-page__actions {
display: flex;
gap: 20rpx;
padding: 24rpx 32rpx;
}
.tab-page__btn {
flex: 1;
height: 80rpx;
border-radius: $radius-full;
background: $gradient-orange;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $shadow-orange-glow;
transition: all 0.2s ease;
}
.tab-page__btn:active {
transform: scale(0.97);
background: $accent-orange-dark;
}
.tab-page__btn--ghost {
background: $bg-white;
border: 1px solid $border-light;
box-shadow: $shadow-sm;
}
.tab-page__btn-text {
font-size: 28rpx;
font-weight: $font-weight-bold;
color: $text-inverse;
}
.tab-page__btn-text--ghost {
color: $primary-deep;
}
.bottom-placeholder {
height: 40rpx;
}
/* 骨架屏样式 */
.skeleton-container {
padding: 24rpx 32rpx;
}
.skeleton-item {
margin-bottom: 24rpx;
padding: 20rpx;
background: $bg-white;
border-radius: $radius-md;
box-shadow: $shadow-sm;
}
.skeleton-img {
width: 100%;
height: 200rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
border-radius: $radius-sm;
}
.skeleton-text {
height: 32rpx;
margin-top: 16rpx;
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; }
}
/* 滚动容器 */
.scroll-container {
position: relative;
z-index: 1;
height: 100vh;
width: 100%;
}
/* 固定 TabBar */
.tabbar-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: transparent;
}
</style>