kechengInfo.vue 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. <template>
  2. <view class="course-container">
  3. <!-- 视频播放器 -->
  4. <video v-if="currentVideo.url" :src="currentVideo.url" :id="videoId" controls @ended="handleVideoEnd"
  5. @timeupdate="handleTimeUpdate" @loadedmetadata="handleVideoLoaded">
  6. </video>
  7. <!-- 课程信息 -->
  8. <view class="course-header">
  9. <text class="course-title">测试课程1</text>
  10. <view class="course-meta">
  11. <text>讲师:张老师</text>
  12. <text>时长:3分42秒</text>
  13. <text>1人学习</text>
  14. </view>
  15. </view>
  16. <!-- 选项卡 -->
  17. <view class="course-tabs">
  18. <text :class="['tab-item', activeTab === '目录' ? 'active' : '']" @click="activeTab = '目录'">目录</text>
  19. <text :class="['tab-item', activeTab === '介绍' ? 'active' : '']" @click="activeTab = '介绍'">介绍</text>
  20. <text :class="['tab-item', activeTab === '评论' ? 'active' : '']" @click="activeTab = '评论'">评论</text>
  21. <text :class="['tab-item', activeTab === '考试' ? 'active' : '']" @click="activeTab = '考试'">考试</text>
  22. </view>
  23. <!-- 目录内容 -->
  24. <view v-if="activeTab === '目录'" class="course-content">
  25. <catalogue ref="catalogueRef" @kejianInfo='getKejianInfo' :options="kejianUserVo"
  26. :current-video="currentVideo"></catalogue>
  27. </view>
  28. </view>
  29. </template>
  30. <script setup>
  31. import {
  32. ref,
  33. onMounted
  34. } from 'vue';
  35. import {
  36. onLoad,
  37. onReady,
  38. } from "@dcloudio/uni-app"
  39. import {
  40. kechengStart
  41. } from "@/api/course.js";
  42. import catalogue from './common/catalogue.vue';
  43. // 选项卡状态
  44. const activeTab = ref('');
  45. let kcId = ref('');
  46. let kejianUserVo = ref(null);
  47. // 当前播放信息
  48. const currentVideo = ref({
  49. url: '',
  50. kjId: ''
  51. });
  52. const currentChapter = ref(0);
  53. const currentSection = ref(0);
  54. const catalogueRef = ref(null);
  55. const videoId = ref('courseVideo');
  56. let videoContext = null;
  57. onLoad((options) => {
  58. console.log('options', options);
  59. kcId.value = options.kcId
  60. getKechengData()
  61. });
  62. onMounted(() => {
  63. });
  64. function getKechengData() {
  65. kechengStart({
  66. kcId: kcId.value
  67. }).then(res => {
  68. console.log('res', res);
  69. kejianUserVo.value = res.data.kejianUserVo
  70. activeTab.value = '目录'
  71. })
  72. }
  73. function getKejianInfo(data) {
  74. console.log('data', data);
  75. // currentVideo.value = {
  76. // url: data.url || 'https://www.w3school.com.cn/example/html5/mov_bbb.mp4',
  77. // kjId: data.kjId
  78. // };
  79. currentVideo.value = {
  80. url: 'https://www.w3school.com.cn/example/html5/mov_bbb.mp4',
  81. kjId: data.kjId
  82. };
  83. setTimeout(() => {
  84. if (!videoContext) {
  85. videoContext = uni.createVideoContext(videoId.value, this);
  86. }
  87. const cachedTime = uni.getStorageSync(`video_${data.kjId}`) || 0;
  88. videoContext.seek(cachedTime);
  89. }, 300);
  90. }
  91. const handleTimeUpdate = (e) => {
  92. const currentTime = e.detail.currentTime;
  93. uni.setStorageSync(`video_${currentVideo.value.kjId}`, currentTime);
  94. };
  95. const handleVideoEnd = () => {
  96. catalogueRef.value.playNextVideo();
  97. };
  98. const handleVideoLoaded = (e) => {
  99. uni.setStorageSync(`duration_${currentVideo.value.kjId}`, e.detail.duration);
  100. };
  101. </script>
  102. <style scoped>
  103. .course-container {
  104. display: flex;
  105. flex-direction: column;
  106. height: 100vh;
  107. }
  108. .course-header {
  109. padding: 20rpx;
  110. background: #f5f5f5;
  111. }
  112. .course-title {
  113. font-size: 36rpx;
  114. font-weight: bold;
  115. }
  116. .course-meta {
  117. margin-top: 10rpx;
  118. font-size: 24rpx;
  119. color: #666;
  120. }
  121. .course-tabs {
  122. display: flex;
  123. justify-content: space-around;
  124. padding: 20rpx;
  125. background: #fff;
  126. border-bottom: 1rpx solid #ddd;
  127. }
  128. .tab-item {
  129. font-size: 28rpx;
  130. color: #666;
  131. }
  132. .tab-item.active {
  133. color: #09BB07;
  134. font-weight: bold;
  135. }
  136. .course-content {
  137. flex: 1;
  138. overflow-y: auto;
  139. padding: 20rpx;
  140. }
  141. .chapter-list {
  142. background: #fff;
  143. }
  144. .chapter-item {
  145. margin-bottom: 20rpx;
  146. }
  147. .chapter-title {
  148. padding: 20rpx;
  149. background: #f5f5f5;
  150. display: flex;
  151. justify-content: space-between;
  152. border-bottom: 1rpx solid #ddd;
  153. }
  154. .section-list {
  155. background: #fff;
  156. }
  157. .section-item {
  158. padding: 20rpx;
  159. display: flex;
  160. justify-content: space-between;
  161. border-bottom: 1rpx solid #eee;
  162. }
  163. .section-info {
  164. flex: 1;
  165. }
  166. .section-title {
  167. display: block;
  168. font-size: 28rpx;
  169. color: #333;
  170. }
  171. .play-status {
  172. width: 180rpx;
  173. text-align: right;
  174. }
  175. .playing {
  176. color: #09BB07;
  177. font-size: 24rpx;
  178. }
  179. .progress {
  180. color: #666;
  181. font-size: 24rpx;
  182. }
  183. .unplayed {
  184. color: #999;
  185. font-size: 24rpx;
  186. }
  187. video {
  188. width: 100%;
  189. height: 300rpx;
  190. background: #000;
  191. }
  192. </style>