feat(StudyDetailPage): 添加课程目录弹出层及课程切换功能
在StudyDetailPage页面中新增课程目录弹出层,支持用户查看课程目录并切换课程。点击目录项时,更新URL并重新获取课程数据,确保页面内容与所选课程一致。同时优化了课程数据的加载逻辑,提升用户体验。
Showing
1 changed file
with
81 additions
and
2 deletions
| ... | @@ -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 | ... | ... |
-
Please register or login to post a comment