onlineTrainingDetails.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <template>
  2. <view class="details">
  3. <u-navbar back-text="" title="" back-icon-color="#FFFFFF" :background="{ background: '#3D5D4C' }" :border-bottom="false"></u-navbar>
  4. <!-- 视频 -->
  5. <view class="details-video" v-if="isPaly" v-loading="videoLoading">
  6. <video
  7. class="details-video-con"
  8. id="myVideo"
  9. @timeupdate="timeUpdate"
  10. :src="videoInfo.videoUrl"
  11. controls
  12. :initial-time="initial_time"
  13. object-fit="fill"
  14. play-btn-position="center"
  15. @tap="videoClick"
  16. @loadedmetadata="loadedmetadata"
  17. >
  18. <cover-view class="video-control">
  19. <cover-view class="multi rate" @tap.stop="showSwitchRate">x {{ currentRate }}</cover-view>
  20. </cover-view>
  21. <cover-view class="multi-list rate" :class="{ active: rateShow }">
  22. <cover-view
  23. v-for="(item, index) in ['0.5', '1.0', '1.5', '2.0']"
  24. :key="index"
  25. class="multi-item rate"
  26. :data-rate="item"
  27. @tap="switchRate"
  28. :class="{ active: item == currentRate }"
  29. >
  30. {{ item }}
  31. </cover-view>
  32. </cover-view>
  33. </video>
  34. </view>
  35. <!-- 介绍 -->
  36. <view class="details-content">
  37. <view class="details-content-title">{{ videoInfo.chapterName }}</view>
  38. <view class="details-content-progress">本课程 共{{ info.amount }}课,已学完{{ info.finishCount }}课,共进度{{ info.finishPercent || 0 }}%</view>
  39. <view class="details-content-teacher">主讲老师:{{ info.presenter }}</view>
  40. <view class="details-content-info">{{ videoInfo.chapterInfo }}</view>
  41. </view>
  42. <!-- 课程章节 -->
  43. <view class="details-classes">
  44. <view class="details-classes-header">
  45. <view>精选课程</view>
  46. <view
  47. >更多
  48. <u-icon name="arrow-right" size="22" color="#A3A3A3" />
  49. </view>
  50. </view>
  51. <view class="details-classes-list">
  52. <view
  53. class="details-classes-list-item"
  54. v-for="(item, index) in info.chapterList"
  55. :key="index"
  56. :class="{ active: index === videoIndex }"
  57. @click="classesClick(index)"
  58. >
  59. <view>{{ index + 1 }}</view>
  60. <view>{{ item.flag === 2 ? '已学' : item.finishPercent + '%' }}</view>
  61. </view>
  62. </view>
  63. </view>
  64. <view class="details-line">
  65. <view></view>
  66. </view>
  67. <!-- 评论 -->
  68. <view class="details-comment">
  69. <view class="details-comment-header">
  70. <view>课程评论</view>
  71. <view>共{{ total }}条评论</view>
  72. </view>
  73. <view class="details-comment-list">
  74. <view class="details-comment-list-item" v-for="(item, index) in commentList" :key="index">
  75. <view class="left">
  76. <u-avatar :src="item.createByAvatar" size="96" mode="square"></u-avatar>
  77. </view>
  78. <view class="right">
  79. <view>{{ item.createBy }}</view>
  80. <view>
  81. <u-rate :count="5" size="28" disabled="" active-color="#FFBC00" v-model="item.starLevel"> </u-rate>
  82. <text>{{ item.createTime }}</text>
  83. </view>
  84. <view>{{ item.content }}</view>
  85. </view>
  86. </view>
  87. </view>
  88. <view class="details-comment-page" v-if="total > 5">
  89. <wyb-pagination :padding="0" :totalItems="total" :current="query.pageNum" @change="pageChange" />
  90. </view>
  91. <view class="details-comment-mine">
  92. <text>我的评论</text>
  93. </view>
  94. <view class="details-comment-conent">
  95. <view class="details-comment-conent-star">
  96. <u-rate :count="5" size="40" active-color="#FFBC00" v-model="form.starLevel"></u-rate>
  97. </view>
  98. <view class="details-comment-content-textarea">
  99. <u-input
  100. v-model="form.content"
  101. placeholder="请输入您的评价"
  102. type="textarea"
  103. :custom-style="{ backgroundColor: '#F5F5F5', padding: '30rpx', borderRadius: '10rpx', minHeight: '280rpx' }"
  104. >
  105. </u-input>
  106. </view>
  107. <view class="details-comment-conent-button" @click="submitCommet">提交</view>
  108. </view>
  109. </view>
  110. <u-toast ref="uToast" />
  111. </view>
  112. </template>
  113. <script>
  114. import wybPagination from '@/components/wyb-pagination/wyb-pagination.vue';
  115. export default {
  116. data() {
  117. return {
  118. info: {},
  119. videoContext: uni.createVideoContext('myVideo', this),
  120. videoInfo: {},
  121. videoIndex: 0,
  122. // 视频实时时间
  123. initial_time: 0,
  124. // 视频已经播放时间
  125. playedTime: 0,
  126. rateShow: false, // 倍速浮层
  127. currentRate: 1.0, // 默认倍速
  128. // 视频实际时间
  129. video_real_time: 0,
  130. classesId: '',
  131. isPaly: true,
  132. query: {
  133. pageNum: 1,
  134. pageSize: 5,
  135. tabId: ''
  136. },
  137. total: 0,
  138. commentList: [],
  139. form: {
  140. tabId: '',
  141. starLevel: 0,
  142. content: ''
  143. },
  144. currentDuration: 0,
  145. duration: 0,
  146. videoLoading: false
  147. };
  148. },
  149. onLoad(page) {
  150. if (page.id) {
  151. this.getClassesDetails(page.id);
  152. this.classesId = page.id;
  153. this.query.tabId = this.classesId;
  154. this.form.tabId = this.classesId;
  155. }
  156. },
  157. beforeDestroy() {
  158. this.confirmSubmitDuration();
  159. },
  160. methods: {
  161. /**
  162. * 获取视频总时长
  163. * @param {Object} data
  164. */
  165. loadedmetadata(data) {
  166. this.duration = data.detail.duration;
  167. this.videoLoading = false;
  168. },
  169. /**
  170. * 显示倍速浮层
  171. * @param {Object} rate
  172. */
  173. showSwitchRate(rate) {
  174. let that = this;
  175. that.rateShow = true;
  176. },
  177. /**
  178. * 切换倍速
  179. * @param {Object} e
  180. */
  181. switchRate(e) {
  182. let that = this;
  183. let rate = Number(e.currentTarget.dataset.rate);
  184. that.currentRate = rate;
  185. that.rateShow = false;
  186. this.videoContext.playbackRate(rate);
  187. },
  188. /**
  189. * 视频点击
  190. * @param {Object} e
  191. */
  192. videoClick(e) {
  193. this.rateShow = false;
  194. },
  195. /**
  196. * 获取课程详情
  197. * @param {Object} id
  198. */
  199. getClassesDetails(id) {
  200. this.videoLoading = true;
  201. this.$u.api.training
  202. .getOnlineDetailsApi({
  203. id
  204. })
  205. .then((res) => {
  206. if (res.code === 200) {
  207. this.info = res.data;
  208. this.videoInfo = res.data.chapterList[this.videoIndex];
  209. this.initial_time = Number(res.data.chapterList[this.videoIndex].currentDuration);
  210. this.video_real_time = Number(res.data.chapterList[this.videoIndex].playDuration);
  211. this.playedTime = Number(res.data.chapterList[this.videoIndex].playDuration);
  212. this.query.pageNum = 1;
  213. this.isPaly = true;
  214. this.getCommentList();
  215. }
  216. });
  217. },
  218. /**
  219. * 课程章节点击
  220. * @param {Object} index
  221. */
  222. classesClick(index) {
  223. this.isPaly = false;
  224. let playDuration = this.video_real_time;
  225. if (this.videoInfo.playDuration > this.video_real_time) {
  226. this.currentDuration = this.video_real_time;
  227. playDuration = this.videoInfo.playDuration;
  228. } else {
  229. this.currentDuration = this.video_real_time;
  230. }
  231. this.submitTimeLong(
  232. {
  233. tabId: this.videoInfo.id,
  234. playDuration: playDuration,
  235. duration: this.duration,
  236. currentDuration: this.currentDuration
  237. },
  238. index
  239. );
  240. },
  241. /**
  242. * 控制视频不能快进
  243. * @param {Object} e
  244. */
  245. timeUpdate(e) {
  246. //播放的总时长
  247. let duration = e.detail.duration;
  248. //实时播放进度 秒数
  249. let jumpTime = parseInt(e.detail.currentTime);
  250. //当前视频进度
  251. if (jumpTime - this.playedTime > 3) {
  252. // 差别过大,调用seek方法跳转到实际观看时间
  253. this.videoContext.seek(this.playedTime);
  254. wx.showToast({
  255. title: '未完整看完该视频,不能快进',
  256. icon: 'none',
  257. duration: 2000
  258. });
  259. } else {
  260. this.video_real_time = parseInt(e.detail.currentTime);
  261. if (this.video_real_time > this.playedTime) {
  262. this.playedTime = this.video_real_time;
  263. }
  264. }
  265. if (parseInt(this.duration) !== 0 && parseInt(this.duration) === parseInt(this.video_real_time)) {
  266. this.videoContext.pause();
  267. this.confirmSubmitDuration();
  268. }
  269. },
  270. /**
  271. * 提交课程时长
  272. */
  273. submitTimeLong({ tabId, playDuration, duration, currentDuration }, index, flag) {
  274. this.$u.api.training
  275. .videoTimeLongApi({
  276. tabId,
  277. playDuration,
  278. duration,
  279. currentDuration
  280. })
  281. .then((res) => {
  282. if (res.code === 200) {
  283. if (!flag) {
  284. this.$refs.uToast.show({
  285. title: '已记录章节时长!',
  286. type: 'success'
  287. });
  288. this.videoInfo = this.info.chapterList[index];
  289. this.videoIndex = index;
  290. this.getClassesDetails(this.classesId);
  291. }
  292. } else {
  293. this.$refs.uToast.show({
  294. title: res.msg,
  295. type: 'error'
  296. });
  297. }
  298. });
  299. },
  300. /**
  301. * 获取评论列表
  302. */
  303. getCommentList() {
  304. this.$u.api.training.getClassesCommentApi(this.query).then((res) => {
  305. if (res.code === 200) {
  306. this.total = Number(res.total);
  307. this.commentList = res.rows;
  308. }
  309. });
  310. },
  311. /**
  312. * @param {Object} e 分页触发
  313. */
  314. pageChange(e) {
  315. this.query.pageNum = e.current;
  316. this.getCommentList();
  317. },
  318. /**
  319. * 提交评论
  320. */
  321. submitCommet() {
  322. if (this.form.starLevel && this.form.content) {
  323. this.$u.api.training.addClassesCommentApi(this.form).then((res) => {
  324. if (res.code === 200) {
  325. this.$refs.uToast.show({
  326. title: '评论成功!',
  327. type: 'success'
  328. });
  329. this.form.content = '';
  330. this.form.starLevel = 0;
  331. this.getCommentList();
  332. } else {
  333. this.$refs.uToast.show({
  334. title: res.msg,
  335. type: 'error'
  336. });
  337. }
  338. });
  339. }
  340. if (!this.form.starLevel) {
  341. this.$refs.uToast.show({
  342. title: '请选择星级',
  343. type: 'warning'
  344. });
  345. }
  346. if (!this.form.content) {
  347. this.$refs.uToast.show({
  348. title: '请输入评论内容',
  349. type: 'warning'
  350. });
  351. }
  352. },
  353. confirmSubmitDuration() {
  354. let playDuration = this.video_real_time;
  355. if (this.videoInfo.playDuration > this.video_real_time) {
  356. this.currentDuration = this.video_real_time;
  357. playDuration = this.videoInfo.playDuration;
  358. } else {
  359. this.currentDuration = this.video_real_time;
  360. }
  361. this.submitTimeLong(
  362. {
  363. tabId: this.videoInfo.id,
  364. playDuration: playDuration,
  365. duration: this.duration,
  366. currentDuration: this.currentDuration
  367. },
  368. 0,
  369. true
  370. );
  371. }
  372. }
  373. };
  374. </script>
  375. <style lang="scss" scoped>
  376. @import './onlineTrainingDetails.scss';
  377. </style>