courseDetailed.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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="isPlay">
  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" @ended="ended"
  9. @tap="videoClick" @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. 更多
  36. <u-icon name="arrow-right" size="22" color="#A3A3A3" />
  37. </view>
  38. </view>
  39. <view class="details-classes-list">
  40. <view class="details-classes-list-item" v-for="(item, index) in info.chapterList" :key="index"
  41. :class="{ active: index === videoIndex }" @click="classesClick(index)">
  42. <view>{{ index + 1 }}</view>
  43. <view>{{ item.flag === 2 ? '已学' : item.finishPercent + '%' }}</view>
  44. </view>
  45. </view>
  46. </view>
  47. <view class="details-line">
  48. <view></view>
  49. </view>
  50. <!-- 评论 -->
  51. <view class="details-comment">
  52. <view class="details-comment-header">
  53. <view>课程评论</view>
  54. <view>共{{ total }}条评论</view>
  55. </view>
  56. <view class="details-comment-list">
  57. <view class="details-comment-list-item" v-for="(item, index) in commentList" :key="index">
  58. <view class="left">
  59. <u-avatar :src="item.createByAvatar" size="96" mode="square"></u-avatar>
  60. </view>
  61. <view class="right">
  62. <view>{{ item.createBy }}</view>
  63. <view>
  64. <u-rate :count="5" size="28" disabled="" active-color="#FFBC00" v-model="item.starLevel">
  65. </u-rate>
  66. <text>{{ item.createTime }}</text>
  67. </view>
  68. <view>{{ item.content }}</view>
  69. </view>
  70. </view>
  71. </view>
  72. <view class="details-comment-page" v-if="total">
  73. <wyb-pagination :padding="0" :totalItems="total" :current="query.pageNum" @change="pageChange" />
  74. </view>
  75. <view class="details-comment-mine"><text>我的评论</text></view>
  76. <view class="details-comment-conent">
  77. <view class="details-comment-conent-star">
  78. <u-rate :count="5" size="40" active-color="#FFBC00" v-model="form.starLevel"></u-rate>
  79. </view>
  80. <view class="details-comment-content-textarea">
  81. <u-input v-model="form.content" placeholder="请输入您的评价" type="textarea"
  82. :custom-style="{ backgroundColor: '#F5F5F5', padding: '30rpx', borderRadius: '10rpx', minHeight: '280rpx' }">
  83. </u-input>
  84. </view>
  85. <view class="details-comment-conent-button" @click="submitCommet">提交</view>
  86. </view>
  87. </view>
  88. <u-toast ref="uToast" />
  89. </view>
  90. </template>
  91. <script>
  92. import wybPagination from '@/components/wyb-pagination/wyb-pagination.vue';
  93. export default {
  94. data() {
  95. return {
  96. info: {},
  97. videoContext: uni.createVideoContext('myVideo', this),
  98. videoInfo: {},
  99. videoIndex: 0,
  100. // 视频实时时间
  101. initial_time: 0,
  102. // 视频已经播放时间
  103. playedTime: 0,
  104. rateShow: false, // 倍速浮层
  105. currentRate: 1.0, // 默认倍速
  106. // 视频实际时间
  107. video_real_time: 0,
  108. classesId: '',
  109. isPlay: true,
  110. query: {
  111. pageNum: 1,
  112. pageSize: 5,
  113. tabId: ''
  114. },
  115. total: 0,
  116. commentList: [],
  117. form: {
  118. tabId: '',
  119. starLevel: 0,
  120. content: ''
  121. },
  122. currentDuration: 0,
  123. duration: 0,
  124. isApply: 1
  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. this.isApply = page.isApply || 1
  134. }
  135. },
  136. beforeDestroy() {
  137. this.confirmSubmitDuration();
  138. },
  139. methods: {
  140. /**
  141. * 获取视频总时长
  142. * @param {Object} data
  143. */
  144. loadedmetadata(data) {
  145. this.duration = data.detail.duration;
  146. },
  147. /**
  148. * 显示倍速浮层
  149. * @param {Object} rate
  150. */
  151. showSwitchRate(rate) {
  152. let that = this;
  153. that.rateShow = true;
  154. },
  155. /**
  156. * 切换倍速
  157. * @param {Object} e
  158. */
  159. switchRate(e) {
  160. let that = this;
  161. let rate = Number(e.currentTarget.dataset.rate);
  162. that.currentRate = rate;
  163. that.rateShow = false;
  164. this.videoContext.playbackRate(rate);
  165. },
  166. /**
  167. * 视频点击
  168. * @param {Object} e
  169. */
  170. videoClick(e) {
  171. this.rateShow = false;
  172. },
  173. pause(e) {
  174. console.log(e);
  175. },
  176. /**
  177. * 获取课程详情
  178. * @param {Object} id
  179. */
  180. getClassesDetails(id) {
  181. this.$u.api.school
  182. .getPackageCourseDetail({
  183. id
  184. })
  185. .then(res => {
  186. if (res.code === 200) {
  187. this.info = res.data;
  188. this.videoInfo = res.data.chapterList[this.videoIndex];
  189. this.initial_time = Number(res.data.chapterList[this.videoIndex].currentDuration);
  190. this.video_real_time = Number(res.data.chapterList[this.videoIndex].playDuration);
  191. this.playedTime = Number(res.data.chapterList[this.videoIndex].playDuration);
  192. this.query.pageNum = 1;
  193. this.isPlay = true;
  194. this.getCommentList();
  195. }
  196. });
  197. },
  198. /**
  199. * 课程章节点击
  200. * @param {Object} index
  201. */
  202. classesClick(index) {
  203. this.isPlay = false;
  204. let playDuration = this.video_real_time;
  205. if (this.videoInfo.playDuration > this.video_real_time) {
  206. this.currentDuration = this.video_real_time;
  207. playDuration = this.videoInfo.playDuration;
  208. } else {
  209. this.currentDuration = this.video_real_time;
  210. }
  211. this.submitTimeLong({
  212. tabId: this.videoInfo.id,
  213. playDuration: playDuration,
  214. duration: this.duration,
  215. currentDuration: this.currentDuration
  216. },
  217. index
  218. );
  219. },
  220. /**
  221. * 控制视频不能快进
  222. * @param {Object} e
  223. */
  224. timeUpdate(e) {
  225. //播放的总时长
  226. let duration = e.detail.duration;
  227. //实时播放进度 秒数
  228. let jumpTime = parseInt(e.detail.currentTime);
  229. //当前视频进度
  230. if (jumpTime - this.playedTime > 3) {
  231. // 差别过大,调用seek方法跳转到实际观看时间
  232. this.videoContext.seek(this.playedTime);
  233. wx.showToast({
  234. title: '未完整看完该视频,不能快进',
  235. icon: 'none',
  236. duration: 2000
  237. });
  238. } else {
  239. this.video_real_time = parseInt(e.detail.currentTime);
  240. if (this.video_real_time > this.playedTime) {
  241. this.playedTime = this.video_real_time;
  242. }
  243. }
  244. if (parseInt(this.duration) !== 0 && parseInt(this.duration) === parseInt(this.video_real_time)) {
  245. this.videoContext.pause();
  246. this.confirmSubmitDuration();
  247. }
  248. },
  249. /**
  250. * 视频结束
  251. */
  252. ended() {
  253. // 用户把进度条拉到最后,但是实际观看时间不够,跳转回去会自动暂停。
  254. // 这里加个判断。
  255. if (this.playedTime < this.duration) {
  256. this.videoContext.play();
  257. this.confirmSubmitDuration();
  258. }
  259. },
  260. /**
  261. * 提交课程时长
  262. */
  263. submitTimeLong({
  264. tabId,
  265. playDuration,
  266. currentDuration,
  267. duration
  268. }, index, flag) {
  269. if (tabId) {
  270. this.$u.api.training
  271. .videoTimeLongApi({
  272. tabId,
  273. playDuration,
  274. currentDuration,
  275. duration
  276. })
  277. .then(res => {
  278. if (res.code === 200) {
  279. if (!flag) {
  280. this.$refs.uToast.show({
  281. title: '已记录章节时长!',
  282. type: 'success'
  283. });
  284. this.videoInfo = this.info.chapterList[index];
  285. this.videoIndex = index;
  286. this.getClassesDetails(this.classesId);
  287. }
  288. } else {
  289. this.$refs.uToast.show({
  290. title: res.msg,
  291. type: 'error'
  292. });
  293. }
  294. });
  295. }
  296. },
  297. /**
  298. * 获取评论列表
  299. */
  300. getCommentList() {
  301. if (this.query.tabId) {
  302. this.$u.api.training.getClassesCommentApi(this.query).then(res => {
  303. if (res.code === 200) {
  304. this.total = Number(res.total);
  305. this.commentList = res.rows;
  306. }
  307. });
  308. }
  309. },
  310. /**
  311. * @param {Object} e 分页触发
  312. */
  313. pageChange(e) {
  314. this.query.pageNum = e.current;
  315. this.getCommentList();
  316. },
  317. /**
  318. * 提交评论
  319. */
  320. submitCommet() {
  321. if (this.form.tabId) {
  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. } else {
  353. this.$refs.uToast.show({
  354. title: '未找到课程章节,无法提交评论!',
  355. type: 'warning'
  356. });
  357. }
  358. },
  359. confirmSubmitDuration() {
  360. let playDuration = this.video_real_time;
  361. if (this.videoInfo.playDuration > this.video_real_time) {
  362. this.currentDuration = this.video_real_time;
  363. playDuration = this.videoInfo.playDuration;
  364. } else {
  365. this.currentDuration = this.video_real_time;
  366. }
  367. if (Number(this.isApply) === 1) {
  368. this.submitTimeLong({
  369. tabId: this.videoInfo.id,
  370. playDuration: playDuration,
  371. currentDuration: this.currentDuration,
  372. duration: this.duration
  373. },
  374. 0,
  375. true
  376. );
  377. }
  378. }
  379. }
  380. };
  381. </script>
  382. <style lang="scss" scoped>
  383. @import './courseDetailed.scss';
  384. </style>