wangguoyu 1 månad sedan
förälder
incheckning
3c45f4426e
2 ändrade filer med 216 tillägg och 10 borttagningar
  1. 210 0
      pages/course/common/catalogue.vue
  2. 6 10
      pages/course/kechengInfo.vue

+ 210 - 0
pages/course/common/catalogue.vue

@@ -0,0 +1,210 @@
+<template>
+	<view>
+		<uni-collapse class="chapter-collapse" accordion>
+			<uni-collapse-item v-for="(zhangItem, zhangIndex) in props.options.zhangList" :key="zhangIndex"
+				:title="zhangItem.name" :open="isCurrentZhang(zhangIndex)">
+				<uni-collapse class="chapter-collapse" accordion>
+					<uni-collapse-item v-for="(jieItem, jieIndex) in zhangItem.jieList" :key="'jie-'+jieIndex"
+						:title="jieItem.name" :open="isCurrentJie(zhangIndex,jieIndex)">
+						<view class="section-header">{{ jieItem.name }}</view>
+						<view v-for="(video, vidIndex) in jieItem.kejianList" :key="'vid-'+video.kjId"
+							class="video-item" @click="handleVideoClick(zhangIndex, jieIndex, vidIndex,video)">
+							<view class="video-info">
+								<text class="video-name"
+									:class="{ active: isCurrentVideo(zhangIndex, jieIndex, vidIndex) }">
+									{{ video.name }}
+								</text>
+								<view class="progress-container">
+									<progress :percent="calcProgress(video)" stroke-width="4"
+										:activeColor="video.curProcess >= 100 ? '#4cd964' : '#007aff'" />
+								</view>
+							</view>
+						</view>
+					</uni-collapse-item>
+				</uni-collapse>
+			</uni-collapse-item>
+		</uni-collapse>
+	</view>
+</template>
+
+<script setup>
+	import {
+		getKechengList,
+		kejianInfo
+	} from "@/api/course.js";
+	import {
+		onLoad,
+		onReady
+	} from "@dcloudio/uni-app"
+
+	import {
+		ref,
+		reactive,
+		defineProps,
+		defineEmits,
+		onMounted,
+		watchEffect,
+		defineExpose
+	} from 'vue';
+	const props = defineProps({
+		options: {
+			type: Object,
+			default: () => {}
+		},
+		currentVideo: {
+			type: Object,
+			default: () => {}
+		},
+
+	})
+	let currentPos = ref({
+		zhangIndex: -1,
+		jieIndex: -1,
+		vidIndex: -1
+	});
+	const Emits = defineEmits(['kejianInfo', 'playNext']);
+	// 初始化时定位第一个未完成视频
+	onMounted(() => {
+		loadProgressFromCache();
+		findFirstUnfinished();
+	});
+	const loadProgressFromCache = () => {
+		props.options.zhangList.forEach(zhang => {
+			zhang.jieList.forEach(jie => {
+				jie.kejianList.forEach(video => {
+					const cached = uni.getStorageSync(`video_${video.kjId}`);
+					if (cached) {
+						video.curProcess = (cached / uni.getStorageSync(
+							`duration_${video.kjId}`)) * 100 || 0;
+						video.maxProcess = Math.max(video.maxProcess, video.curProcess);
+					}
+				});
+			});
+		});
+	};
+	// 查找第一个未完成视频
+	const findFirstUnfinished = () => {
+		for (let z = 0; z < props.options.zhangList.length; z++) {
+			const zhang = props.options.zhangList[z];
+			for (let j = 0; j < zhang.jieList.length; j++) {
+				const jie = zhang.jieList[j];
+				for (let v = 0; v < jie.kejianList.length; v++) {
+					const video = jie.kejianList[v];
+					if (video.curProcess < 100) {
+						updateCurrentPosition(z, j, v, video);
+						return;
+					}
+				}
+			}
+		}
+	};
+	// 判断是否当前章
+	const isCurrentZhang = (zhangIndex) => {
+		return currentPos.value.zhangIndex === zhangIndex;
+	};
+
+	// 判断是否当前节(需要同时匹配章和节)
+	const isCurrentJie = (zhangIndex, jieIndex) => {
+		return currentPos.value.zhangIndex === zhangIndex &&
+			currentPos.value.jieIndex === jieIndex;
+	};
+
+	// 判断是否当前视频
+	const isCurrentVideo = (z, j, v) => {
+		return currentPos.value.zhangIndex === z &&
+			currentPos.value.jieIndex === j &&
+			currentPos.value.vidIndex === v;
+	};
+
+	// 计算进度
+	const calcProgress = (video) => {
+		return Math.min(video.curProcess, 100);
+	};
+
+	// 点击视频处理
+	const handleVideoClick = (z, j, v, video) => {
+		console.log('video', video);
+		// currentPos.value = {
+		// 	zhangIndex: z,
+		// 	jieIndex: j,
+		// 	vidIndex: v
+		// };
+		updateCurrentPosition(z, j, v, video);
+		kejianInfo({
+			kjId: video.kjId
+		}).then(res => {
+			console.log('res', res);
+			Emits('kejianInfo', {
+				...res.data,
+				kjId: video.kjId
+			})
+		})
+		// 这里添加实际播放逻辑
+	};
+	// 更新播放位置
+	const updateCurrentPosition = (z, j, v, video) => {
+		currentPos.value = {
+			zhangIndex: z,
+			jieIndex: j,
+			vidIndex: v
+		};
+		video.curProcess = uni.getStorageSync(`video_${video.kjId}`) /
+			uni.getStorageSync(`duration_${video.kjId}`) * 100 || 0;
+	};
+	// 播放下一个视频
+	let playNextVideo = () => {
+		let {
+			zhangIndex,
+			jieIndex,
+			vidIndex
+		} = currentPos.value;
+		const chapters = props.options.zhangList;
+
+		// 1. 同节的下一个视频
+		const currentSection = chapters[zhangIndex].jieList[jieIndex];
+		if (vidIndex < currentSection.kejianList.length - 1) {
+			vidIndex++;
+			return playVideoAt(zhangIndex, jieIndex, vidIndex);
+		}
+
+		// 2. 同章的下一节
+		const currentChapter = chapters[zhangIndex];
+		if (jieIndex < currentChapter.jieList.length - 1) {
+			for (let j = jieIndex + 1; j < currentChapter.jieList.length; j++) {
+				if (currentChapter.jieList[j].kejianList.length > 0) {
+					return playVideoAt(zhangIndex, j, 0);
+				}
+			}
+		}
+
+		// 3. 后续章节
+		for (let z = zhangIndex + 1; z < chapters.length; z++) {
+			for (let j = 0; j < chapters[z].jieList.length; j++) {
+				if (chapters[z].jieList[j].kejianList.length > 0) {
+					return playVideoAt(z, j, 0);
+				}
+			}
+		}
+
+		uni.showToast({
+			title: '已播放所有课程',
+			icon: 'none'
+		});
+	};
+	// 辅助方法
+	const playVideoAt = (z, j, v) => {
+		const video = props.options.zhangList[z].jieList[j].kejianList[v];
+		updateCurrentPosition(z, j, v, video);
+		Emits('kejianInfo', {
+			...video,
+			kjId: video.kjId
+		});
+	};
+	defineExpose({
+	  playNextVideo
+	});
+</script>
+
+<style lang="scss">
+
+</style>

+ 6 - 10
pages/course/kechengInfo.vue

@@ -14,7 +14,6 @@
 				<text>1人学习</text>
 			</view>
 		</view>
-
 		<!-- 选项卡 -->
 		<view class="course-tabs">
 			<text :class="['tab-item', activeTab === '目录' ? 'active' : '']" @click="activeTab = '目录'">目录</text>
@@ -22,12 +21,10 @@
 			<text :class="['tab-item', activeTab === '评论' ? 'active' : '']" @click="activeTab = '评论'">评论</text>
 			<text :class="['tab-item', activeTab === '考试' ? 'active' : '']" @click="activeTab = '考试'">考试</text>
 		</view>
-
 		<!-- 目录内容 -->
-		<view v-if="activeTab === '目录'" class="course-content">
-			<catalogue ref="catalogueRef" @kejianInfo='getKejianInfo' :options="kejianUserVo" 
+		<view v-if="activeTab == '目录'" class="course-content">
+			<catalogue  ref="catalogueRef"  @kejianInfo='getKejianInfo' :options="kejianUserVo"
 				:current-video="currentVideo"></catalogue>
-
 		</view>
 
 	</view>
@@ -64,10 +61,10 @@
 	onLoad((options) => {
 		console.log('options', options);
 		kcId.value = options.kcId
-		getKechengData()
+
 	});
 	onMounted(() => {
-
+		getKechengData()
 	});
 
 	function getKechengData() {
@@ -75,8 +72,8 @@
 			kcId: kcId.value
 		}).then(res => {
 			console.log('res', res);
-			kejianUserVo.value = res.data.kejianUserVo
 			activeTab.value = '目录'
+			kejianUserVo.value = res.data.kejianUserVo
 		})
 	}
 
@@ -90,7 +87,7 @@
 			url: 'https://www.w3school.com.cn/example/html5/mov_bbb.mp4',
 			kjId: data.kjId
 		};
-		
+
 		setTimeout(() => {
 			if (!videoContext) {
 				videoContext = uni.createVideoContext(videoId.value, this);
@@ -109,7 +106,6 @@
 	const handleVideoEnd = () => {
 		catalogueRef.value.playNextVideo();
 	};
-
 	const handleVideoLoaded = (e) => {
 		uni.setStorageSync(`duration_${currentVideo.value.kjId}`, e.detail.duration);
 	};