完善团课推荐页面
This commit is contained in:
@@ -8,25 +8,30 @@
|
||||
<!-- 骨架屏 -->
|
||||
<view v-if="loading" class="skeleton-container">
|
||||
<view class="skeleton-card" v-for="i in 3" :key="i">
|
||||
<view class="skeleton-img"></view>
|
||||
<view class="skeleton-text"></view>
|
||||
<view class="skeleton-text-short"></view>
|
||||
<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-grid">
|
||||
<CourseCard
|
||||
v-for="course in courses"
|
||||
:key="course.id"
|
||||
:course="course"
|
||||
@join="handleJoinCourse"
|
||||
<view class="courses-list">
|
||||
<RecommendCourseCard
|
||||
v-for="recommend in recommendCourses"
|
||||
:key="recommend.id"
|
||||
:recommend="recommend"
|
||||
@click="handleCardClick(recommend)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!loading && courses.length === 0" class="empty-state">
|
||||
<view v-if="!loading && recommendCourses.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无推荐课程</text>
|
||||
</view>
|
||||
|
||||
@@ -38,123 +43,108 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import CourseCard from '@/components/index/CourseCard.vue'
|
||||
import PageHeader from '@/components/index/PageHeader.vue'
|
||||
import { groupCourseMockApi } from '@/request_api/groupCourse.mock.js'
|
||||
import RecommendCourseCard from '@/components/recommendCourses/RecommendCourseCard.vue'
|
||||
import { getGroupCourseRecommendList } from '@/api/groupCourse.js'
|
||||
|
||||
// 课程列表数据
|
||||
const courses = ref([])
|
||||
// 推荐课程列表数据
|
||||
const recommendCourses = ref([])
|
||||
// 加载状态
|
||||
const loading = ref(true)
|
||||
|
||||
// 课程类型映射(用于显示标签)
|
||||
const getCourseTypeName = (type) => {
|
||||
const typeMap = {
|
||||
'1': '瑜伽',
|
||||
'2': '搏击',
|
||||
'3': '塑形'
|
||||
// 从缓存加载数据
|
||||
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 typeMap[type] || '课程'
|
||||
return false
|
||||
}
|
||||
|
||||
// 根据课程信息获取标签文本
|
||||
const getTag = (course) => {
|
||||
if (course.currentMembers >= course.maxMembers) return '已满员'
|
||||
if (course.status === '2') return '已结束'
|
||||
if (course.status === '1') 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.status === '1') 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 transformCourseData = (course) => {
|
||||
return {
|
||||
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 fetchRecommendCourses = async () => {
|
||||
// 从网络加载数据
|
||||
async function loadFromNetwork() {
|
||||
loading.value = true
|
||||
console.log('[Recommend Courses Page] 开始从后端获取团课推荐列表...')
|
||||
|
||||
try {
|
||||
// 使用 mock API 获取数据
|
||||
const res = await groupCourseMockApi.getList({
|
||||
pageNum: 1,
|
||||
pageSize: 20
|
||||
// 调用获取所有团课推荐接口
|
||||
console.log('[Recommend Courses Page] 发起 API 请求: GET /groupCourse/recommend/list')
|
||||
const res = await getGroupCourseRecommendList({
|
||||
sortBy: 'priority',
|
||||
sortOrder: 'desc'
|
||||
})
|
||||
|
||||
if (res && res.data && res.data.list) {
|
||||
// 按参与人数排序,推荐热门课程
|
||||
const sortedList = res.data.list.sort((a, b) => b.currentMembers - a.currentMembers)
|
||||
courses.value = sortedList.map(course => transformCourseData(course))
|
||||
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('获取推荐课程失败', 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理参与课程点击
|
||||
const handleJoinCourse = (course) => {
|
||||
if (course.rawData.status === '2') {
|
||||
// 获取推荐课程列表
|
||||
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 (course.rawData.status === '1') {
|
||||
if (status === 1) {
|
||||
uni.showToast({ title: '课程已取消', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (course.rawData.currentMembers >= course.rawData.maxMembers) {
|
||||
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=${course.id}` })
|
||||
uni.navigateTo({ url: `/pages/groupCourse/detail?id=${recommend.groupCourse.id}` })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -165,25 +155,16 @@ onMounted(() => {
|
||||
<style lang="scss" scoped>
|
||||
.recommend-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #E3F2FD 0%, #F5F5F5 100%);
|
||||
background: #F5F7FA;
|
||||
}
|
||||
|
||||
.courses-container {
|
||||
height: calc(100vh - 160rpx);
|
||||
padding: 32rpx 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.courses-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24rpx;
|
||||
justify-content: center;
|
||||
|
||||
// 两列网格布局,适度缩小卡片宽度
|
||||
> * {
|
||||
flex: 0 0 calc(45% - 12rpx);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.courses-list {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
@@ -204,18 +185,15 @@ onMounted(() => {
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 32rpx 24rpx;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.skeleton-card {
|
||||
flex: 0 0 calc(50% - 10rpx);
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 28rpx rgba(45, 74, 90, 0.08);
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.skeleton-img {
|
||||
|
||||
Reference in New Issue
Block a user