kechengInfo.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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 v-if="activeTab == '介绍'" class="course-content">
  29. <view v-if="!kechengData.intro" class="s-kehceng-default">
  30. <view>
  31. <img :src="jieshaoImg">
  32. <text>还没有介绍</text>
  33. </view>
  34. </view>
  35. <view v-else v-html="kechengData.intro" class="kecheng-tab-jieshao"></view>
  36. </view>
  37. </view>
  38. </template>
  39. <script setup>
  40. import {
  41. ref,
  42. onMounted
  43. } from 'vue';
  44. import {
  45. onLoad,
  46. onReady,
  47. } from "@dcloudio/uni-app"
  48. import {
  49. kechengStart
  50. } from "@/api/course.js";
  51. import catalogue from './common/catalogue.vue';
  52. // 选项卡状态
  53. const activeTab = ref('');
  54. let kcId = ref('');
  55. let kejianUserVo = ref(null);
  56. let jieshaoImg = ref('/static/images/kecheng/kecheng-introduce.svg');
  57. let kechengData = ref(null);
  58. // 当前播放信息
  59. const currentVideo = ref({
  60. url: '',
  61. kjId: ''
  62. });
  63. const currentChapter = ref(0);
  64. const currentSection = ref(0);
  65. const catalogueRef = ref(null);
  66. const videoId = ref('courseVideo');
  67. let videoContext = null;
  68. onLoad((options) => {
  69. console.log('options', options);
  70. kcId.value = options.kcId
  71. });
  72. onMounted(() => {
  73. getKechengData()
  74. });
  75. function getKechengData() {
  76. kechengStart({
  77. kcId: kcId.value
  78. }).then(res => {
  79. console.log('res', res);
  80. activeTab.value = '目录'
  81. kejianUserVo.value = res.data.kejianUserVo
  82. kechengData.value = res.data
  83. })
  84. }
  85. function getKejianInfo(data) {
  86. console.log('data', data.url);
  87. currentVideo.value = {
  88. url: data.url,
  89. kjId: data.kjId
  90. };
  91. setTimeout(() => {
  92. if (!videoContext) {
  93. videoContext = uni.createVideoContext(videoId.value, this);
  94. }
  95. const cachedTime = uni.getStorageSync(`video_${data.kjId}`) || 0;
  96. videoContext.seek(cachedTime);
  97. }, 300);
  98. }
  99. const handleTimeUpdate = (e) => {
  100. const currentTime = e.detail.currentTime;
  101. uni.setStorageSync(`video_${currentVideo.value.kjId}`, currentTime);
  102. };
  103. const handleVideoEnd = () => {
  104. catalogueRef.value.playNextVideo();
  105. };
  106. const handleVideoLoaded = (e) => {
  107. uni.setStorageSync(`duration_${currentVideo.value.kjId}`, e.detail.duration);
  108. };
  109. </script>
  110. <style scoped>
  111. .course-container {
  112. display: flex;
  113. flex-direction: column;
  114. height: 100vh;
  115. }
  116. .course-header {
  117. padding: 20rpx;
  118. background: #f5f5f5;
  119. }
  120. .course-title {
  121. font-size: 36rpx;
  122. font-weight: bold;
  123. }
  124. .course-meta {
  125. margin-top: 10rpx;
  126. font-size: 24rpx;
  127. color: #666;
  128. }
  129. .course-tabs {
  130. display: flex;
  131. justify-content: space-around;
  132. padding: 20rpx;
  133. background: #fff;
  134. border-bottom: 1rpx solid #ddd;
  135. }
  136. .tab-item {
  137. font-size: 28rpx;
  138. color: #666;
  139. }
  140. .tab-item.active {
  141. color: #09BB07;
  142. font-weight: bold;
  143. }
  144. .course-content {
  145. flex: 1;
  146. overflow-y: auto;
  147. padding: 20rpx;
  148. }
  149. .chapter-list {
  150. background: #fff;
  151. }
  152. .chapter-item {
  153. margin-bottom: 20rpx;
  154. }
  155. .chapter-title {
  156. padding: 20rpx;
  157. background: #f5f5f5;
  158. display: flex;
  159. justify-content: space-between;
  160. border-bottom: 1rpx solid #ddd;
  161. }
  162. .section-list {
  163. background: #fff;
  164. }
  165. .section-item {
  166. padding: 20rpx;
  167. display: flex;
  168. justify-content: space-between;
  169. border-bottom: 1rpx solid #eee;
  170. }
  171. .section-info {
  172. flex: 1;
  173. }
  174. .section-title {
  175. display: block;
  176. font-size: 28rpx;
  177. color: #333;
  178. }
  179. .play-status {
  180. width: 180rpx;
  181. text-align: right;
  182. }
  183. .playing {
  184. color: #09BB07;
  185. font-size: 24rpx;
  186. }
  187. .progress {
  188. color: #666;
  189. font-size: 24rpx;
  190. }
  191. .unplayed {
  192. color: #999;
  193. font-size: 24rpx;
  194. }
  195. video {
  196. width: 100%;
  197. height: 300rpx;
  198. background: #000;
  199. }
  200. </style>