hookehuyr

feat(router): 添加课程集合页面路由和导航

在路由配置中添加了课程集合页面的路由,并在用户设置页面中添加了导航到课程集合页面的链接。新增了课程集合页面的视图组件,用于展示课程详情、目录和互动内容。
/*
* @Date: 2025-03-20 20:36:36
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-08 13:39:00
* @LastEditTime: 2025-04-08 16:09:21
* @FilePath: /mlaj/src/router/routes.js
* @Description: 路由地址映射配置
*/
......@@ -212,5 +212,12 @@ export const routes = [
title: '学习详情页面',
}
},
{
path: '/studyCourse',
component: () => import('@/views/study/studyCoursePage.vue'),
meta: {
title: '课程集合页面',
}
},
...checkinRoutes,
]
......
<!--
* @Date: 2025-03-24 13:04:21
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-08 11:21:01
* @LastEditTime: 2025-04-08 16:08:24
* @FilePath: /mlaj/src/views/profile/SettingsPage.vue
* @Description: 用户设置页面
-->
......@@ -86,6 +86,17 @@
<ChevronRightIcon class="w-5 h-5 text-gray-400" />
</div>
</div>
<!-- 课程集合 -->
<div class="p-4" @click="router.push('/studyCourse')">
<div class="flex items-center justify-between">
<div>
<h3 class="text-base font-medium text-gray-900">课程集合</h3>
<p class="text-sm text-gray-500">课程集合</p>
</div>
<ChevronRightIcon class="w-5 h-5 text-gray-400" />
</div>
</div>
</div>
</FrostedGlass>
</div>
......
<!--
* @Date: 2024-01-17
* @Description: 课程详情页面
-->
<template>
<div class="study-course-page bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen">
<div v-if="course" class="flex flex-col h-screen">
<!-- 固定区域:课程封面和标签页 -->
<div class="fixed top-0 left-0 right-0 z-10 top-wrapper bg-white">
<!-- 课程封面区域 -->
<van-image
class="w-full aspect-video object-cover"
:src="course?.coverImage"
:alt="course?.title"
/>
<div class="p-4">
<h1 class="text-black text-xl font-bold mb-2">{{ course?.title }}</h1>
<div class="flex items-center text-gray-500 text-sm">
<span>已更新 20期</span>
<span class="mx-2">|</span>
<span>116人订阅</span>
</div>
</div>
<div class="h-2 bg-gray-100"></div>
<!-- 标签页区域 -->
<div class="py-3 bg-white">
<van-tabs v-model:active="activeTab" sticky animated swipeable shrink @change="handleTabChange">
<van-tab title="详情" name="detail">
</van-tab>
<van-tab title="目录" name="catalog">
</van-tab>
<van-tab title="课程互动" name="interaction">
</van-tab>
</van-tabs>
</div>
</div>
<!-- 滚动区域:详情、目录和互动内容 -->
<div class="overflow-y-auto flex-1" :style="{paddingTop: topWrapperHeight, height: 'calc(100vh - ' + topWrapperHeight + ')'}">
<!-- 详情区域 -->
<div id="detail" class="py-4 px-4">
<div class="text-gray-700 text-sm leading-relaxed" v-html="course?.description"></div>
<van-empty description="暂无详情" />
</div>
<div class="h-2 bg-gray-100"></div>
<!-- 目录区域 -->
<div id="catalog" class="py-4">
<div class="space-y-4">
<div v-for="(lesson, index) in course?.lessons" :key="index" class="bg-white p-4 cursor-pointer hover:bg-gray-50 transition-colors border-b border-gray-200 relative">
<div v-if="lesson.progress > 0 && lesson.progress < 100" class="absolute top-2 right-2 px-2 py-1 bg-green-100 text-green-600 text-xs rounded">上次看到</div>
<div class="text-black text-base font-medium mb-2">{{ lesson.title }}</div>
<div class="flex items-center text-sm text-gray-500">
<span>视频</span>&nbsp;
<span>2024-10-22</span>
<span class="mx-2">|</span>
<span>1897次学习</span>
<span class="mx-2">|</span>
<span>已学习{{ lesson.progress }}%</span>
</div>
</div>
</div>
</div>
<div class="h-2 bg-gray-100"></div>
<!-- 互动区域 -->
<div id="interaction" class="py-4 px-4">
<div class="bg-white rounded-lg p-4 mb-4 cursor-pointer">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<van-icon size="3rem" name="calendar-o" class="text-xl text-gray-600" />
<div>
<div class="text-base font-medium">打卡</div>
<div class="text-sm text-gray-500">关联7个打卡</div>
</div>
</div>
<van-icon name="arrow" class="text-gray-400" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick, onUnmounted } from 'vue';
import { useTitle } from '@vueuse/core';
// 页面标题
useTitle('课程详情');
// 当前激活的标签页
const activeTab = ref('detail');
const topWrapperHeight = ref(0);
// 处理滚动事件
const handleScroll = () => {
const detailElement = document.getElementById('detail');
const catalogElement = document.getElementById('catalog');
const interactionElement = document.getElementById('interaction');
if (!detailElement || !catalogElement || !interactionElement) return;
const scrollTop = window.scrollY;
const catalogOffset = catalogElement.offsetTop - parseInt(topWrapperHeight.value);
const interactionOffset = interactionElement.offsetTop - parseInt(topWrapperHeight.value);
// 根据滚动位置更新activeTab
if (scrollTop >= interactionOffset) {
activeTab.value = 'interaction';
} else if (scrollTop >= catalogOffset) {
activeTab.value = 'catalog';
} else {
activeTab.value = 'detail';
}
};
// 处理标签页切换
const handleTabChange = (name) => {
nextTick(() => {
const element = document.getElementById(name);
if (element) {
const topOffset = element.offsetTop - parseInt(topWrapperHeight.value);
window.scrollTo({
top: topOffset,
behavior: 'smooth'
});
}
});
};
onMounted(() => {
nextTick(() => {
const topWrapper = document.querySelector('.top-wrapper');
if (topWrapper) {
topWrapperHeight.value = topWrapper.clientHeight + 'px';
}
// 添加滚动监听
window.addEventListener('scroll', handleScroll);
});
});
// 在组件卸载时移除滚动监听
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll);
});
// 课程数据
const course = ref({
title: '开学礼·止的智慧·心法老师·20241001',
coverImage: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
updateTime: '2024.01.17',
viewCount: 1897,
description: '这是一门关于心法的课程,帮助学员掌握止的智慧...',
lessons: [
{
title: '第一课:止的基础',
duration: '45分钟',
progress: 100
},
{
title: '第二课:止的技巧',
duration: '50分钟',
progress: 60
},
{
title: '第三课:止的应用',
duration: '40分钟',
progress: 0
}
]
});
</script>
<style scoped>
.study-course-page {
min-height: 100vh;
}
.course-header {
height: auto;
}
.course-tabs {
background-color: #fff;
}
.course-catalog {
background-color: #fff;
}
</style>