catalogue.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  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. // 加载缓存进度
  69. const loadProgressFromCache = () => {
  70. props.options.zhangList.forEach(zhang => {
  71. zhang.jieList.forEach(jie => {
  72. jie.kejianList.forEach(video => {
  73. const cached = uni.getStorageSync(`video_${video.kjId}`);
  74. if (cached) {
  75. video.curProcess = (cached / uni.getStorageSync(
  76. `duration_${video.kjId}`)) * 100 || 0;
  77. video.maxProcess = Math.max(video.maxProcess, video.curProcess);
  78. }
  79. });
  80. });
  81. });
  82. };
  83. // 查找第一个未完成视频
  84. const findFirstUnfinished = () => {
  85. for (let z = 0; z < props.options.zhangList.length; z++) {
  86. const zhang = props.options.zhangList[z];
  87. for (let j = 0; j < zhang.jieList.length; j++) {
  88. const jie = zhang.jieList[j];
  89. for (let v = 0; v < jie.kejianList.length; v++) {
  90. const video = jie.kejianList[v];
  91. if (video.curProcess < 100) {
  92. updateCurrentPosition(z, j, v, video);
  93. return;
  94. }
  95. }
  96. }
  97. }
  98. };
  99. // 判断是否当前章
  100. const isCurrentZhang = (zhangIndex) => {
  101. return currentPos.value.zhangIndex === zhangIndex;
  102. };
  103. // 判断是否当前节(需要同时匹配章和节)
  104. const isCurrentJie = (zhangIndex, jieIndex) => {
  105. return currentPos.value.zhangIndex === zhangIndex &&
  106. currentPos.value.jieIndex === jieIndex;
  107. };
  108. // 判断是否当前视频
  109. const isCurrentVideo = (z, j, v) => {
  110. return currentPos.value.zhangIndex === z &&
  111. currentPos.value.jieIndex === j &&
  112. currentPos.value.vidIndex === v;
  113. };
  114. // 计算进度
  115. const calcProgress = (video) => {
  116. return Math.min(video.curProcess, 100);
  117. };
  118. // 点击视频处理
  119. const handleVideoClick = (z, j, v, video) => {
  120. console.log('video', video);
  121. // currentPos.value = {
  122. // zhangIndex: z,
  123. // jieIndex: j,
  124. // vidIndex: v
  125. // };
  126. updateCurrentPosition(z, j, v, video);
  127. kejianInfo({
  128. kjId: video.kjId
  129. }).then(res => {
  130. console.log('res', res);
  131. Emits('kejianInfo', {
  132. ...res.data,
  133. kjId: video.kjId
  134. })
  135. })
  136. // 这里添加实际播放逻辑
  137. };
  138. // 更新播放位置
  139. const updateCurrentPosition = (z, j, v, video) => {
  140. currentPos.value = {
  141. zhangIndex: z,
  142. jieIndex: j,
  143. vidIndex: v
  144. };
  145. video.curProcess = uni.getStorageSync(`video_${video.kjId}`) /
  146. uni.getStorageSync(`duration_${video.kjId}`) * 100 || 0;
  147. };
  148. // 播放下一个视频
  149. let playNextVideo = () => {
  150. let {
  151. zhangIndex,
  152. jieIndex,
  153. vidIndex
  154. } = currentPos.value;
  155. const chapters = props.options.zhangList;
  156. // 1. 同节的下一个视频
  157. const currentSection = chapters[zhangIndex].jieList[jieIndex];
  158. if (vidIndex < currentSection.kejianList.length - 1) {
  159. vidIndex++;
  160. return playVideoAt(zhangIndex, jieIndex, vidIndex);
  161. }
  162. // 2. 同章的下一节
  163. const currentChapter = chapters[zhangIndex];
  164. if (jieIndex < currentChapter.jieList.length - 1) {
  165. for (let j = jieIndex + 1; j < currentChapter.jieList.length; j++) {
  166. if (currentChapter.jieList[j].kejianList.length > 0) {
  167. return playVideoAt(zhangIndex, j, 0);
  168. }
  169. }
  170. }
  171. // 3. 后续章节
  172. for (let z = zhangIndex + 1; z < chapters.length; z++) {
  173. for (let j = 0; j < chapters[z].jieList.length; j++) {
  174. if (chapters[z].jieList[j].kejianList.length > 0) {
  175. return playVideoAt(z, j, 0);
  176. }
  177. }
  178. }
  179. uni.showToast({
  180. title: '已播放所有课程',
  181. icon: 'none'
  182. });
  183. };
  184. // 辅助方法
  185. const playVideoAt = (z, j, v) => {
  186. const video = props.options.zhangList[z].jieList[j].kejianList[v];
  187. updateCurrentPosition(z, j, v, video);
  188. Emits('kejianInfo', {
  189. ...video,
  190. kjId: video.kjId
  191. });
  192. };
  193. defineExpose({
  194. playNextVideo
  195. });
  196. </script>
  197. <style lang="scss">
  198. </style>