|
|
@@ -1,290 +0,0 @@
|
|
|
-<template>
|
|
|
- <div class="course-detail-page" v-if="currentCourse">
|
|
|
- <!-- <el-page-header @back="goBack">
|
|
|
- <template #content>{{ currentCourse.title }}</template>
|
|
|
- </el-page-header> -->
|
|
|
- <Breadcrumb />
|
|
|
-
|
|
|
- <div class="course-detail">
|
|
|
- <div class="course-main">
|
|
|
- <div class="course-video">
|
|
|
- <!-- 视频播放器占位 -->
|
|
|
- <div class="video-player">
|
|
|
- <VideoPlayer
|
|
|
- ref="videoPlayer"
|
|
|
- :src="currentVideoUrl"
|
|
|
- :poster="currentCourse.cover"
|
|
|
- @play="onPlayerPlay"
|
|
|
- @pause="onPlayerPause"
|
|
|
- @ended="onPlayerEnded"
|
|
|
- @timeupdate="onPlayerTimeupdate"
|
|
|
- @loadedmetadata="onPlayerLoadedmetadata"
|
|
|
- @error="onPlayerError"
|
|
|
- @ready="onPlayerReady"
|
|
|
- />
|
|
|
- <!-- <img :src="currentCourse.cover" :alt="currentCourse.title">
|
|
|
- <div class="play-button">▶</div> -->
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="video-info">
|
|
|
- <h2>{{ currentCourse.title }}</h2>
|
|
|
- <div class="course-meta">
|
|
|
- <span>讲师:{{ currentCourse.teacher }}</span>
|
|
|
- <span>价格:¥{{ currentCourse.price }}</span>
|
|
|
- </div>
|
|
|
- <p class="course-desc">{{ currentCourse.description }}</p>
|
|
|
- <el-button type="primary" size="large">立即学习</el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="course-chapters">
|
|
|
- <h3>课程章节</h3>
|
|
|
- <el-collapse>
|
|
|
- <el-collapse-item
|
|
|
- v-for="chapter in currentCourseChapters"
|
|
|
- :key="chapter.id"
|
|
|
- :title="chapter.title"
|
|
|
- @click="playVideo(video)"
|
|
|
- >
|
|
|
- <div
|
|
|
- v-for="video in chapter.videos"
|
|
|
- :key="video.id"
|
|
|
- class="chapter-video"
|
|
|
- >
|
|
|
- <el-icon><VideoPlay /></el-icon>
|
|
|
- {{ video.title }} ({{ video.duration }})
|
|
|
- </div>
|
|
|
- </el-collapse-item>
|
|
|
- </el-collapse>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup>
|
|
|
-import { onMounted,ref } from 'vue'
|
|
|
-import { useRoute, useRouter } from 'vue-router'
|
|
|
-import { useCourseStore } from '@/pinia/courseStore'
|
|
|
-import { VideoPlay } from '@element-plus/icons-vue'
|
|
|
-import VideoPlayer from '@/components/VideoPlayer.vue'
|
|
|
-import DGTMessage from '@/utils/message'
|
|
|
-
|
|
|
-const route = useRoute()
|
|
|
-const router = useRouter()
|
|
|
-const courseStore = useCourseStore()
|
|
|
-
|
|
|
-const courseId = route.params.id
|
|
|
-const currentCourse = ref(null)
|
|
|
-const currentCourseChapters = ref(null)
|
|
|
-
|
|
|
-const videoPlayer = ref(null)
|
|
|
-// const currentVideoUrl = ref('')
|
|
|
-// const currentVideoUrl = ref('http://jcxxpt.oss-cn-beijing.aliyuncs.com/common/2025/12/19/actmOvmq0xOBc8448561c73a066a523821c6f7ae4868_20251219094240A008.mp4')
|
|
|
-// const currentVideoUrl = ref('http://baomiai.oss-cn-shanghai.aliyuncs.com/video/2025/12/30/123_20251226133117A047_20251230095248A001.mp4?response-content-disposition=inline&response-content-type=video%2Fmp4&x-oss-date=20251230T015258Z&x-oss-expires=7200&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5tG5dEtkqwDGcU6AtaYE%2F20251230%2Fcn-shanghai%2Foss%2Faliyun_v4_reque')
|
|
|
-const currentVideoUrl = ref('http://baomiai.oss-cn-shanghai.aliyuncs.com/video/2025/12/30/123_20251226133117A047_20251230095248A001.mp4?response-content-disposition=inline&x-oss-date=20251230T015720Z&x-oss-expires=7199&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5tG5dEtkqwDGcU6AtaYE%2F20251230%2Fcn-shanghai%2Foss%2Faliyun_v4_request&x-oss-signature=f93c20421dcfdc6822cd03d42ae66f8a28acd2c4d25c79ce16e2312537eabaa5')
|
|
|
-const currentPlayingVideoId = ref(null)
|
|
|
-const currentVideoDuration = ref(0)
|
|
|
-const currentPlayTime = ref(0)
|
|
|
-
|
|
|
-
|
|
|
-onMounted(() => {
|
|
|
- courseStore.fetchCourseDetail(courseId)
|
|
|
- courseStore.fetchCourseChapters(courseId)
|
|
|
-
|
|
|
- currentCourse.value = courseStore.currentCourse
|
|
|
- currentCourseChapters.value = courseStore.currentCourseChapters
|
|
|
-})
|
|
|
-
|
|
|
-const goBack = () => {
|
|
|
- router.back()
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-// 播放指定视频
|
|
|
-const playVideo = (video) => {
|
|
|
- if (!video) return
|
|
|
-
|
|
|
- // 更新当前播放的视频ID
|
|
|
- currentPlayingVideoId.value = video.id
|
|
|
-
|
|
|
- // 这里可以根据video.id从API获取实际的视频地址
|
|
|
- // 暂时使用模拟地址
|
|
|
- const videoUrl = `https://example.com/videos/${video.id}.mp4`
|
|
|
-
|
|
|
- // 更新视频源
|
|
|
- currentVideoUrl.value = videoUrl
|
|
|
-
|
|
|
- // 播放视频
|
|
|
- if (videoPlayer.value) {
|
|
|
- videoPlayer.value.play()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 播放器事件处理
|
|
|
-const onPlayerPlay = () => {
|
|
|
- console.log('视频开始播放')
|
|
|
-}
|
|
|
-
|
|
|
-const onPlayerPause = () => {
|
|
|
- console.log('视频暂停')
|
|
|
-}
|
|
|
-
|
|
|
-const onPlayerEnded = () => {
|
|
|
- console.log('视频播放结束')
|
|
|
-
|
|
|
- // 自动播放下一个视频
|
|
|
- playNextVideo()
|
|
|
-}
|
|
|
-
|
|
|
-const onPlayerTimeupdate = (time) => {
|
|
|
- currentPlayTime.value = time
|
|
|
- // 这里可以保存播放进度
|
|
|
- console.log('当前播放时间:', time)
|
|
|
-}
|
|
|
-
|
|
|
-const onPlayerLoadedmetadata = (duration) => {
|
|
|
- currentVideoDuration.value = duration
|
|
|
- console.log('视频时长:', duration)
|
|
|
-}
|
|
|
-
|
|
|
-const onPlayerError = (error) => {
|
|
|
- console.error('视频播放错误:', error)
|
|
|
- DGTMessage.error('视频播放失败,请稍后再试')
|
|
|
-}
|
|
|
-
|
|
|
-const onPlayerReady = (player) => {
|
|
|
- console.log('播放器就绪', player)
|
|
|
- // 可以在这里进行高级操作
|
|
|
-}
|
|
|
-
|
|
|
-// 播放下一个视频
|
|
|
-const playNextVideo = () => {
|
|
|
- if (!currentCourseChapters.value || currentPlayingVideoId.value === null) return
|
|
|
-
|
|
|
- // 查找当前播放视频的位置
|
|
|
- let currentIndex = -1
|
|
|
- let chapterIndex = -1
|
|
|
-
|
|
|
- for (let i = 0; i < currentCourseChapters.value.length; i++) {
|
|
|
- const chapter = currentCourseChapters.value[i]
|
|
|
- const index = chapter.videos.findIndex(v => v.id === currentPlayingVideoId.value)
|
|
|
-
|
|
|
- if (index !== -1) {
|
|
|
- chapterIndex = i
|
|
|
- currentIndex = index
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 如果找到当前视频
|
|
|
- if (chapterIndex !== -1 && currentIndex !== -1) {
|
|
|
- const currentChapter = currentCourseChapters.value[chapterIndex]
|
|
|
-
|
|
|
- // 如果当前章节还有下一个视频
|
|
|
- if (currentIndex < currentChapter.videos.length - 1) {
|
|
|
- playVideo(currentChapter.videos[currentIndex + 1])
|
|
|
- }
|
|
|
- // 如果是当前章节最后一个视频,且有下一个章节
|
|
|
- else if (chapterIndex < currentCourseChapters.value.length - 1) {
|
|
|
- playVideo(currentCourseChapters.value[chapterIndex + 1].videos[0])
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 格式化时间
|
|
|
-const formatTime = (seconds) => {
|
|
|
- if (!seconds || isNaN(seconds)) return '00:00'
|
|
|
-
|
|
|
- const minutes = Math.floor(seconds / 60)
|
|
|
- const secs = Math.floor(seconds % 60)
|
|
|
- return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
|
|
-}
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped lang="scss">
|
|
|
-
|
|
|
-.course-detail {
|
|
|
- .course-video {
|
|
|
- display: flex;
|
|
|
- gap: 20px;
|
|
|
- margin-bottom: 40px;
|
|
|
- // height: 500px; /* 设置容器高度,可根据需要调整 */
|
|
|
-
|
|
|
- @media (max-width: 768px) {
|
|
|
- flex-direction: column;
|
|
|
- }
|
|
|
-
|
|
|
- .video-player {
|
|
|
- flex: 2;
|
|
|
- position: relative;
|
|
|
- // height: 500px;
|
|
|
-
|
|
|
- img {
|
|
|
- width: 100%;
|
|
|
- height: auto;
|
|
|
- border-radius: 8px;
|
|
|
- }
|
|
|
-
|
|
|
- .play-button {
|
|
|
- position: absolute;
|
|
|
- top: 50%;
|
|
|
- left: 50%;
|
|
|
- transform: translate(-50%, -50%);
|
|
|
- width: 80px;
|
|
|
- height: 80px;
|
|
|
- background: rgba(0,0,0,0.5);
|
|
|
- color: white;
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- font-size: 30px;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .video-info {
|
|
|
- flex: 1;
|
|
|
-
|
|
|
- h2 {
|
|
|
- font-size: 1.8rem;
|
|
|
- margin-bottom: 15px;
|
|
|
- }
|
|
|
-
|
|
|
- .course-meta {
|
|
|
- margin-bottom: 15px;
|
|
|
- color: #666;
|
|
|
-
|
|
|
- span {
|
|
|
- display: block;
|
|
|
- margin-bottom: 5px;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .course-desc {
|
|
|
- margin-bottom: 20px;
|
|
|
- line-height: 1.6;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .course-chapters {
|
|
|
- h3 {
|
|
|
- font-size: 1.5rem;
|
|
|
- margin-bottom: 15px;
|
|
|
- }
|
|
|
-
|
|
|
- .chapter-video {
|
|
|
- padding: 10px;
|
|
|
- border-bottom: 1px solid #eee;
|
|
|
- cursor: pointer;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background-color: #f5f5f5;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-</style>
|