Selaa lähdekoodia

```
feat(course): 课程详情页添加章节购买功能和加载状态

- 在课程详情页添加v-loading指令显示加载状态
- 将章节名称从固定"第1期"改为动态显示章节顺序名称
- 为CourseDirectory组件添加ref引用和chapterId参数
- 添加purchaseFn事件回调处理购买逻辑
- 导入purchase API和confirmBuy工具函数
- 添加isLoading、chapterId、chapterInfo响应式变量
- 实现purchaseFn方法处理章节购买逻辑
- 修复视频播放结束后自动播放下个视频的逻辑

fix(course-directory): 课程目录组件优化和路由跳转修复

- 添加空数据时的空状态显示
- 添加router实例和emit定义
- 添加chapterId属性支持
- 修复章节选择逻辑,支持根据传入的chapterId定位选中项
- 优化路由跳转逻辑,使用router.push替代window.location.href
- 添加purchaseFn事件发射机制

refactor(utils): 优化confirmBuy工具函数参数支持

- 添加callbackParams参数支持回调函数传参
- 修复回调函数执行时参数传递问题

fix(learning-system): 学习系统详情页组件参数调整

- 调整CourseDescription和CourseDirectory组件的formPage参数传递
```

zhangningning 1 kuukausi sitten
vanhempi
commit
188e6779ac

+ 45 - 14
src/pages/LearningSystem/CourseDetail.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="CourseDetail container-height">
     <Breadcrumb />
-    <div>
+    <div v-loading="isLoading">
       <div class="flex_1 bg_color_fff padding16 border_radius_16 box_shadow_card fit_content">
         <div class="gap10">
           <el-button type="primary">{{info.skillTagName}}</el-button>
-          <div class="bold font_size30">{{info.courseCategoryName}}(第1期)</div>
+          <div class="bold font_size30">{{info.courseCategoryName}}({{chapterInfo.chapterOrderName}})</div>
         </div>
         <div class="gap5 mt10">
           <img :src="riliIcon" alt="" style="width: 16px; height: 16px;">
@@ -63,7 +63,7 @@
         </div>
         <div class="detail_right">
           <div class="bg_color_fff padding16 border_radius_16 box_shadow_card" v-show="!isShowNote">
-            <CourseDirectory :info="info" />
+            <CourseDirectory :info="info" ref="courseDirectory" :chapterId="chapterId" @purchaseFn="purchaseFn"/>
           </div>
           <div class=" bg_color_fff padding16 border_radius_16 box_shadow_card mt10" v-show="!isShowNote">
             <OtherCourse />
@@ -93,7 +93,8 @@ import VideoPlayer from '@/components/VideoPlayer.vue'
 import Pinglun from './components/pinglun.vue'
 import Xuxibiji from './components/Xuxibiji.vue'
 import DGTMessage from '@/utils/message'
-import { copyText } from '@/utils/util'
+import { copyText, confirmBuy } from '@/utils/util'
+import { purchase } from '@/api/common.js'
 
 // 引入api
 import { getCourseDetail,collect } from '@/api/course.js'
@@ -108,10 +109,12 @@ const appStore = useAppStore()
 
 import { useI18n } from 'vue-i18n' 
 const { t } = useI18n() 
-
+const isLoading = ref(false);
 //获取参数
 const query = route.query;
-const courseId = ref(route.params.courseId || '');
+const courseId = ref(route.params.courseId || '');//课程id
+const chapterId = ref(route.query.chapterId || '');//章节id
+const chapterInfo = ref({})
 const info = ref({})
 
 // 视频相关
@@ -126,17 +129,45 @@ const currentPlayTime = ref(0)
 const videoPlayer = ref(null)
 
 onMounted(() => {
- getDetail();
+  getDetail();
 });
 const getDetail = async () => {
-    getCourseDetail({id: courseId.value}).then(res => {
+    await getCourseDetail({id: courseId.value}).then(res => {
+      if(res.code === 200){
+        console.log(res)
+        info.value = res.data || {};
+        currentCourseCover.value = info.value.coverImageUrl || ''
+      }
+    })
+};
+const purchaseFn = async (chapterId, chapterInfoItem={}) => {
+  console.log(chapterId, chapterInfoItem)
+  isLoading.value = true;
+  chapterInfo.value = chapterInfoItem || {};
+  await purchase({
+    buyType: 2,
+    chapterId,
+  }).then(res => {
+    isLoading.value = false;
     if(res.code === 200){
-      console.log(res)
-      info.value = res.data || {};
-      currentCourseCover.value = info.value.coverImageUrl || ''
+      if(res.data?.buyFlag == 1){
+        currentVideoUrl.value = res.data?.contentUrl || ''
+      }else{
+        confirmBuy({
+          callback:purchaseFn,
+          callbackParams:chapterId,
+          appStore,
+          router,
+          price:info.value.coursePrice,
+          t,
+          productId:info.value.courseId,
+          orderType:'course_purchase',
+          payMethod:'BMI'
+        })
+      }
     }
   })
-};
+}
 
 // 打开添加对话框
 const isShowNote = ref(false)
@@ -177,7 +208,7 @@ const onPlayerEnded = () => {
   console.log('视频播放结束')
   
   // 自动播放下一个视频
-  playNextVideo()
+  // playNextVideo()
 }
 
 const onPlayerTimeupdate = (time) => {
@@ -247,7 +278,7 @@ const collectFn = () => {
 // 复制当前页面地址
 const copyCurrentUrl = () => {
   copyText(window.location.href, t)
-}
+};
 
 
 

+ 2 - 2
src/pages/LearningSystem/LearningSystemDetail.vue

@@ -44,11 +44,11 @@
         <div class="flex_1 bg_color_fff padding16 border_radius_16 box_shadow_card mr20">
           <el-tabs v-model="activeName" class="demo-tabs">
             <el-tab-pane :label="$t('common.kechengjieshao')" name="first">
-              <CourseDescription :info="info" formPage="LearningSystemDetail" />
+              <CourseDescription :info="info" />
             </el-tab-pane>
             <el-tab-pane :label="$t('common.kechengmulu')" name="kechengmulu">
               <keep-alive>
-                <CourseDirectory :info="info" />
+                <CourseDirectory :info="info" formPage="LearningSystemDetail"/>
               </keep-alive>
             </el-tab-pane>
             <el-tab-pane :label="$t('common.pinglun')" name="pinglun">

+ 30 - 5
src/pages/LearningSystem/components/CourseDirectory.vue

@@ -17,12 +17,18 @@
       </div>
       <div>{{item.chapterTime}}</div>
     </div>
+    <div v-if="list.length === 0" >
+      <el-empty :image-size="200" />
+    </div>
   </div>
 </template>
 <script setup>
 import muluIcon from '@/assets/imgs/mulu.png'
 import { getChaptersList } from '@/api/course.js'
 import { ref, onMounted, watch, onActivated } from 'vue'
+import { useRouter } from 'vue-router'
+const emit = defineEmits(['purchaseFn']);
+const router = useRouter();
 const props = defineProps({
   info: {
     type: Object,
@@ -31,6 +37,10 @@ const props = defineProps({
   formPage: {
     type: String,
     default: ''
+  },
+  chapterId: {
+    type: String,
+    default: ''
   }
 })
 const selectedItem = ref({});//选中的章节
@@ -56,7 +66,12 @@ const getChaptersListFn = () => {
     if(res.code === 200){
       list.value = res.rows || [];
       if(list.value.length > 0){
-        selectedItem.value = list.value[0];
+        if(props.chapterId){
+          selectedItem.value = list.value.find(item => item.chapterId === props.chapterId) || {};
+        }else{
+          selectedItem.value = list.value[0];
+        }
+        purchaseFn(selectedItem.value.chapterId, selectedItem.value);
       }
     }
   })
@@ -66,14 +81,24 @@ const getChaptersListFn = () => {
 const handleCheck = (item) => {
   selectedItem.value = item;
   if(props.formPage === 'LearningSystemDetail'){
-    // 学习系统详情页点击章节跳转
-    window.location.href = '#/LearningSystemDetail/' + props.info.courseId + '/' + item.chapterId;
+    //增加参数名称
+    router.push({
+      path: `/learning-system/detail/${item.courseId}/course/${item.courseId}`,
+      query: {
+        courseId: item.courseId,
+        chapterId: item.chapterId,
+        metaTitle: '课程详情'
+      }
+    })
   }else{
-    // 课程详情页点击章节跳转
-    window.location.href = '#/CourseDetail/' + props.info.courseId + '/' + item.chapterId;
+    purchaseFn(selectedItem.value.chapterId, selectedItem.value);
   }
 }
 
+const purchaseFn = (chapterId, chapterInfo={}) => {
+  emit('purchaseFn', chapterId, chapterInfo);
+}
+
 
 
 </script>

+ 13 - 2
src/utils/util.js

@@ -49,7 +49,18 @@ export function formatDeadline(deadline) {
   return {diffDays,diffHours};
 }
 //type: pointsBalance  pointsBalance
-export function confirmBuy({type='pointsBalance',price=0,callback,appStore,router,t,payMethod,orderType,productId}){
+export function confirmBuy({
+  type='baoMiBalance',
+  price=0,
+  callback,
+  callbackParams=null,
+  appStore,
+  router,
+  t,
+  payMethod,
+  orderType,
+  productId
+}){
   const balance = appStore.userInfo?.[type] || 0;
   if(balance < price ){
     ElMessageBox.confirm('您的余额不足,是否前往充值?', t('common.tip'), {
@@ -82,7 +93,7 @@ export function confirmBuy({type='pointsBalance',price=0,callback,appStore,route
       if(res.code === 200){
         DGTMessage.success(t('common.buy')+t('common.success'));
         if(callback && typeof callback === 'function'){
-          callback();
+          callback(callbackParams);
         }
       }else{
         // DGTMessage.error(res.msg || t('common.buy')+t('common.fail'));