onlineTrainingDetails.vue 9.7 KB

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