courseDetailed.vue 10.0 KB

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