videoDetails.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <template>
  2. <view class="page_body">
  3. <!-- 头部 -->
  4. <nav-bar height="96" title="视频详情" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
  5. <view class="page_content">
  6. <view class="video_content">
  7. <!-- duration:指定视频时长,单位为秒(s) initial-time:指定视频初始播放位置,单位为秒(s) -->
  8. <video
  9. ref="myVideoRef"
  10. id="myVideoRef"
  11. :title="state?.videoInfo?.courseName || ''"
  12. :initial-time="state?.videoInfo?.initialTime"
  13. :duration="state?.videoInfo?.duration"
  14. :show-mute-btn="true"
  15. :poster="state?.videoInfo?.videoCoverUrl"
  16. :src="state?.videoInfo?.attachmentUrl"
  17. style="width: 100%; height: 100%; object-fit: contain;"
  18. @timeupdate="videoTimeupdate"
  19. >
  20. </video>
  21. </view>
  22. <view class="video_title">{{state?.videoInfo?.courseName}}</view>
  23. <view class="video_progress">
  24. <text class="video_progress_text" v-if="state?.videoInfo?.courseType===0">必修任务:{{state.videoInfo.courseStatus===0?`剩余${state.videoInfo.remainingTime}`:'已结束'}}</text>
  25. <text class="video_progress_text">观看进度:{{state?.videoInfo?.progress}}</text>
  26. </view>
  27. <view class="video_class_name">
  28. <text class="class_name_item">{{state?.videoInfo?.teachCourseTypeName}}</text>
  29. <text class="class_name_item">{{state?.videoInfo?.teachCourseTypeClassName}}</text>
  30. <text class="class_name_item">{{state?.videoInfo?.createdBy}}</text>
  31. </view>
  32. <view class="video_time">
  33. <text class="time_item">{{state?.videoInfo?.courseType===0 ?'开始':'上传'}}时间:{{state?.videoInfo?.courseStartDatetime}}</text>
  34. <text class="time_item" v-if="state?.videoInfo?.courseType===0">结束时间:{{state.videoInfo.courseEndDatetime}}</text>
  35. </view>
  36. <view class="video_desc">
  37. 课程简介:{{state?.videoInfo?.courseContent}}
  38. </view>
  39. </view>
  40. </view>
  41. <!-- 提示框 -->
  42. <custom-popup-dialog ref="popupDialogRef" title="试题弹出" dialogWidth="702" :showCloseBtn="false" @DialogConfirm="DialogConfirm">
  43. <view class="dialog_questions_input">
  44. <view class="questions_title">{{state?.question?.codeName}}:{{state?.question?.questionsName}}</view>
  45. <uni-easyinput type="textarea" v-model="state.questionsAnswer" :style="state.easyinputStyle" placeholder="请输入你的回答" placeholderStyle="font-weight: 400;font-size: 28rpx;color: #999999;"></uni-easyinput>
  46. </view>
  47. </custom-popup-dialog>
  48. </template>
  49. <script setup>
  50. import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
  51. import navBar from '@/components/nav-bar.vue';
  52. import customPopupDialog from '@/components/custom-popup-dialog.vue';
  53. import { selectCourseDetail,addQuestionAnswerData } from '@/reqApi/teacherStudy.js';
  54. import {
  55. onLoad,
  56. onUnload
  57. } from '@dcloudio/uni-app';
  58. import {
  59. reactive,
  60. ref,
  61. getCurrentInstance
  62. } from 'vue';
  63. const state = reactive({
  64. id: '', //视频id
  65. videoInfo:{},
  66. currentTime:'',//播放进度时分秒
  67. question:null,//试题弹出信息
  68. easyinputStyle:{
  69. color: '#303133',
  70. borderColor: '#E9E9E9'
  71. },
  72. questionsAnswer:''
  73. });
  74. //提示框
  75. const popupDialogRef = ref(null);
  76. const myVideoRef = ref(null);
  77. const videoCtx = ref(null);//播放器
  78. onLoad((option) => {
  79. state.id = option.id;
  80. const instance = getCurrentInstance();
  81. videoCtx.value = uni.createVideoContext('myVideoRef', instance.proxy);
  82. GetCourseDetail();
  83. })
  84. //视频详情
  85. const GetCourseDetail = () => {
  86. selectCourseDetail(state.id).then(res => {
  87. if(res.code == 200){
  88. state.videoInfo = res.data;
  89. state.videoInfo.initialTime = timeToSecond(state.videoInfo.courseLearnTime || '');
  90. state.videoInfo.duration = timeToSecond(state.videoInfo.videoTime || '')
  91. }
  92. })
  93. }
  94. //计算秒数
  95. const timeToSecond = (timeStr) => {
  96. const timeArr = timeStr.split(':');
  97. const [h, m, s] = timeArr.map(item => Number(item));
  98. return h * 3600 + m * 60 + s;
  99. }
  100. //播放进度变化时触发,event.detail = {currentTime, duration} 。触发频率 250ms 一次
  101. const videoTimeupdate = (event) => {
  102. const currentTime = event.detail.currentTime;
  103. state.currentTime = formatSecondsToHms(currentTime);
  104. const questionsList = state?.videoInfo?.questionsList || [];
  105. const question = questionsList.find(item=>item.showTime == state.currentTime);//视频显示的时间
  106. state.question = question;//试题弹框信息
  107. if(question){
  108. videoCtx?.value?.pause();//暂停播放
  109. state.questionsAnswer = '';
  110. popupDialogRef.value.OpenDialog();
  111. }
  112. }
  113. //提交试题
  114. const DialogConfirm = () => {
  115. if(state.questionsAnswer == ''){
  116. uni.showToast({
  117. title: '请输入答案!',
  118. icon: 'none'
  119. })
  120. return false
  121. }
  122. addQuestionAnswerData({
  123. teachCourseId: state.id,
  124. teachQuestionsId: state?.question?.id || '',
  125. teachQuestionsAnswer: state.questionsAnswer
  126. }).then(res=>{
  127. if(res.code == 200){
  128. popupDialogRef.value.CloseDialog();//关闭弹框
  129. console.log(videoCtx?.value)
  130. videoCtx?.value?.play();//播放
  131. }else{
  132. uni.showToast({
  133. title: res.msg || '请求异常',
  134. icon: 'none'
  135. })
  136. }
  137. })
  138. }
  139. // 数字补零工具
  140. const addZero = (num) => {
  141. return num < 10 ? '0' + num : String(num);
  142. }
  143. //转换时分秒
  144. const formatSecondsToHms = (seconds) => {
  145. let total = Math.floor(seconds);
  146. const h = Math.floor(total / 3600);
  147. const m = Math.floor((total % 3600) / 60);
  148. const s = total % 60;
  149. const hh = addZero(h);
  150. const mm = addZero(m);
  151. const ss = addZero(s);
  152. return `${hh}:${mm}:${ss}`;
  153. }
  154. //页面销毁时触发
  155. onUnload(()=>{
  156. })
  157. //返回
  158. const GoBack = () => {
  159. uni.navigateBack({
  160. delta: 1
  161. });
  162. }
  163. </script>
  164. <style scoped lang="scss">
  165. .video_content {
  166. width: 100%;
  167. border-radius: 8rpx;
  168. margin-top: 24rpx;
  169. height: 398rpx;
  170. overflow: hidden;
  171. }
  172. .video_title{
  173. width: 100%;
  174. margin-top: 32rpx;
  175. font-weight: 500;
  176. font-size: 36rpx;
  177. color: #333333;
  178. display: -webkit-box;
  179. -webkit-line-clamp: 2;
  180. -webkit-box-orient: vertical;
  181. overflow: hidden;
  182. text-overflow: ellipsis;
  183. }
  184. .video_progress{
  185. display: flex;
  186. justify-content: space-between;
  187. width: 100%;
  188. margin-top: 16rpx;
  189. .video_progress_text{
  190. font-weight: 400;
  191. font-size: 24rpx;
  192. color: #2E64FA;
  193. }
  194. }
  195. .video_class_name{
  196. display: flex;
  197. width: 100%;
  198. margin-top: 16rpx;
  199. gap: 16rpx 24rpx;
  200. flex-wrap: wrap;
  201. .class_name_item{
  202. font-weight: 400;
  203. font-size: 24rpx;
  204. color: #666666;
  205. }
  206. }
  207. .video_time{
  208. display: flex;
  209. width: 100%;
  210. margin-top: 16rpx;
  211. gap: 16rpx;
  212. flex-wrap: wrap;
  213. .time_item{
  214. font-weight: 400;
  215. font-size: 24rpx;
  216. color: #666666;
  217. }
  218. }
  219. .video_desc{
  220. display: flex;
  221. width: 100%;
  222. margin-top: 32rpx;
  223. flex-wrap: wrap;
  224. word-break: break-all;
  225. font-weight: 400;
  226. font-size: 24rpx;
  227. color: #999999;
  228. line-height: 44rpx;
  229. }
  230. .dialog_questions_input{
  231. display: flex;
  232. width: 100%;
  233. flex-direction: column;
  234. .questions_title{
  235. display: flex;
  236. width: 100%;
  237. font-weight: 400;
  238. font-size: 32rpx;
  239. color: #303133;
  240. text-align: left;
  241. word-break: break-all;
  242. }
  243. :deep(.uni-easyinput){
  244. margin-top: 20rpx;
  245. font-size: 28rpx;
  246. .is-input-border{
  247. border-radius: 8rpx;
  248. border-color: #E9E9E9 !important;
  249. }
  250. .uni-easyinput__content-textarea{
  251. height: 440rpx;
  252. min-height: 440rpx;
  253. font-size: 28rpx;
  254. margin:20rpx 24rpx 20rpx 14rpx;
  255. }
  256. .input-padding{
  257. padding-left: 10rpx;
  258. }
  259. }
  260. }
  261. </style>