kechengInfo.vue 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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. });
  61. onMounted(() => {
  62. getKechengData()
  63. });
  64. function getKechengData() {
  65. kechengStart({
  66. kcId: kcId.value
  67. }).then(res => {
  68. console.log('res', res);
  69. activeTab.value = '目录'
  70. kejianUserVo.value = res.data.kejianUserVo
  71. })
  72. }
  73. function getKejianInfo(data) {
  74. console.log('data', data.url);
  75. currentVideo.value = {
  76. url: data.url,
  77. kjId: data.kjId
  78. };
  79. setTimeout(() => {
  80. if (!videoContext) {
  81. videoContext = uni.createVideoContext(videoId.value, this);
  82. }
  83. const cachedTime = uni.getStorageSync(`video_${data.kjId}`) || 0;
  84. videoContext.seek(cachedTime);
  85. }, 300);
  86. }
  87. const handleTimeUpdate = (e) => {
  88. const currentTime = e.detail.currentTime;
  89. uni.setStorageSync(`video_${currentVideo.value.kjId}`, currentTime);
  90. };
  91. const handleVideoEnd = () => {
  92. catalogueRef.value.playNextVideo();
  93. };
  94. const handleVideoLoaded = (e) => {
  95. uni.setStorageSync(`duration_${currentVideo.value.kjId}`, e.detail.duration);
  96. };
  97. </script>
  98. <style scoped>
  99. .course-container {
  100. display: flex;
  101. flex-direction: column;
  102. height: 100vh;
  103. }
  104. .course-header {
  105. padding: 20rpx;
  106. background: #f5f5f5;
  107. }
  108. .course-title {
  109. font-size: 36rpx;
  110. font-weight: bold;
  111. }
  112. .course-meta {
  113. margin-top: 10rpx;
  114. font-size: 24rpx;
  115. color: #666;
  116. }
  117. .course-tabs {
  118. display: flex;
  119. justify-content: space-around;
  120. padding: 20rpx;
  121. background: #fff;
  122. border-bottom: 1rpx solid #ddd;
  123. }
  124. .tab-item {
  125. font-size: 28rpx;
  126. color: #666;
  127. }
  128. .tab-item.active {
  129. color: #09BB07;
  130. font-weight: bold;
  131. }
  132. .course-content {
  133. flex: 1;
  134. overflow-y: auto;
  135. padding: 20rpx;
  136. }
  137. .chapter-list {
  138. background: #fff;
  139. }
  140. .chapter-item {
  141. margin-bottom: 20rpx;
  142. }
  143. .chapter-title {
  144. padding: 20rpx;
  145. background: #f5f5f5;
  146. display: flex;
  147. justify-content: space-between;
  148. border-bottom: 1rpx solid #ddd;
  149. }
  150. .section-list {
  151. background: #fff;
  152. }
  153. .section-item {
  154. padding: 20rpx;
  155. display: flex;
  156. justify-content: space-between;
  157. border-bottom: 1rpx solid #eee;
  158. }
  159. .section-info {
  160. flex: 1;
  161. }
  162. .section-title {
  163. display: block;
  164. font-size: 28rpx;
  165. color: #333;
  166. }
  167. .play-status {
  168. width: 180rpx;
  169. text-align: right;
  170. }
  171. .playing {
  172. color: #09BB07;
  173. font-size: 24rpx;
  174. }
  175. .progress {
  176. color: #666;
  177. font-size: 24rpx;
  178. }
  179. .unplayed {
  180. color: #999;
  181. font-size: 24rpx;
  182. }
  183. video {
  184. width: 100%;
  185. height: 300rpx;
  186. background: #000;
  187. }
  188. </style>