|
@@ -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>
|