Prechádzať zdrojové kódy

```
fix(LearningSystem): 修复学习系统详情页面组件显示和路由问题

- 修复BlockNoteEditorDialog组件默认隐藏问题
- 修改VideoPlayer组件默认不自动播放
- 优化LearningSystemDetail页面结构,添加子路由判断逻辑
- 更新课程目录组件样式,添加章节计数显示
- 重命名OtherCourse组件替换原Right组件
- 添加课程详情子路由配置
- 新增fit_content样式类
- 修复学习笔记组件样式和交互问题
```

zhangningning 1 mesiac pred
rodič
commit
d9e7de28f8

BIN
src/assets/imgs/fenxiang.png


BIN
src/assets/imgs/shoucang.png


BIN
src/assets/imgs/weishoucang.png


+ 1 - 1
src/components/BlockNoteEditorDialog.vue

@@ -30,7 +30,7 @@ const { t } = useI18n()
 
 
 
-const dialogVisible = ref(true)
+const dialogVisible = ref(false)
 const title = ref(t('common.add'));
 // 编辑器内容
 const editorContent = ref(null);

+ 1 - 1
src/components/VideoPlayer.vue

@@ -46,7 +46,7 @@ const props = defineProps({
   // 是否自动播放
   autoplay: {
     type: Boolean,
-    default: true
+    default: false
   },
   // 是否显示控件
   controls: {

+ 299 - 0
src/pages/LearningSystem/CourseDetail.vue

@@ -0,0 +1,299 @@
+<template>
+  <div class="CourseDetail container-height">
+    <Breadcrumb />
+    <div>
+      <div class="flex_1 bg_color_fff padding16 border_radius_16 box_shadow_card fit_content">
+        <div class="gap10">
+          <el-button type="primary">标签</el-button>
+          <div class="bold font_size30">UI设计求职实战班(第1期)</div>
+        </div>
+        <div class="gap5 mt10">
+          <img :src="riliIcon" alt="" style="width: 16px; height: 16px;">
+          <span class="font_size14">2023-10-10 00:00:00</span>
+        </div>
+      </div>
+      <div class="flex-between mt20">
+        <div class="flex_1">
+          <div class="video-player mr20">
+            <VideoPlayer 
+              ref="videoPlayer"
+              :src="currentVideoUrl"
+              :poster="currentCourseCover"
+              @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="bg_color_fff padding16 border_radius_16 box_shadow_card mt20">
+            <div class="flex-center-between border_bottom">
+              <div  class="gap10">
+                <div class="gap5">
+                  <img :src="weishoucangIcon" alt="" style="width: 24px; height: 24px;">
+                  <span class="bold font_size14">未收藏</span>
+                </div>
+                <div class="gap5">
+                  <img :src="shoucangIcon" alt="" style="width: 24px; height: 24px;">
+                  <span class="bold font_size14">已收藏</span>
+                </div>
+                <div class="gap5">
+                  <img :src="fenxiangIcon" alt="" style="width: 24px; height: 24px;">
+                  <span class="bold font_size14">分享</span>
+                </div>
+              </div>
+              <div class="addBtn flex-center gradient border_radius_10">
+                <div class="gap10" @click="openAddDialog">
+                  <img :src="addIcon" alt="" style="width:30px;height:30px">
+                  <span class="font_size18">{{$t('common.add')}}{{$t('common.xuxibiji')}}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="bg_color_fff padding16 border_radius_16 box_shadow_card mt20">
+            <Pinglun :info="info"/>
+          </div>
+        </div>
+        <div class="detail_right">
+          <div class="bg_color_fff padding16 border_radius_16 box_shadow_card">
+            <CourseDirectory :info="info" />
+          </div>
+          <div class=" bg_color_fff padding16 border_radius_16 box_shadow_card mt10">
+            <OtherCourse />
+          </div>
+        </div>
+      </div>
+    </div>
+    <BlockNoteEditorDialog ref="blockNoteEditorDialogRef" />
+  </div>
+</template>
+
+<script setup>
+import riliIcon from '@/assets/imgs/rili.png'
+import weishoucangIcon from '@/assets/imgs/weishoucang.png'
+import shoucangIcon from '@/assets/imgs/shoucang.png'
+import fenxiangIcon from '@/assets/imgs/fenxiang.png'
+import addIcon from '@/assets/imgs/add.png'
+
+import OtherCourse from './components/OtherCourse.vue'
+import CourseDirectory from './components/CourseDirectory.vue'
+import VideoPlayer from '@/components/VideoPlayer.vue'
+import Pinglun from './components/pinglun.vue'
+import BlockNoteEditorDialog from '@/components/BlockNoteEditorDialog.vue'
+import DGTMessage from '@/utils/message'
+
+// 引入api
+import { getCourseDetail } from '@/api/course.js'
+
+import { useRouter, useRoute } from 'vue-router'
+const router = useRouter()
+const route = useRoute()
+console.log(router,route)
+import { ref, computed, reactive, onMounted } from 'vue'
+import { useAppStore } from '@/pinia/appStore'
+const appStore = useAppStore()
+
+//获取参数
+const query = route.query;
+const courseId = ref(query.courseId || '');
+const info = ref({})
+
+// 视频相关
+// 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 currentVideoUrl = ref('http://jcxxpt.oss-cn-beijing.aliyuncs.com/common/2025/12/19/actmOvmq0xOBc8448561c73a066a523821c6f7ae4868_20251219094240A008.mp4')
+const currentCourseCover = ref('http://jcxxpt.oss-cn-beijing.aliyuncs.com/common/2025/12/15/3120938e-8205-416e-aedc-8b25ea23cc94_20251125142306A001_20251215110507A001.png')
+const currentPlayingVideoId = ref(null)
+const currentVideoDuration = ref(0)
+const currentPlayTime = ref(0)
+const videoPlayer = ref(null)
+
+onMounted(() => {
+ getDetail();
+});
+const getDetail = async () => {
+    getCourseDetail({id: courseId.value}).then(res => {
+    if(res.code === 200){
+      console.log(res)
+      info.value = res.data || {};
+    }
+  })
+};
+
+// 打开添加对话框
+const blockNoteEditorDialogRef = ref(null)
+const openAddDialog = () => {
+  blockNoteEditorDialogRef.value.openDialog();
+};
+
+// 播放指定视频
+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])
+    }
+  }
+};
+
+
+</script>
+
+<style scoped lang="scss">
+  .CourseDetail{
+    .addBtn{
+      cursor: pointer;
+      padding: 10px 20px;
+      color: #fff;
+    }
+    .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;
+        }
+      }
+    }
+    
+  }
+
+</style>

+ 23 - 6
src/pages/LearningSystem/LearningSystemDetail.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="LearningSystemDetail container-height">
-    <Breadcrumb />
-    <div>
+    <div v-if="!isChildRoute">
+      <Breadcrumb />
       <div>
         <div style="width:100%;position: absolute;top: 60px;left: 0;overflow: hidden;z-index: -1;">
           <div :style="{backgroundImage: `url(${info.coverImageUrl})`}" class="coverImageUrlBg">
@@ -25,13 +25,13 @@
           </div>
         </div>
       </div>
-      <div class="border_radius_16 gradient mt10 pt10">
+      <div class="border_radius_16 gradient mt20 pt10">
         <div class="border_radius_16 bg_color_fff flex-center-between" style="padding:0 16px">
           <div class="color_price">
             <span class="bold font_size36">{{info.coursePrice}}</span>
             <span class="font_size18">{{$t('common.mibi')}}</span>
           </div>
-          <div class="flex-center-between">
+          <div class="flex-center-between" @click="goDetail(info)">
             <img :src="zuIcon" alt="" style="height:98px">
             <div class="gap5 gradient border_radius_4 cursor-pointer xuexi ml10">
               <img :src="playIcon" alt="" style="width:13px;height:15px">
@@ -58,17 +58,18 @@
           </el-tabs>
         </div>
         <div class="detail_right bg_color_fff padding16 border_radius_16 box_shadow_card">
-          <Right />
+          <OtherCourse />
         </div>
       </div>
     </div>
+    <router-view />
   </div>
 </template>
 
 <script setup>
   import playIcon from '@/assets/imgs/bofang.png'
   import zuIcon from '@/assets/imgs/zu.png'
-  import Right from './components/right.vue'
+  import OtherCourse from './components/OtherCourse.vue'
   import CourseDescription from './components/CourseDescription.vue'
   import CourseDirectory from './components/CourseDirectory.vue'
   import Pinglun from './components/pinglun.vue'
@@ -80,6 +81,12 @@
   import { useRouter, useRoute } from 'vue-router'
   const router = useRouter()
   const route = useRoute()
+  //获取当前路由路径
+  const isChildRoute = computed(() => {
+    return route.matched.length > 2
+  });
+
+
   console.log(router,route)
   import { ref, computed, reactive, onMounted } from 'vue'
   import { useAppStore } from '@/pinia/appStore'
@@ -105,6 +112,16 @@
       }
     })
   };
+  const goDetail = (item) => {
+    //增加参数名称
+    router.push({
+      path: `/learning-system/detail/course`,
+      query: {
+        courseId: item.courseId,
+        metaTitle: '课程详情'
+      }
+    })
+  };
 
 
 </script>

+ 4 - 1
src/pages/LearningSystem/components/CourseDirectory.vue

@@ -1,6 +1,9 @@
 <template>
   <div class="kechengmulu">
-    <div class="font_size18 bold">{{$t('common.kechengmulu')}}</div>
+    <div class="gap10">
+      <div class="line_vertical"></div>
+      <div class="font_size18 bold">{{$t('common.kechengmulu')}} (1/20)</div>
+    </div>
     <div class="flex-center-between font_size16 gray list_item" 
     :class="{'active': index === 0}"
     v-for="(item, index) in 4" :key="index">

src/pages/LearningSystem/components/right.vue → src/pages/LearningSystem/components/OtherCourse.vue


+ 2 - 1
src/pages/LearningSystem/components/xuxibiji.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="xuxibiji">
     <div class="font_size18 bold">{{$t('common.xuxibiji')}}</div>
-    <div class="addBtn flex-center gradient border_radius_16">
+    <div class="addBtn flex-center gradient border_radius_10">
       <div class="gap10" @click="openAddDialog">
         <img :src="addIcon" alt="" style="width:30px;height:30px">
         <span class="font_size18">{{$t('common.add')}}{{$t('common.xuxibiji')}}</span>
@@ -89,6 +89,7 @@ const openAddDialog = () => {
       background: #F5F7FA;
     }
     .addBtn{
+      cursor: pointer;
       margin-top: 20px;
       padding: 10px 20px;
       color: #fff;

+ 0 - 0
src/pages/order/orderCon.vue


+ 10 - 2
src/router/index.js

@@ -89,13 +89,21 @@ const routes = [
         path: 'detail',
         name: 'LearningSystemDetail',
         component: () => import('@/pages/LearningSystem/LearningSystemDetail.vue'),
-        meta: { title: '' }
+        meta: { title: '' },
+        children: [
+          {
+            path: 'course',
+            name: 'CourseDetail',
+            component: () => import('@/pages/LearningSystem/CourseDetail.vue'),
+            meta: { title: '' }
+          }
+        ]
       },
     ]
   },
   {
     path: '/course/:id',
-    name: 'CourseDetail',
+    name: 'CourseDetail2',
     component: CourseDetail,
     meta: { title: 'route.courseDetail' }
   },

+ 3 - 0
src/styles/index.scss

@@ -83,6 +83,9 @@ body{
   display: block;
   clear: both;
 }
+.fit_content{
+	height: fit-content;
+}
 
 // 禁用选中
 .no-select {