hookehuyr

feat(课程): 添加课程评论功能并优化课程详情页

- 添加课程评论功能,支持评论的增删改查
- 优化课程详情页,显示课程评论列表和评分
- 修改课程卡片组件,支持自定义跳转链接
- 更新路由配置,支持带参数的课程学习页面
- 修复部分页面样式和逻辑问题
1 /* 1 /*
2 * @Date: 2025-04-15 09:32:07 2 * @Date: 2025-04-15 09:32:07
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-04-15 12:58:17 4 + * @LastEditTime: 2025-04-18 14:43:46
5 * @FilePath: /mlaj/src/api/course.js 5 * @FilePath: /mlaj/src/api/course.js
6 * @Description: 课程模块相关接口 6 * @Description: 课程模块相关接口
7 */ 7 */
...@@ -10,7 +10,11 @@ import { fn, fetch } from './fn' ...@@ -10,7 +10,11 @@ import { fn, fetch } from './fn'
10 const Api = { 10 const Api = {
11 GET_COURSE_LIST: '/srv/?a=schedule&t=list', 11 GET_COURSE_LIST: '/srv/?a=schedule&t=list',
12 GET_COURSE_DETAIL: '/srv/?a=schedule&t=detail', 12 GET_COURSE_DETAIL: '/srv/?a=schedule&t=detail',
13 - GET_SCHEDULE_COURSE_LIST: '/srv/?a=schedule&t=course', 13 + GET_SCHEDULE_COURSE: '/srv/?a=schedule&t=course',
14 + GET_GROUP_COMMENT_LIST: '/srv/?a=group_comment_list',
15 + GROUP_COMMENT_ADD: '/srv/?a=group_comment_add',
16 + GROUP_COMMENT_EDIT: '/srv/?a=group_comment_edit',
17 + GROUP_COMMENT_DEL: '/srv/?a=group_comment_del',
14 } 18 }
15 19
16 /** 20 /**
...@@ -31,8 +35,42 @@ export const getCourseListAPI = (params) => fn(fetch.get(Api.GET_COURSE_LIST, p ...@@ -31,8 +35,42 @@ export const getCourseListAPI = (params) => fn(fetch.get(Api.GET_COURSE_LIST, p
31 export const getCourseDetailAPI = (params) => fn(fetch.get(Api.GET_COURSE_DETAIL, params)) 35 export const getCourseDetailAPI = (params) => fn(fetch.get(Api.GET_COURSE_DETAIL, params))
32 36
33 /** 37 /**
34 - * @description: 获取特定学习课程的目录 38 + * @description: 获取特定学习课程的详情
35 * @param: i 课程 ID 39 * @param: i 课程 ID
36 * @return: data: [{ id, schedule_time, seq, title, duration, course_id, file}] 40 * @return: data: [{ id, schedule_time, seq, title, duration, course_id, file}]
37 */ 41 */
38 -export const getScheduleCourseListAPI = (params) => fn(fetch.get(Api.GET_SCHEDULE_COURSE_LIST, params)) 42 +export const getScheduleCourseAPI = (params) => fn(fetch.get(Api.GET_SCHEDULE_COURSE, params))
43 +
44 +/**
45 + * @description: 获取课程评论列表
46 + * @param: i 课程 ID
47 + * @param: limit 每页数量 默认10
48 + * @param: page 页码
49 + * @return: data: { comment_score 课程评论分数, comment_count 评论数量, comment_list [{ id 评论id, created_by 评论人ID, name 评论人姓名, note 评论内容, score 分数, create_time 评论时间}] 评论列表}
50 + */
51 +export const getGroupCommentListAPI = (params) => fn(fetch.get(Api.GET_GROUP_COMMENT_LIST, params))
52 +
53 +/**
54 + * @description: 添加课程评论
55 + * @param: i 课程 ID
56 + * @param: note 评论内容
57 + * @param: score 分数
58 + * @return: data: ''
59 + */
60 +export const addGroupCommentAPI = (params) => fn(fetch.post(Api.GROUP_COMMENT_ADD, params))
61 +
62 +/**
63 + * @description: 编辑课程评论
64 + * @param: i 课程 ID
65 + * @param: note 评论内容
66 + * @param: score 分数
67 + * @return: data: ''
68 + */
69 +export const editGroupCommentAPI = (params) => fn(fetch.post(Api.GROUP_COMMENT_EDIT, params))
70 +
71 +/**
72 + * @description: 删除课程评论
73 + * @param: i 课程 ID
74 + * @return: data: ''
75 + */
76 +export const delGroupCommentAPI = (params) => fn(fetch.post(Api.GROUP_COMMENT_DEL, params))
......
...@@ -27,6 +27,7 @@ declare module 'vue' { ...@@ -27,6 +27,7 @@ declare module 'vue' {
27 SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] 27 SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default']
28 TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default'] 28 TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default']
29 UploadVideoPopup: typeof import('./components/ui/UploadVideoPopup.vue')['default'] 29 UploadVideoPopup: typeof import('./components/ui/UploadVideoPopup.vue')['default']
30 + VanActionSheet: typeof import('vant/es')['ActionSheet']
30 VanButton: typeof import('vant/es')['Button'] 31 VanButton: typeof import('vant/es')['Button']
31 VanCellGroup: typeof import('vant/es')['CellGroup'] 32 VanCellGroup: typeof import('vant/es')['CellGroup']
32 VanCheckbox: typeof import('vant/es')['Checkbox'] 33 VanCheckbox: typeof import('vant/es')['Checkbox']
......
1 +<!--
2 + * @Date: 2025-04-18
3 + * @Description: 评论编辑组件
4 +-->
5 +<template>
6 + <van-popup :show="show" @update:show="emit('update:show', $event)" position="bottom" round>
7 + <div class="p-4">
8 + <div class="text-lg font-bold text-center mb-4">编辑评价</div>
9 + <div class="flex justify-center mb-4">
10 + <van-rate v-model="score" :size="24" color="#ffd21e" void-icon="star" void-color="#eee" />
11 + </div>
12 + <van-field v-model="note" rows="3" type="textarea" placeholder="请输入您的评价内容" class="mb-4" />
13 + <div class="flex justify-end space-x-3">
14 + <van-button round plain @click="handleCancel">取消</van-button>
15 + <van-button round type="primary" color="#4CAF50" @click="handleSubmit">提交</van-button>
16 + </div>
17 + </div>
18 + </van-popup>
19 +</template>
20 +
21 +<script setup>
22 +import { ref, watch } from 'vue'
23 +import { Popup, Rate, Field, Button } from 'vant'
24 +
25 +const props = defineProps({
26 + show: {
27 + type: Boolean,
28 + default: false
29 + },
30 + initialScore: {
31 + type: Number,
32 + default: 5
33 + },
34 + initialNote: {
35 + type: String,
36 + default: ''
37 + }
38 +})
39 +
40 +const emit = defineEmits(['update:show', 'submit'])
41 +
42 +const score = ref(props.initialScore)
43 +const note = ref(props.initialNote)
44 +
45 +watch(() => props.show, (newVal) => {
46 + if (newVal) {
47 + score.value = props.initialScore
48 + note.value = props.initialNote
49 + }
50 +})
51 +
52 +const handleCancel = () => {
53 + emit('update:show', false)
54 +}
55 +
56 +const handleSubmit = () => {
57 + if (score.value === 0) {
58 + return
59 + }
60 + if (!note.value.trim()) {
61 + return
62 + }
63 + emit('submit', {
64 + score: score.value,
65 + note: note.value.trim()
66 + })
67 + emit('update:show', false)
68 +}
69 +</script>
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
8 <template> 8 <template>
9 - <router-link :to="`/courses/${course.id}`" class="flex bg-white rounded-lg overflow-hidden shadow-sm"> 9 + <router-link :to="linkTo || `/courses/${course.id}`" class="flex bg-white rounded-lg overflow-hidden shadow-sm">
10 <div class="w-1/3 h-28"> 10 <div class="w-1/3 h-28">
11 <img 11 <img
12 :src="course.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'" 12 :src="course.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'"
...@@ -37,6 +37,10 @@ defineProps({ ...@@ -37,6 +37,10 @@ defineProps({
37 course: { 37 course: {
38 type: Object, 38 type: Object,
39 required: true 39 required: true
40 + },
41 + linkTo: {
42 + type: String,
43 + default: ''
40 } 44 }
41 }) 45 })
42 </script> 46 </script>
......
1 <!-- 1 <!--
2 * @Date: 2025-03-24 16:57:55 2 * @Date: 2025-03-24 16:57:55
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-03-24 17:17:44 4 + * @LastEditTime: 2025-04-18 14:12:14
5 * @FilePath: /mlaj/src/components/ui/ReviewPopup.vue 5 * @FilePath: /mlaj/src/components/ui/ReviewPopup.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -91,8 +91,7 @@ const handleSubmit = () => { ...@@ -91,8 +91,7 @@ const handleSubmit = () => {
91 rating: rating.value, 91 rating: rating.value,
92 content: content.value.trim(), 92 content: content.value.trim(),
93 }); 93 });
94 - show_toast.value = true; 94 + // 提交成功后关闭弹窗
95 - message.value = "评论提交成功";
96 handleCancel(); 95 handleCancel();
97 }; 96 };
98 </script> 97 </script>
......
1 /* 1 /*
2 * @Date: 2025-03-20 20:36:36 2 * @Date: 2025-03-20 20:36:36
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-04-08 16:09:21 4 + * @LastEditTime: 2025-04-18 10:08:35
5 * @FilePath: /mlaj/src/router/routes.js 5 * @FilePath: /mlaj/src/router/routes.js
6 * @Description: 路由地址映射配置 6 * @Description: 路由地址映射配置
7 */ 7 */
...@@ -213,7 +213,7 @@ export const routes = [ ...@@ -213,7 +213,7 @@ export const routes = [
213 } 213 }
214 }, 214 },
215 { 215 {
216 - path: '/studyCourse', 216 + path: '/studyCourse/:id',
217 component: () => import('@/views/study/studyCoursePage.vue'), 217 component: () => import('@/views/study/studyCoursePage.vue'),
218 meta: { 218 meta: {
219 title: '课程集合页面', 219 title: '课程集合页面',
......
...@@ -118,38 +118,23 @@ ...@@ -118,38 +118,23 @@
118 <FrostedGlass class="mb-6 p-4 rounded-xl"> 118 <FrostedGlass class="mb-6 p-4 rounded-xl">
119 <h3 class="text-lg font-bold text-gray-800 mb-3">学员评价</h3> 119 <h3 class="text-lg font-bold text-gray-800 mb-3">学员评价</h3>
120 <div class="flex items-center mb-3"> 120 <div class="flex items-center mb-3">
121 - <div class="flex text-yellow-400 mr-2"> 121 + <div class="flex items-center mr-2">
122 - <svg v-for="star in 5" :key="star" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" 122 + <van-rate v-model="commentScore" readonly allow-half color="#facc15" void-color="#e5e7eb" size="20" />
123 - fill="currentColor">
124 - <path
125 - d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
126 - </svg>
127 </div> 123 </div>
128 - <div class="text-gray-700">4.9 (126条评论)</div> 124 + <div class="text-gray-700">{{ commentScore }} ({{ commentTotal }}条评论)</div>
129 </div> 125 </div>
130 126
131 <div class="space-y-4"> 127 <div class="space-y-4">
132 - <div class="border-b border-gray-100 pb-3"> 128 + <div v-for="(item, index) in commentList" :key="index" class="border-b border-gray-100 pb-3">
133 <div class="flex justify-between"> 129 <div class="flex justify-between">
134 - <div class="font-medium text-gray-800">王小明</div> 130 + <div class="font-medium text-gray-800">{{ item.name }}</div>
135 - <div class="text-xs text-gray-500">2024-06-15</div> 131 + <div class="text-xs text-gray-500">{{ formatDate(item.created_time) }}</div>
136 </div> 132 </div>
137 <p class="text-sm text-gray-600 mt-1"> 133 <p class="text-sm text-gray-600 mt-1">
138 - 课程内容非常实用,老师讲解清晰,帮助我和孩子度过了考前紧张期。 134 + {{ item.note }}
139 </p> 135 </p>
140 </div> 136 </div>
141 -
142 - <div class="border-b border-gray-100 pb-3">
143 - <div class="flex justify-between">
144 - <div class="font-medium text-gray-800">李晓华</div>
145 - <div class="text-xs text-gray-500">2024-06-10</div>
146 - </div>
147 - <p class="text-sm text-gray-600 mt-1">
148 - 老师提供的减压方法很有效,孩子学习状态明显改善,感谢这个课程!
149 - </p>
150 </div> 137 </div>
151 - </div>
152 -
153 <button @click="router.push(`/courses/${course?.id}/reviews`)" 138 <button @click="router.push(`/courses/${course?.id}/reviews`)"
154 class="w-full text-center text-green-600 mt-3 text-sm"> 139 class="w-full text-center text-green-600 mt-3 text-sm">
155 查看全部评价 140 查看全部评价
...@@ -199,7 +184,7 @@ ...@@ -199,7 +184,7 @@
199 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 transition-transform duration-300" 184 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 transition-transform duration-300"
200 :fill="isFavorite ? 'red' : 'none'" viewBox="0 0 24 24" :stroke="isFavorite ? 'red' : 'currentColor'"> 185 :fill="isFavorite ? 'red' : 'none'" viewBox="0 0 24 24" :stroke="isFavorite ? 'red' : 'currentColor'">
201 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 186 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
202 - d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" /> 187 + d="M4.318 6.318 a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682 a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318 a4.5 4.5 0 00-6.364 0z" />
203 </svg> 188 </svg>
204 收藏 189 收藏
205 </button> 190 </button>
...@@ -235,16 +220,17 @@ ...@@ -235,16 +220,17 @@
235 <script setup lang="jsx"> 220 <script setup lang="jsx">
236 import { ref, onMounted, defineComponent, h } from 'vue' 221 import { ref, onMounted, defineComponent, h } from 'vue'
237 import { useRoute, useRouter } from 'vue-router' 222 import { useRoute, useRouter } from 'vue-router'
238 -import AppLayout from '@/components/layout/AppLayout.vue'
239 -import FrostedGlass from '@/components/ui/FrostedGlass.vue'
240 -import ReviewPopup from '@/components/ui/ReviewPopup.vue'
241 -// import { courses } from '@/utils/mockData'
242 import { useCart } from '@/contexts/cart' 223 import { useCart } from '@/contexts/cart'
243 import { useTitle } from '@vueuse/core'; 224 import { useTitle } from '@vueuse/core';
244 import { showToast } from 'vant'; 225 import { showToast } from 'vant';
226 +import { formatDate } from '@/utils/tools'
227 +
228 +import AppLayout from '@/components/layout/AppLayout.vue'
229 +import FrostedGlass from '@/components/ui/FrostedGlass.vue'
230 +import ReviewPopup from '@/components/ui/ReviewPopup.vue'
245 231
246 // 导入接口 232 // 导入接口
247 -import { getCourseDetailAPI } from "@/api/course"; 233 +import { getCourseDetailAPI, getGroupCommentListAPI, addGroupCommentAPI } from "@/api/course";
248 import { addFavoriteAPI, cancelFavoriteAPI } from "@/api/favorite"; 234 import { addFavoriteAPI, cancelFavoriteAPI } from "@/api/favorite";
249 235
250 const $route = useRoute(); 236 const $route = useRoute();
...@@ -328,7 +314,7 @@ const RightContent = defineComponent({ ...@@ -328,7 +314,7 @@ const RightContent = defineComponent({
328 314
329 const rightContent = h(RightContent) 315 const rightContent = h(RightContent)
330 316
331 -// Handle purchase 317 +// 立即购买操作
332 const handlePurchase = () => { 318 const handlePurchase = () => {
333 if (course.value) { 319 if (course.value) {
334 addToCart({ 320 addToCart({
...@@ -342,20 +328,45 @@ const handlePurchase = () => { ...@@ -342,20 +328,45 @@ const handlePurchase = () => {
342 } 328 }
343 } 329 }
344 330
345 -// Handle review submit 331 +// 提交评论操作
346 -const handleReviewSubmit = (review) => { 332 +const handleReviewSubmit = async (review) => {
347 - // TODO: 对接评论提交接口 333 + const { code, msg } = await addGroupCommentAPI({
348 - console.log('Review submitted:', review) 334 + group_id: course.value?.id,
335 + note: review.content,
336 + score: review.rating
337 + })
338 + if (code) {
339 + showToast('评论提交成功')
349 isReviewed.value = true 340 isReviewed.value = true
341 + await fetchCommentList()
342 + }
350 } 343 }
351 344
352 -// Fetch course data 345 +const commentList = ref([])
346 +const commentScore = ref(0)
347 +const commentTotal = ref(0)
348 +
349 +// 获取评论列表
350 +const fetchCommentList = async () => {
351 + const { code, data } = await getGroupCommentListAPI({
352 + group_id: course.value?.id,
353 + page: 0,
354 + limit: 5
355 + })
356 + if (code) {
357 + commentList.value = data.comment_list
358 + commentScore.value = data.comment_score || 0
359 + commentTotal.value = data.comment_count || 0
360 + }
361 +}
362 +
363 +// 初始化
353 onMounted(async () => { 364 onMounted(async () => {
354 const id = route.params.id 365 const id = route.params.id
355 // 调用接口获取课程详情 366 // 调用接口获取课程详情
356 - const res = await getCourseDetailAPI({ i: id }); 367 + const { code, data } = await getCourseDetailAPI({ i: id });
357 - if (res.code) { 368 + if (code) {
358 - const foundCourse = res.data; 369 + const foundCourse = data;
359 if (foundCourse) { 370 if (foundCourse) {
360 course.value = foundCourse; 371 course.value = foundCourse;
361 teacher.value = { 372 teacher.value = {
...@@ -364,10 +375,13 @@ onMounted(async () => { ...@@ -364,10 +375,13 @@ onMounted(async () => {
364 position: '', 375 position: '',
365 description: '', 376 description: '',
366 } 377 }
367 - isPurchased.value = foundCourse.isPurchased; 378 + isFavorite.value = foundCourse.is_favorite;
368 - isReviewed.value = foundCourse.isReviewed; 379 + isPurchased.value = foundCourse.is_buy;
380 + isReviewed.value = foundCourse.is_comment;
381 + // 获取评论列表
382 + await fetchCommentList()
369 } else { 383 } else {
370 - // Course not found, redirect to courses page 384 + // 课程不存在,跳转到课程主页面
371 showToast('课程不存在') 385 showToast('课程不存在')
372 router.push('/courses') 386 router.push('/courses')
373 } 387 }
......
1 <!-- 1 <!--
2 * @Date: 2025-03-21 11:33:26 2 * @Date: 2025-03-21 11:33:26
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-03-21 16:02:21 4 + * @LastEditTime: 2025-04-18 14:55:53
5 * @FilePath: /mlaj/src/views/courses/CourseReviewsPage.vue 5 * @FilePath: /mlaj/src/views/courses/CourseReviewsPage.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -11,81 +11,136 @@ ...@@ -11,81 +11,136 @@
11 <!-- Overall Rating --> 11 <!-- Overall Rating -->
12 <div class="flex items-center justify-between mb-4"> 12 <div class="flex items-center justify-between mb-4">
13 <div class="flex items-center"> 13 <div class="flex items-center">
14 - <div class="text-2xl font-bold mr-2">4.9</div> 14 + <div class="text-2xl font-bold mr-2">{{ overallRating }}</div>
15 <van-rate v-model="overallRating" readonly :size="20" color="#ffd21e" void-icon="star" void-color="#eee" /> 15 <van-rate v-model="overallRating" readonly :size="20" color="#ffd21e" void-icon="star" void-color="#eee" />
16 </div> 16 </div>
17 - <div class="text-gray-500 text-sm">126条评论</div> 17 + <div class="text-gray-500 text-sm">{{ commentTotal }}条评论</div>
18 </div> 18 </div>
19 19
20 <!-- Reviews List --> 20 <!-- Reviews List -->
21 - <van-list 21 + <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
22 - v-model:loading="loading"
23 - :finished="finished"
24 - finished-text="没有更多了"
25 - @load="onLoad"
26 - >
27 <div v-for="(review, index) in reviews" :key="index" class="mb-4 pb-4 border-b border-gray-100 last:border-0"> 22 <div v-for="(review, index) in reviews" :key="index" class="mb-4 pb-4 border-b border-gray-100 last:border-0">
28 <div class="flex justify-between items-center mb-2"> 23 <div class="flex justify-between items-center mb-2">
29 <div class="flex items-center flex-1 min-w-0 mr-2"> 24 <div class="flex items-center flex-1 min-w-0 mr-2">
30 <div class="flex-grow"> 25 <div class="flex-grow">
31 - <span class="font-medium text-sm block">{{ review.username }}</span> 26 + <span class="font-medium text-sm block">{{ review.name }}</span>
32 </div> 27 </div>
33 </div> 28 </div>
34 - <van-rate v-model="review.rating" readonly :size="20" color="#ffd21e" void-icon="star" void-color="#eee" /> 29 + <van-rate v-model="review.score" readonly :size="20" color="#ffd21e" void-icon="star" void-color="#eee" />
30 + </div>
31 + <p class="text-gray-600 text-sm mb-2">{{ review.note }}</p>
32 + <div class="flex justify-between items-center">
33 + <div class="text-gray-400 text-xs">{{ formatDate(review.created_time) }}</div>
34 + <van-icon v-if="review.is_my" name="ellipsis" class="text-gray-400" @click="showActionSheet(review)" />
35 </div> 35 </div>
36 - <p class="text-gray-600 text-sm mb-2">{{ review.content }}</p>
37 - <div class="text-gray-400 text-xs">{{ review.date }}</div>
38 </div> 36 </div>
39 </van-list> 37 </van-list>
40 </div> 38 </div>
39 +
40 + <!-- Action Sheet -->
41 + <van-action-sheet v-model:show="showActions" :actions="actions" cancel-text="取消" close-on-click-action
42 + @select="onSelect" />
43 +
44 + <!-- Review Edit Popup -->
45 + <ReviewPopup v-model:show="showReviewPopup" :initial-score="currentReview?.score"
46 + :initial-note="currentReview?.note" @submit="handleReviewEdit" />
41 </AppLayout> 47 </AppLayout>
42 </template> 48 </template>
43 49
44 <script setup> 50 <script setup>
45 -import { ref } from 'vue' 51 +import { ref, onMounted } from 'vue'
46 import { useRoute } from 'vue-router' 52 import { useRoute } from 'vue-router'
47 import AppLayout from '@/components/layout/AppLayout.vue' 53 import AppLayout from '@/components/layout/AppLayout.vue'
48 -import { Rate, List } from 'vant' 54 +import { Rate, List, Icon, ActionSheet, showConfirmDialog, showToast } from 'vant'
55 +import { formatDate } from '@/utils/tools'
56 +import ReviewPopup from '@/components/courses/ReviewPopup.vue'
57 +
58 +// 导入接口
59 +import { getGroupCommentListAPI, editGroupCommentAPI, delGroupCommentAPI } from '@/api/course'
49 60
50 const route = useRoute() 61 const route = useRoute()
51 -const overallRating = ref(4.9) 62 +const overallRating = ref(0)
52 const loading = ref(false) 63 const loading = ref(false)
53 const finished = ref(false) 64 const finished = ref(false)
54 const reviews = ref([]) 65 const reviews = ref([])
66 +const commentTotal = ref(0)
67 +const limit = ref(5)
68 +const page = ref(0)
55 69
56 -// Mock data for demonstration 70 +// 动作面板相关
57 -const mockReviews = [ 71 +const showActions = ref(false)
58 - { 72 +const currentReview = ref(null)
59 - username: '王小明', 73 +const actions = [
60 - avatar: '', 74 + { name: '编辑', color: '#2563eb' },
61 - rating: 5, 75 + { name: '删除', color: '#ef4444' },
62 - content: '课程内容非常实用,老师讲解清晰,帮助我和孩子度过了考前紧张期。',
63 - date: '2024-06-15'
64 - },
65 - {
66 - username: '李晓华',
67 - avatar: '',
68 - rating: 4.5,
69 - content: '老师提供的减压方法很有效,孩子学习状态明显改善,感谢这个课程!',
70 - date: '2024-06-10'
71 - },
72 - // Add more mock reviews here
73 ] 76 ]
74 77
75 -const onLoad = () => { 78 +// 评论编辑相关
76 - // Simulate async data loading 79 +const showReviewPopup = ref(false)
77 - setTimeout(() => { 80 +
78 - const newReviews = mockReviews.map(review => ({ 81 +const fetchComments = async () => {
79 - ...review, 82 + const { code, data } = await getGroupCommentListAPI({
80 - avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${Math.random()}` 83 + group_id: route.params.id,
81 - })) 84 + page: page.value,
82 - reviews.value.push(...newReviews) 85 + limit: limit.value
86 + })
87 + if (code) {
88 + if (page.value === 0) {
89 + reviews.value = data.comment_list
90 + overallRating.value = data.comment_score || 0
91 + commentTotal.value = data.comment_count || 0
92 + } else {
93 + reviews.value.push(...data.comment_list)
94 + }
95 + finished.value = data.comment_list.length < limit.value
96 + page.value += 1
97 + }
83 loading.value = false 98 loading.value = false
99 +}
100 +
101 +const onLoad = () => {
102 + fetchComments()
103 +}
104 +
105 +const showActionSheet = (review) => {
106 + currentReview.value = review
107 + showActions.value = true
108 +}
109 +
110 +const onSelect = (action) => {
111 + if (action.name === '编辑') {
112 + showReviewPopup.value = true
113 + } else if (action.name === '删除') {
114 + showConfirmDialog({
115 + title: '温馨提示',
116 + message: '确定要删除这条评论吗?',
117 + }).then(() => {
118 + handleReviewDelete()
119 + })
120 + }
121 +}
122 +
123 +const handleReviewEdit = async ({ score, note }) => {
124 + const { code } = await editGroupCommentAPI({
125 + i: currentReview.value.id,
126 + score,
127 + note
128 + })
129 + if (code) {
130 + showToast('评论修改成功')
131 + page.value = 0
132 + await fetchComments()
133 + }
134 +}
84 135
85 - // Set finished when no more data 136 +const handleReviewDelete = async () => {
86 - if (reviews.value.length >= 20) { 137 + const { code } = await delGroupCommentAPI({
87 - finished.value = true 138 + i: currentReview.value.id
139 + })
140 + if (code) {
141 + showToast('评论删除成功')
142 + page.value = 0
143 + await fetchComments()
88 } 144 }
89 - }, 1000)
90 } 145 }
91 </script> 146 </script>
......
1 +<!--
2 + * @Date: 2025-03-21 12:17:03
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-04-18 10:05:47
5 + * @FilePath: /mlaj/src/views/courses/MyCoursesPage.vue
6 + * @Description: 文件描述
7 +-->
1 <template> 8 <template>
2 <div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20"> 9 <div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20">
3 <!-- Course List --> 10 <!-- Course List -->
...@@ -8,7 +15,7 @@ ...@@ -8,7 +15,7 @@
8 @load="onLoad" 15 @load="onLoad"
9 class="px-4 py-3 space-y-4" 16 class="px-4 py-3 space-y-4"
10 > 17 >
11 - <CourseCard v-for="course in courses" :key="course.id" :course="course" /> 18 + <CourseCard v-for="course in courses" :key="course.id" :course="course" :linkTo="`/studyCourse/${course.id}`" />
12 </van-list> 19 </van-list>
13 20
14 <!-- 无数据提示 --> 21 <!-- 无数据提示 -->
...@@ -28,28 +35,46 @@ import CourseCard from '@/components/ui/CourseCard.vue'; ...@@ -28,28 +35,46 @@ import CourseCard from '@/components/ui/CourseCard.vue';
28 import { courses as mockCourses } from '@/utils/mockData'; 35 import { courses as mockCourses } from '@/utils/mockData';
29 import { useTitle } from '@vueuse/core'; 36 import { useTitle } from '@vueuse/core';
30 37
38 +// 导入接口
39 +import { getOrderListAPI } from '@/api/order'
40 +
31 const $route = useRoute(); 41 const $route = useRoute();
32 const $router = useRouter(); 42 const $router = useRouter();
33 useTitle($route.meta.title); 43 useTitle($route.meta.title);
44 +
34 const courses = ref([]) 45 const courses = ref([])
35 const loading = ref(false) 46 const loading = ref(false)
36 const finished = ref(false) 47 const finished = ref(false)
37 -const page = ref(1) 48 +const page = ref(0)
38 -const pageSize = 10 49 +const limit = ref(10)
39 50
40 -const onLoad = () => { 51 +const onLoad = async () => {
41 - loading.value = true 52 + const nextPage = page.value
42 - // 模拟异步加载数据 53 + try {
43 - setTimeout(() => { 54 + const res = await getOrderListAPI({
44 - const start = (page.value - 1) * pageSize 55 + limit: limit.value,
45 - const end = start + pageSize 56 + page: nextPage,
46 - const newCourses = mockCourses.slice(start, end) 57 + status: 'PAY' // 只获取已支付的订单
47 - courses.value.push(...newCourses) 58 + })
48 - loading.value = false 59 + if (res.code) {
49 - if (courses.value.length >= mockCourses.length) { 60 + // 从订单中提取所有课程信息
50 - finished.value = true 61 + const newCourses = res.data.reduce((acc, order) => {
62 + if (order.details && Array.isArray(order.details)) {
63 + const coursesWithTitle = order.details.map(detail => ({
64 + ...detail,
65 + title: detail.product_name
66 + }))
67 + acc.push(...coursesWithTitle)
51 } 68 }
52 - page.value++ 69 + return acc
53 - }, 1000) 70 + }, [])
71 + courses.value = [...courses.value, ...newCourses]
72 + finished.value = newCourses.length < limit.value
73 + page.value = nextPage + 1
74 + }
75 + } catch (error) {
76 + console.error('获取课程列表失败:', error)
77 + }
78 + loading.value = false
54 } 79 }
55 </script> 80 </script>
......
...@@ -8,13 +8,13 @@ ...@@ -8,13 +8,13 @@
8 <!-- 固定区域:课程封面和标签页 --> 8 <!-- 固定区域:课程封面和标签页 -->
9 <div class="fixed top-0 left-0 right-0 z-10 top-wrapper bg-white"> 9 <div class="fixed top-0 left-0 right-0 z-10 top-wrapper bg-white">
10 <!-- 课程封面区域 --> 10 <!-- 课程封面区域 -->
11 - <van-image class="w-full aspect-video object-cover" :src="course?.coverImage" :alt="course?.title" /> 11 + <van-image class="w-full aspect-video object-cover" :src="course?.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'" :alt="course?.title" />
12 <div class="p-4"> 12 <div class="p-4">
13 <h1 class="text-black text-xl font-bold mb-2">{{ course?.title }}</h1> 13 <h1 class="text-black text-xl font-bold mb-2">{{ course?.title }}</h1>
14 <div class="flex items-center text-gray-500 text-sm"> 14 <div class="flex items-center text-gray-500 text-sm">
15 - <span>已更新 20期</span> 15 + <span>已更新 没有字段 期</span>
16 <span class="mx-2">|</span> 16 <span class="mx-2">|</span>
17 - <span>116人订阅</span> 17 + <span>没有字段 人订阅</span>
18 </div> 18 </div>
19 </div> 19 </div>
20 20
...@@ -38,32 +38,34 @@ ...@@ -38,32 +38,34 @@
38 :style="{ paddingTop: topWrapperHeight, height: 'calc(100vh - ' + topWrapperHeight + ')' }"> 38 :style="{ paddingTop: topWrapperHeight, height: 'calc(100vh - ' + topWrapperHeight + ')' }">
39 <!-- 详情区域 --> 39 <!-- 详情区域 -->
40 <div id="detail" class="py-4 px-4"> 40 <div id="detail" class="py-4 px-4">
41 - <div class="text-gray-700 text-sm leading-relaxed" v-html="course?.description"></div> 41 + <div v-if="course?.feature" class="text-gray-700 text-sm leading-relaxed" v-html="course?.feature"></div>
42 - <van-empty description="暂无详情" /> 42 + <div v-if="course?.highlights" class="text-gray-700 text-sm leading-relaxed" v-html="course?.highlights"></div>
43 + <van-empty v-else description="暂无详情" />
43 </div> 44 </div>
44 45
45 <div class="h-2 bg-gray-100"></div> 46 <div class="h-2 bg-gray-100"></div>
46 47
47 <!-- 目录区域 --> 48 <!-- 目录区域 -->
48 <div id="catalog" class="py-4"> 49 <div id="catalog" class="py-4">
49 - <div class="space-y-4"> 50 + <div v-if="course_lessons.length" class="space-y-4">
50 - <div v-for="(lesson, index) in course?.lessons" :key="index" 51 + <div v-for="(lesson, index) in course_lessons" :key="index"
51 @click="router.push(`/studyDetail/${lesson.id}`)" 52 @click="router.push(`/studyDetail/${lesson.id}`)"
52 class="bg-white p-4 cursor-pointer hover:bg-gray-50 transition-colors border-b border-gray-200 relative"> 53 class="bg-white p-4 cursor-pointer hover:bg-gray-50 transition-colors border-b border-gray-200 relative">
53 <div v-if="lesson.progress > 0 && lesson.progress < 100" 54 <div v-if="lesson.progress > 0 && lesson.progress < 100"
54 class="absolute top-2 right-2 px-2 py-1 bg-green-100 text-green-600 text-xs rounded"> 55 class="absolute top-2 right-2 px-2 py-1 bg-green-100 text-green-600 text-xs rounded">
55 - 上次看到</div> 56 + 没有字段上次看到</div>
56 <div class="text-black text-base font-medium mb-2">{{ lesson.title }}</div> 57 <div class="text-black text-base font-medium mb-2">{{ lesson.title }}</div>
57 <div class="flex items-center text-sm text-gray-500"> 58 <div class="flex items-center text-sm text-gray-500">
58 - <span>视频</span>&nbsp; 59 + <span>{{ course_type_maps[lesson.course_type] }}</span>&nbsp;
59 - <span>2024-10-22</span> 60 + <span>{{ dayjs(course.schedule_time).format('YYYY-MM-DD') }}</span>
60 <span class="mx-2">|</span> 61 <span class="mx-2">|</span>
61 - <span>1897次学习</span> 62 + <span>没有字段 次学习</span>
62 <span class="mx-2">|</span> 63 <span class="mx-2">|</span>
63 - <span>已学习{{ lesson.progress }}%</span> 64 + <span>没有字段 已学习{{ lesson?.progress }}%</span>
64 </div> 65 </div>
65 </div> 66 </div>
66 </div> 67 </div>
68 + <van-empty v-else description="暂无目录" />
67 </div> 69 </div>
68 70
69 <div class="h-2 bg-gray-100"></div> 71 <div class="h-2 bg-gray-100"></div>
...@@ -94,6 +96,10 @@ ...@@ -94,6 +96,10 @@
94 import { ref, onMounted, nextTick, onUnmounted } from 'vue'; 96 import { ref, onMounted, nextTick, onUnmounted } from 'vue';
95 import { useTitle } from '@vueuse/core'; 97 import { useTitle } from '@vueuse/core';
96 import { useRouter } from "vue-router"; 98 import { useRouter } from "vue-router";
99 +import dayjs from 'dayjs';
100 +
101 +// 导入接口
102 +import { getCourseDetailAPI } from '@/api/course'
97 103
98 const router = useRouter(); 104 const router = useRouter();
99 105
...@@ -103,6 +109,7 @@ useTitle('课程详情'); ...@@ -103,6 +109,7 @@ useTitle('课程详情');
103 // 当前激活的标签页 109 // 当前激活的标签页
104 const activeTab = ref('detail'); 110 const activeTab = ref('detail');
105 const topWrapperHeight = ref(0); 111 const topWrapperHeight = ref(0);
112 +const resizeObserver = ref(null);
106 113
107 // 计算topWrapperHeight的函数 114 // 计算topWrapperHeight的函数
108 const updateTopWrapperHeight = () => { 115 const updateTopWrapperHeight = () => {
...@@ -110,21 +117,38 @@ const updateTopWrapperHeight = () => { ...@@ -110,21 +117,38 @@ const updateTopWrapperHeight = () => {
110 const topWrapper = document.querySelector('.top-wrapper'); 117 const topWrapper = document.querySelector('.top-wrapper');
111 if (topWrapper) { 118 if (topWrapper) {
112 // 使用 ResizeObserver 监听元素尺寸变化 119 // 使用 ResizeObserver 监听元素尺寸变化
113 - const resizeObserver = new ResizeObserver(() => { 120 + resizeObserver.value = new ResizeObserver(() => {
114 topWrapperHeight.value = `${topWrapper.offsetHeight}px`; 121 topWrapperHeight.value = `${topWrapper.offsetHeight}px`;
115 }); 122 });
116 - resizeObserver.observe(topWrapper); 123 + resizeObserver.value.observe(topWrapper);
117 -
118 - // 组件卸载时取消监听
119 - onUnmounted(() => {
120 - resizeObserver.disconnect();
121 - });
122 } 124 }
123 }); 125 });
124 }; 126 };
125 127
126 -// 初始化时计算topWrapperHeight 128 +const course = ref([]);
127 -onMounted(() => { 129 +const course_lessons = ref([]);
130 +const course_type_maps = ref({
131 + video: '视频',
132 + audio: '录播课',
133 + image: '图片',
134 + file: '文件',
135 +})
136 +
137 +onMounted(async () => {
138 + /**
139 + * 组件挂载时获取课程详情
140 + */
141 + // 获取课程ID
142 + const courseId = router.currentRoute.value.params.id;
143 + // 调用接口获取课程详情
144 + const { code, data } = await getCourseDetailAPI({ i: courseId });
145 + if (code) {
146 + course.value = data;
147 + course_lessons.value = data.schedule || [];
148 + }
149 + /**
150 + * 初始化时计算topWrapperHeight
151 + */
128 // 添加滚动监听 152 // 添加滚动监听
129 window.addEventListener('scroll', handleScroll); 153 window.addEventListener('scroll', handleScroll);
130 // 添加窗口大小变化监听 154 // 添加窗口大小变化监听
...@@ -137,6 +161,8 @@ onMounted(() => { ...@@ -137,6 +161,8 @@ onMounted(() => {
137 onUnmounted(() => { 161 onUnmounted(() => {
138 window.removeEventListener('scroll', handleScroll); 162 window.removeEventListener('scroll', handleScroll);
139 window.removeEventListener('resize', updateTopWrapperHeight); 163 window.removeEventListener('resize', updateTopWrapperHeight);
164 + // 组件卸载时取消监听
165 + resizeObserver.value.disconnect();
140 }); 166 });
141 167
142 // 处理滚动事件 168 // 处理滚动事件
...@@ -175,30 +201,30 @@ const handleTabChange = (name) => { ...@@ -175,30 +201,30 @@ const handleTabChange = (name) => {
175 }; 201 };
176 202
177 // 课程数据 203 // 课程数据
178 -const course = ref({ 204 +// const course = ref({
179 - title: '开学礼·止的智慧·心法老师·20241001', 205 +// title: '开学礼·止的智慧·心法老师·20241001',
180 - coverImage: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg', 206 +// coverImage: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
181 - updateTime: '2024.01.17', 207 +// updateTime: '2024.01.17',
182 - viewCount: 1897, 208 +// viewCount: 1897,
183 - description: '这是一门关于心法的课程,帮助学员掌握止的智慧...', 209 +// description: '这是一门关于心法的课程,帮助学员掌握止的智慧...',
184 - lessons: [ 210 +// lessons: [
185 - { 211 +// {
186 - title: '第一课:止的基础', 212 +// title: '第一课:止的基础',
187 - duration: '45分钟', 213 +// duration: '45分钟',
188 - progress: 100 214 +// progress: 100
189 - }, 215 +// },
190 - { 216 +// {
191 - title: '第二课:止的技巧', 217 +// title: '第二课:止的技巧',
192 - duration: '50分钟', 218 +// duration: '50分钟',
193 - progress: 60 219 +// progress: 60
194 - }, 220 +// },
195 - { 221 +// {
196 - title: '第三课:止的应用', 222 +// title: '第三课:止的应用',
197 - duration: '40分钟', 223 +// duration: '40分钟',
198 - progress: 0 224 +// progress: 0
199 - } 225 +// }
200 - ] 226 +// ]
201 -}); 227 +// });
202 </script> 228 </script>
203 229
204 <style scoped> 230 <style scoped>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
8 <!-- 固定区域:视频播放和标签页 --> 8 <!-- 固定区域:视频播放和标签页 -->
9 <div class="fixed top-0 left-0 right-0 z-10 top-wrapper"> 9 <div class="fixed top-0 left-0 right-0 z-10 top-wrapper">
10 <!-- 视频播放区域 --> 10 <!-- 视频播放区域 -->
11 - <div v-if="course.type === 'video'" class="w-full bg-black relative"> 11 + <div v-if="course.course_type === 'video'" class="w-full bg-black relative">
12 <!-- 视频封面和播放按钮 --> 12 <!-- 视频封面和播放按钮 -->
13 <div v-if="!isPlaying" class="relative w-full" style="aspect-ratio: 16/9;"> 13 <div v-if="!isPlaying" class="relative w-full" style="aspect-ratio: 16/9;">
14 <img :src="course.cover" :alt="course.title" class="w-full h-full object-cover" /> 14 <img :src="course.cover" :alt="course.title" class="w-full h-full object-cover" />
...@@ -22,10 +22,10 @@ ...@@ -22,10 +22,10 @@
22 </div> 22 </div>
23 </div> 23 </div>
24 <!-- 视频播放器 --> 24 <!-- 视频播放器 -->
25 - <VideoPlayer v-show="isPlaying" ref="videoPlayerRef" :video-url="course.videoUrl" :autoplay="false" 25 + <VideoPlayer v-show="isPlaying" ref="videoPlayerRef" :video-url="course.file" :autoplay="false"
26 @onPlay="handleVideoPlay" @onPause="handleVideoPause" /> 26 @onPlay="handleVideoPlay" @onPause="handleVideoPause" />
27 </div> 27 </div>
28 - <div v-if="course.type === 'audio'" class="w-full relative" style="border-bottom: 1px solid #F3F4F6;"> 28 + <div v-if="course.course_type === 'audio'" class="w-full relative" style="border-bottom: 1px solid #F3F4F6;">
29 <!-- 音频播放器 --> 29 <!-- 音频播放器 -->
30 <AudioPlayer :songs="audioList" /> 30 <AudioPlayer :songs="audioList" />
31 </div> 31 </div>
...@@ -47,9 +47,9 @@ ...@@ -47,9 +47,9 @@
47 <div id="intro" class="py-4 px-4"> 47 <div id="intro" class="py-4 px-4">
48 <h1 class="text-lg font-bold mb-2">{{ course.title }}</h1> 48 <h1 class="text-lg font-bold mb-2">{{ course.title }}</h1>
49 <div class="text-gray-500 text-sm flex items-center gap-2"> 49 <div class="text-gray-500 text-sm flex items-center gap-2">
50 - <span>{{ course.date }}</span> 50 + <span>{{ dayjs(course.schedule_time).format('YYYY-MM-DD') }}</span>
51 <span class="text-gray-300">|</span> 51 <span class="text-gray-300">|</span>
52 - <span>{{ course.studyCount || 0 }}次学习</span> 52 + <span>没有字段{{ course.studyCount || 0 }}次学习</span>
53 </div> 53 </div>
54 </div> 54 </div>
55 55
...@@ -160,6 +160,10 @@ import { useRoute } from 'vue-router'; ...@@ -160,6 +160,10 @@ import { useRoute } from 'vue-router';
160 import { useTitle } from '@vueuse/core'; 160 import { useTitle } from '@vueuse/core';
161 import VideoPlayer from '@/components/ui/VideoPlayer.vue'; 161 import VideoPlayer from '@/components/ui/VideoPlayer.vue';
162 import AudioPlayer from '@/components/ui/AudioPlayer.vue'; 162 import AudioPlayer from '@/components/ui/AudioPlayer.vue';
163 +import dayjs from 'dayjs';
164 +
165 +// 导入接口
166 +import { getScheduleCourseAPI } from '@/api/course';
163 167
164 const route = useRoute(); 168 const route = useRoute();
165 const course = ref(null); 169 const course = ref(null);
...@@ -329,7 +333,9 @@ const handleScroll = () => { ...@@ -329,7 +333,9 @@ const handleScroll = () => {
329 } 333 }
330 }; 334 };
331 335
332 -onMounted(() => { 336 +onMounted(async () => {
337 + // 延迟设置topWrapper和bottomWrapper的高度
338 + setTimeout(() => {
333 nextTick(() => { 339 nextTick(() => {
334 const topWrapper = document.querySelector('.top-wrapper'); 340 const topWrapper = document.querySelector('.top-wrapper');
335 const bottomWrapper = document.querySelector('.bottom-wrapper'); 341 const bottomWrapper = document.querySelector('.bottom-wrapper');
...@@ -343,29 +349,19 @@ onMounted(() => { ...@@ -343,29 +349,19 @@ onMounted(() => {
343 // 添加滚动监听 349 // 添加滚动监听
344 window.addEventListener('scroll', handleScroll); 350 window.addEventListener('scroll', handleScroll);
345 }) 351 })
352 + }, 500);
353 +
346 const courseId = route.params.id; 354 const courseId = route.params.id;
347 if (courseId) { 355 if (courseId) {
348 - // TODO: 这里需要替换为实际的API调用 356 + const { code, data } = await getScheduleCourseAPI({ i: courseId });
349 - // 临时使用模拟数据 357 + if (code) {
350 - course.value = { 358 + course.value = data;
351 - id: courseId, 359 + // TODO: 测试数据
352 - title: '开学礼·止的智慧·心法老师·20241001(上)', 360 + course.value.cover = 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg';
353 - videoUrl: 'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4', 361 + // 音频列表处理
354 - progress: 35, 362 + // if (data.course_type === 'audio') {
355 - studyTime: 3600, 363 + // audioList.value = [course.value.file];
356 - type: '视频课程', 364 + // }
357 - studyCount: 1896,
358 - cover: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
359 - date: '2024-12-04',
360 - type: 'video',
361 - };
362 -
363 - // TODO: 模拟数据音频和视频显示
364 - console.warn(courseId);
365 - if (courseId == '2') {
366 - course.value.type = 'audio'
367 - } else {
368 - course.value.type = 'video'
369 } 365 }
370 } 366 }
371 }) 367 })
......