courseDetail.vue 11 KB

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