catalogue.vue 5.6 KB

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