hookehuyr

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

在路由配置中添加了课程集合页面的路由,并在用户设置页面中添加了导航到课程集合页面的链接。新增了课程集合页面的视图组件,用于展示课程详情、目录和互动内容。
1 /* 1 /*
2 * @Date: 2025-03-20 20:36:36 2 * @Date: 2025-03-20 20:36:36
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-04-08 13:39:00 4 + * @LastEditTime: 2025-04-08 16:09:21
5 * @FilePath: /mlaj/src/router/routes.js 5 * @FilePath: /mlaj/src/router/routes.js
6 * @Description: 路由地址映射配置 6 * @Description: 路由地址映射配置
7 */ 7 */
...@@ -212,5 +212,12 @@ export const routes = [ ...@@ -212,5 +212,12 @@ export const routes = [
212 title: '学习详情页面', 212 title: '学习详情页面',
213 } 213 }
214 }, 214 },
215 + {
216 + path: '/studyCourse',
217 + component: () => import('@/views/study/studyCoursePage.vue'),
218 + meta: {
219 + title: '课程集合页面',
220 + }
221 + },
215 ...checkinRoutes, 222 ...checkinRoutes,
216 ] 223 ]
......
1 <!-- 1 <!--
2 * @Date: 2025-03-24 13:04:21 2 * @Date: 2025-03-24 13:04:21
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-04-08 11:21:01 4 + * @LastEditTime: 2025-04-08 16:08:24
5 * @FilePath: /mlaj/src/views/profile/SettingsPage.vue 5 * @FilePath: /mlaj/src/views/profile/SettingsPage.vue
6 * @Description: 用户设置页面 6 * @Description: 用户设置页面
7 --> 7 -->
...@@ -86,6 +86,17 @@ ...@@ -86,6 +86,17 @@
86 <ChevronRightIcon class="w-5 h-5 text-gray-400" /> 86 <ChevronRightIcon class="w-5 h-5 text-gray-400" />
87 </div> 87 </div>
88 </div> 88 </div>
89 +
90 + <!-- 课程集合 -->
91 + <div class="p-4" @click="router.push('/studyCourse')">
92 + <div class="flex items-center justify-between">
93 + <div>
94 + <h3 class="text-base font-medium text-gray-900">课程集合</h3>
95 + <p class="text-sm text-gray-500">课程集合</p>
96 + </div>
97 + <ChevronRightIcon class="w-5 h-5 text-gray-400" />
98 + </div>
99 + </div>
89 </div> 100 </div>
90 </FrostedGlass> 101 </FrostedGlass>
91 </div> 102 </div>
......
1 +<!--
2 + * @Date: 2024-01-17
3 + * @Description: 课程详情页面
4 +-->
5 +<template>
6 + <div class="study-course-page bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen">
7 + <div v-if="course" class="flex flex-col h-screen">
8 + <!-- 固定区域:课程封面和标签页 -->
9 + <div class="fixed top-0 left-0 right-0 z-10 top-wrapper bg-white">
10 + <!-- 课程封面区域 -->
11 + <van-image
12 + class="w-full aspect-video object-cover"
13 + :src="course?.coverImage"
14 + :alt="course?.title"
15 + />
16 + <div class="p-4">
17 + <h1 class="text-black text-xl font-bold mb-2">{{ course?.title }}</h1>
18 + <div class="flex items-center text-gray-500 text-sm">
19 + <span>已更新 20期</span>
20 + <span class="mx-2">|</span>
21 + <span>116人订阅</span>
22 + </div>
23 + </div>
24 +
25 + <div class="h-2 bg-gray-100"></div>
26 +
27 + <!-- 标签页区域 -->
28 + <div class="py-3 bg-white">
29 + <van-tabs v-model:active="activeTab" sticky animated swipeable shrink @change="handleTabChange">
30 + <van-tab title="详情" name="detail">
31 + </van-tab>
32 + <van-tab title="目录" name="catalog">
33 + </van-tab>
34 + <van-tab title="课程互动" name="interaction">
35 + </van-tab>
36 + </van-tabs>
37 + </div>
38 + </div>
39 +
40 + <!-- 滚动区域:详情、目录和互动内容 -->
41 + <div class="overflow-y-auto flex-1" :style="{paddingTop: topWrapperHeight, height: 'calc(100vh - ' + topWrapperHeight + ')'}">
42 + <!-- 详情区域 -->
43 + <div id="detail" class="py-4 px-4">
44 + <div class="text-gray-700 text-sm leading-relaxed" v-html="course?.description"></div>
45 + <van-empty description="暂无详情" />
46 + </div>
47 +
48 + <div class="h-2 bg-gray-100"></div>
49 +
50 + <!-- 目录区域 -->
51 + <div id="catalog" class="py-4">
52 + <div class="space-y-4">
53 + <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">
54 + <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>
55 + <div class="text-black text-base font-medium mb-2">{{ lesson.title }}</div>
56 + <div class="flex items-center text-sm text-gray-500">
57 + <span>视频</span>&nbsp;
58 + <span>2024-10-22</span>
59 + <span class="mx-2">|</span>
60 + <span>1897次学习</span>
61 + <span class="mx-2">|</span>
62 + <span>已学习{{ lesson.progress }}%</span>
63 + </div>
64 + </div>
65 + </div>
66 + </div>
67 +
68 + <div class="h-2 bg-gray-100"></div>
69 +
70 + <!-- 互动区域 -->
71 + <div id="interaction" class="py-4 px-4">
72 + <div class="bg-white rounded-lg p-4 mb-4 cursor-pointer">
73 + <div class="flex items-center justify-between">
74 + <div class="flex items-center gap-3">
75 + <van-icon size="3rem" name="calendar-o" class="text-xl text-gray-600" />
76 + <div>
77 + <div class="text-base font-medium">打卡</div>
78 + <div class="text-sm text-gray-500">关联7个打卡</div>
79 + </div>
80 + </div>
81 + <van-icon name="arrow" class="text-gray-400" />
82 + </div>
83 + </div>
84 + </div>
85 + </div>
86 + </div>
87 + </div>
88 +</template>
89 +
90 +<script setup>
91 +import { ref, onMounted, nextTick, onUnmounted } from 'vue';
92 +import { useTitle } from '@vueuse/core';
93 +
94 +// 页面标题
95 +useTitle('课程详情');
96 +
97 +// 当前激活的标签页
98 +const activeTab = ref('detail');
99 +const topWrapperHeight = ref(0);
100 +
101 +// 处理滚动事件
102 +const handleScroll = () => {
103 + const detailElement = document.getElementById('detail');
104 + const catalogElement = document.getElementById('catalog');
105 + const interactionElement = document.getElementById('interaction');
106 + if (!detailElement || !catalogElement || !interactionElement) return;
107 +
108 + const scrollTop = window.scrollY;
109 + const catalogOffset = catalogElement.offsetTop - parseInt(topWrapperHeight.value);
110 + const interactionOffset = interactionElement.offsetTop - parseInt(topWrapperHeight.value);
111 +
112 + // 根据滚动位置更新activeTab
113 + if (scrollTop >= interactionOffset) {
114 + activeTab.value = 'interaction';
115 + } else if (scrollTop >= catalogOffset) {
116 + activeTab.value = 'catalog';
117 + } else {
118 + activeTab.value = 'detail';
119 + }
120 +};
121 +
122 +// 处理标签页切换
123 +const handleTabChange = (name) => {
124 + nextTick(() => {
125 + const element = document.getElementById(name);
126 + if (element) {
127 + const topOffset = element.offsetTop - parseInt(topWrapperHeight.value);
128 + window.scrollTo({
129 + top: topOffset,
130 + behavior: 'smooth'
131 + });
132 + }
133 + });
134 +};
135 +
136 +onMounted(() => {
137 + nextTick(() => {
138 + const topWrapper = document.querySelector('.top-wrapper');
139 + if (topWrapper) {
140 + topWrapperHeight.value = topWrapper.clientHeight + 'px';
141 + }
142 +
143 + // 添加滚动监听
144 + window.addEventListener('scroll', handleScroll);
145 + });
146 +});
147 +
148 +// 在组件卸载时移除滚动监听
149 +onUnmounted(() => {
150 + window.removeEventListener('scroll', handleScroll);
151 +});
152 +
153 +// 课程数据
154 +const course = ref({
155 + title: '开学礼·止的智慧·心法老师·20241001',
156 + coverImage: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
157 + updateTime: '2024.01.17',
158 + viewCount: 1897,
159 + description: '这是一门关于心法的课程,帮助学员掌握止的智慧...',
160 + lessons: [
161 + {
162 + title: '第一课:止的基础',
163 + duration: '45分钟',
164 + progress: 100
165 + },
166 + {
167 + title: '第二课:止的技巧',
168 + duration: '50分钟',
169 + progress: 60
170 + },
171 + {
172 + title: '第三课:止的应用',
173 + duration: '40分钟',
174 + progress: 0
175 + }
176 + ]
177 +});
178 +</script>
179 +
180 +<style scoped>
181 +.study-course-page {
182 + min-height: 100vh;
183 +}
184 +
185 +.course-header {
186 + height: auto;
187 +}
188 +
189 +.course-tabs {
190 + background-color: #fff;
191 +}
192 +
193 +.course-catalog {
194 + background-color: #fff;
195 +}
196 +</style>