catalogue.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <template>
  2. <view>
  3. <uni-collapse class="chapter-collapse" accordion>
  4. <uni-collapse-item v-for="(zhangItem, zhangIndex) in props.options.zhangList" :key="zhangIndex"
  5. :title="zhangItem.name" :open="isCurrentZhang(zhangIndex)">
  6. <uni-collapse class="chapter-collapse" accordion>
  7. <uni-collapse-item v-for="(jieItem, jieIndex) in zhangItem.jieList" :key="'jie-'+jieIndex"
  8. :title="jieItem.name" :open="isCurrentJie(zhangIndex,jieIndex)">
  9. <view class="section-header">{{ jieItem.name }}</view>
  10. <view v-for="(video, vidIndex) in jieItem.kejianList" :key="'vid-'+video.kjId"
  11. class="video-item" @click="handleVideoClick(zhangIndex, jieIndex, vidIndex,video)">
  12. <view class="video-info">
  13. <text class="video-name"
  14. :class="{ active: isCurrentVideo(zhangIndex, jieIndex, vidIndex) }">
  15. {{ video.name }}
  16. </text>
  17. <view class="progress-container">
  18. <progress :percent="calcProgress(video)" stroke-width="4"
  19. :activeColor="video.curProcess >= 100 ? '#4cd964' : '#007aff'" />
  20. </view>
  21. </view>
  22. </view>
  23. </uni-collapse-item>
  24. </uni-collapse>
  25. </uni-collapse-item>
  26. </uni-collapse>
  27. </view>
  28. </template>
  29. <script setup>
  30. import {
  31. getKechengList,
  32. kejianInfo
  33. } from "@/api/course.js";
  34. import {
  35. onLoad,
  36. onReady
  37. } from "@dcloudio/uni-app"
  38. import {
  39. ref,
  40. reactive,
  41. defineProps,
  42. defineEmits,
  43. onMounted,
  44. watchEffect,
  45. defineExpose
  46. } from 'vue';
  47. const props = defineProps({
  48. options: {
  49. type: Object,
  50. default: () => {}
  51. },
  52. currentVideo: {
  53. type: Object,
  54. default: () => {}
  55. },
  56. })
  57. let currentPos = ref({
  58. zhangIndex: -1,
  59. jieIndex: -1,
  60. vidIndex: -1
  61. });
  62. const Emits = defineEmits(['kejianInfo', 'playNext']);
  63. // 初始化时定位第一个未完成视频
  64. onMounted(() => {
  65. loadProgressFromCache();
  66. findFirstUnfinished();
  67. });
  68. const loadProgressFromCache = () => {
  69. props.options.zhangList.forEach(zhang => {
  70. zhang.jieList.forEach(jie => {
  71. jie.kejianList.forEach(video => {
  72. const cached = uni.getStorageSync(`video_${video.kjId}`);
  73. if (cached) {
  74. video.curProcess = (cached / uni.getStorageSync(
  75. `duration_${video.kjId}`)) * 100 || 0;
  76. video.maxProcess = Math.max(video.maxProcess, video.curProcess);
  77. }
  78. });
  79. });
  80. });
  81. };
  82. // 查找第一个未完成视频
  83. const findFirstUnfinished = () => {
  84. for (let z = 0; z < props.options.zhangList.length; z++) {
  85. const zhang = props.options.zhangList[z];
  86. for (let j = 0; j < zhang.jieList.length; j++) {
  87. const jie = zhang.jieList[j];
  88. for (let v = 0; v < jie.kejianList.length; v++) {
  89. const video = jie.kejianList[v];
  90. if (video.curProcess < 100) {
  91. updateCurrentPosition(z, j, v, video);
  92. return;
  93. }
  94. }
  95. }
  96. }
  97. };
  98. // 判断是否当前章
  99. const isCurrentZhang = (zhangIndex) => {
  100. return currentPos.value.zhangIndex === zhangIndex;
  101. };
  102. // 判断是否当前节(需要同时匹配章和节)
  103. const isCurrentJie = (zhangIndex, jieIndex) => {
  104. return currentPos.value.zhangIndex === zhangIndex &&
  105. currentPos.value.jieIndex === jieIndex;
  106. };
  107. // 判断是否当前视频
  108. const isCurrentVideo = (z, j, v) => {
  109. return currentPos.value.zhangIndex === z &&
  110. currentPos.value.jieIndex === j &&
  111. currentPos.value.vidIndex === v;
  112. };
  113. // 计算进度
  114. const calcProgress = (video) => {
  115. return Math.min(video.curProcess, 100);
  116. };
  117. // 点击视频处理
  118. const handleVideoClick = (z, j, v, video) => {
  119. console.log('video', video);
  120. // currentPos.value = {
  121. // zhangIndex: z,
  122. // jieIndex: j,
  123. // vidIndex: v
  124. // };
  125. updateCurrentPosition(z, j, v, video);
  126. kejianInfo({
  127. kjId: video.kjId
  128. }).then(res => {
  129. console.log('res', res);
  130. Emits('kejianInfo', {
  131. ...res.data,
  132. kjId: video.kjId
  133. })
  134. })
  135. // 这里添加实际播放逻辑
  136. };
  137. // 更新播放位置
  138. const updateCurrentPosition = (z, j, v, video) => {
  139. currentPos.value = {
  140. zhangIndex: z,
  141. jieIndex: j,
  142. vidIndex: v
  143. };
  144. video.curProcess = uni.getStorageSync(`video_${video.kjId}`) /
  145. uni.getStorageSync(`duration_${video.kjId}`) * 100 || 0;
  146. };
  147. // 播放下一个视频
  148. let playNextVideo = () => {
  149. let {
  150. zhangIndex,
  151. jieIndex,
  152. vidIndex
  153. } = currentPos.value;
  154. const chapters = props.options.zhangList;
  155. // 1. 同节的下一个视频
  156. const currentSection = chapters[zhangIndex].jieList[jieIndex];
  157. if (vidIndex < currentSection.kejianList.length - 1) {
  158. vidIndex++;
  159. return playVideoAt(zhangIndex, jieIndex, vidIndex);
  160. }
  161. // 2. 同章的下一节
  162. const currentChapter = chapters[zhangIndex];
  163. if (jieIndex < currentChapter.jieList.length - 1) {
  164. for (let j = jieIndex + 1; j < currentChapter.jieList.length; j++) {
  165. if (currentChapter.jieList[j].kejianList.length > 0) {
  166. return playVideoAt(zhangIndex, j, 0);
  167. }
  168. }
  169. }
  170. // 3. 后续章节
  171. for (let z = zhangIndex + 1; z < chapters.length; z++) {
  172. for (let j = 0; j < chapters[z].jieList.length; j++) {
  173. if (chapters[z].jieList[j].kejianList.length > 0) {
  174. return playVideoAt(z, j, 0);
  175. }
  176. }
  177. }
  178. uni.showToast({
  179. title: '已播放所有课程',
  180. icon: 'none'
  181. });
  182. };
  183. // 辅助方法
  184. const playVideoAt = (z, j, v) => {
  185. const video = props.options.zhangList[z].jieList[j].kejianList[v];
  186. updateCurrentPosition(z, j, v, video);
  187. Emits('kejianInfo', {
  188. ...video,
  189. kjId: video.kjId
  190. });
  191. };
  192. defineExpose({
  193. playNextVideo
  194. });
  195. </script>
  196. <style lang="scss">
  197. </style>