hookehuyr

feat(StudyDetailPage): 添加课程目录弹出层及课程切换功能

在StudyDetailPage页面中新增课程目录弹出层,支持用户查看课程目录并切换课程。点击目录项时,更新URL并重新获取课程数据,确保页面内容与所选课程一致。同时优化了课程数据的加载逻辑,提升用户体验。
...@@ -156,12 +156,45 @@ ...@@ -156,12 +156,45 @@
156 <van-button type="primary" size="small" @click="submitComment">发送</van-button> 156 <van-button type="primary" size="small" @click="submitComment">发送</van-button>
157 </div> 157 </div>
158 </div> 158 </div>
159 +
160 + <!-- 课程目录弹出层 -->
161 + <van-popup v-model:show="showCatalog" position="bottom" round closeable safe-area-inset-bottom
162 + style="height: 80%">
163 + <div class="flex flex-col h-full">
164 + <!-- 固定头部 -->
165 + <div class="flex-none px-4 py-3 border-b bg-white sticky top-0 z-10">
166 + <div class="text-lg font-medium">课程目录</div>
167 + </div>
168 +
169 + <!-- 可滚动的目录列表 -->
170 + <div class="flex-1 overflow-y-auto px-4 py-2">
171 + <div v-if="course_lessons.length" class="space-y-4">
172 + <div v-for="(lesson, index) in course_lessons" :key="index"
173 + @click="handleLessonClick(lesson)"
174 + class="bg-white p-4 cursor-pointer hover:bg-gray-50 transition-colors border-b border-gray-200 relative">
175 + <div v-if="lesson.progress > 0 && lesson.progress < 100"
176 + class="absolute top-2 right-2 px-2 py-1 bg-green-100 text-green-600 text-xs rounded">
177 + 上次看到</div>
178 + <div class="text-black text-base font-medium mb-2">{{ lesson.title }}</div>
179 + <div class="flex items-center text-sm text-gray-500">
180 + <span>{{ course_type_maps[lesson.course_type] }}</span>
181 + <span v-if="course_type_maps[lesson.course_type]" class="mx-2">|</span>
182 + <span>开课时间: {{ dayjs(lesson.schedule_time).format('YYYY-MM-DD') }}</span>
183 + <span class="mx-2">|</span>
184 + <span>课程时长: {{ lesson.duration }} 分钟</span>
185 + </div>
186 + </div>
187 + </div>
188 + <van-empty v-else description="暂无目录" />
189 + </div>
190 + </div>
191 + </van-popup>
159 </div> 192 </div>
160 </template> 193 </template>
161 194
162 <script setup> 195 <script setup>
163 import { ref, onMounted, nextTick } from 'vue'; 196 import { ref, onMounted, nextTick } from 'vue';
164 -import { useRoute } from 'vue-router'; 197 +import { useRoute, useRouter } from 'vue-router';
165 import { useTitle } from '@vueuse/core'; 198 import { useTitle } from '@vueuse/core';
166 import VideoPlayer from '@/components/ui/VideoPlayer.vue'; 199 import VideoPlayer from '@/components/ui/VideoPlayer.vue';
167 import AudioPlayer from '@/components/ui/AudioPlayer.vue'; 200 import AudioPlayer from '@/components/ui/AudioPlayer.vue';
...@@ -169,10 +202,12 @@ import dayjs from 'dayjs'; ...@@ -169,10 +202,12 @@ import dayjs from 'dayjs';
169 import { formatDate } from '@/utils/tools' 202 import { formatDate } from '@/utils/tools'
170 203
171 // 导入接口 204 // 导入接口
172 -import { getScheduleCourseAPI, getGroupCommentListAPI, addGroupCommentAPI, addGroupCommentLikeAPI, delGroupCommentLikeAPI } from '@/api/course'; 205 +import { getScheduleCourseAPI, getGroupCommentListAPI, addGroupCommentAPI, addGroupCommentLikeAPI, delGroupCommentLikeAPI, getCourseDetailAPI } from '@/api/course';
173 206
174 const route = useRoute(); 207 const route = useRoute();
208 +const router = useRouter();
175 const course = ref(null); 209 const course = ref(null);
210 +
176 const activeTab = ref('intro'); 211 const activeTab = ref('intro');
177 const newComment = ref(''); 212 const newComment = ref('');
178 const showCatalog = ref(false); 213 const showCatalog = ref(false);
...@@ -181,6 +216,15 @@ const videoPlayerRef = ref(null); ...@@ -181,6 +216,15 @@ const videoPlayerRef = ref(null);
181 const showCommentPopup = ref(false); 216 const showCommentPopup = ref(false);
182 const popupComment = ref(''); 217 const popupComment = ref('');
183 218
219 +// 课程目录相关
220 +const course_lessons = ref([]);
221 +const course_type_maps = ref({
222 + video: '视频',
223 + audio: '音频',
224 + image: '图片',
225 + file: '文件',
226 +});
227 +
184 // 开始播放视频 228 // 开始播放视频
185 const startPlay = async () => { 229 const startPlay = async () => {
186 isPlaying.value = true; 230 isPlaying.value = true;
...@@ -299,6 +343,34 @@ const commentCount = ref(0); ...@@ -299,6 +343,34 @@ const commentCount = ref(0);
299 const commentList = ref([]); 343 const commentList = ref([]);
300 const courseFile = ref({}); 344 const courseFile = ref({});
301 345
346 +// 处理课程切换
347 +const handleLessonClick = async (lesson) => {
348 + showCatalog.value = false; // 关闭目录弹窗
349 + isPlaying.value = false; // 重置播放状态
350 +
351 + // 更新URL地址,不触发页面重新加载
352 + router.replace({ params: { id: lesson.id } });
353 +
354 + // 重新获取课程数据
355 + const { code, data } = await getScheduleCourseAPI({ i: lesson.id });
356 + if (code) {
357 + course.value = data;
358 + courseFile.value = data.file;
359 +
360 + // 音频列表处理
361 + if (data.course_type === 'audio') {
362 + audioList.value = [data.file];
363 + }
364 +
365 + // 获取评论列表
366 + const comment = await getGroupCommentListAPI({ group_id: data.group_id, schedule_id: data.id });
367 + if (comment.code) {
368 + commentList.value = comment.data.comment_list;
369 + commentCount.value = comment.data.comment_count;
370 + }
371 + }
372 +};
373 +
302 onMounted(async () => { 374 onMounted(async () => {
303 // 延迟设置topWrapper和bottomWrapper的高度 375 // 延迟设置topWrapper和bottomWrapper的高度
304 setTimeout(() => { 376 setTimeout(() => {
...@@ -334,7 +406,14 @@ onMounted(async () => { ...@@ -334,7 +406,14 @@ onMounted(async () => {
334 commentList.value = comment.data.comment_list; 406 commentList.value = comment.data.comment_list;
335 commentCount.value = comment.data.comment_count; 407 commentCount.value = comment.data.comment_count;
336 } 408 }
409 +
410 + // 获取课程目录
411 + const detail = await getCourseDetailAPI({ i: course.value.group_id });
412 + if (detail.code) {
413 + course_lessons.value = detail.data.schedule || [];
414 + }
337 } 415 }
416 +
338 } 417 }
339 }) 418 })
340 419
......