courseDetail.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import {
  2. courseProcess,
  3. courseTypes,
  4. courseTypeTexts,
  5. htmlTypes,
  6. logicStatus,
  7. liveStatus,
  8. liveSources
  9. } from "../../model/enum";
  10. import Api from "../../model/api";
  11. import {
  12. getDataSet,
  13. getEventParam,
  14. showLoading,
  15. toast
  16. } from "../../utils/utils";
  17. import Route from "../../model/route";
  18. import {
  19. wxToPromise
  20. } from "../../utils/wx";
  21. Page({
  22. data: {
  23. id: '',
  24. type: '',
  25. item: {},
  26. obj: {},
  27. plan: {},
  28. fileList: [],
  29. videoList: [],
  30. videoIndex: 0,
  31. courseTypeTexts: courseTypeTexts,
  32. courseTypesEnum: courseTypes,
  33. coverDefaultVal: '/images/kc.jpg',
  34. looseSecondTime: 3, //学习时长和视频总时长差3s就算学完
  35. videoItem: {},
  36. scanTime: 0,
  37. //从数据库中查询到的视频学习时长
  38. oldNodeTime: -1,
  39. //已被记录的视频播放时长
  40. nodeTime: 0,
  41. //课程状态 logicStatus.NO: 未学完 logicStatus.YES: 已学完
  42. lessStatus: logicStatus.NO,
  43. show: false,
  44. //视频实时播放时长(拖拽进度条,currentTime为拖拽位置的时长)
  45. currentTime: 0,
  46. scanOkTime: 0,
  47. videoContext: null,
  48. tempFilePath: '',
  49. //10s 本地缓存一次视频时长
  50. localRecordInterval: 10,
  51. //是否是从往期培训来的
  52. isPre: logicStatus.NO,
  53. updatingStudyRecord: false,
  54. },
  55. async onLoad(options) {
  56. let {
  57. id,
  58. detail,
  59. plan,
  60. type,
  61. isPre
  62. } = options;
  63. let obj = JSON.parse(detail);
  64. plan = JSON.parse(plan);
  65. wx.setNavigationBarTitle({
  66. title: courseTypeTexts[type]
  67. });
  68. const videoContext = wx.createVideoContext('myVideo3');
  69. this.setData({
  70. id,
  71. plan,
  72. obj,
  73. type,
  74. videoContext,
  75. isPre: Number(isPre)
  76. }, async () => {
  77. showLoading();
  78. await this.getData();
  79. wx.hideLoading()
  80. this.data.videoContext.seek(this.data.nodeTime)
  81. })
  82. },
  83. async getData() {
  84. //查询课程详细信息
  85. const res = await Api.getCourseDetail(this.data.id, this.data.obj.eduStuId);
  86. if (this.data.obj.isOnline) {
  87. res.data.isQuestion = res.data.suitangUpper;
  88. } else {
  89. res.data.isQuestion = res.data.suitangLower;
  90. }
  91. let fileList = [];
  92. let videoItem = {};
  93. if (res.data.enclosureUrl) {
  94. if (this.data.type == courseTypes.RECORD) {
  95. //videoItem 附加信息
  96. videoItem = JSON.parse(res.data.enclosureUrl)[0];
  97. if (this.data.isPre == logicStatus.YES) {
  98. // 如果是往期培训来的
  99. this.setData({
  100. nodeTime: 0,
  101. lessStatus: logicStatus.NO,
  102. videoItem
  103. });
  104. return;
  105. }
  106. if (videoItem.verifyInterval) {
  107. //如果有人脸识别,设置人脸识别次数
  108. let scanTime = videoItem.verifyInterval;
  109. this.setData({
  110. scanTime
  111. })
  112. }
  113. //获取学员本节课的学习记录
  114. const less = await Api.getRecordedLesson({
  115. stuId: this.data.obj.eduStuId,
  116. scheduleId: this.data.id
  117. })
  118. if (less.data) {
  119. //课程学习状态 0:为学完 1:已学完
  120. let lessStatus = less.data.status;
  121. //课程是否已经完成
  122. let hasFulfilled = lessStatus == logicStatus.YES;
  123. //查询本地缓存里面记录的学习时长
  124. let localRecordTime = this.getLessonRecordByLocal();
  125. //数据库里面记录的学习时长
  126. let serviceRecordTime = less.data.nodeTime || 0;
  127. console.log('course - localRecordTime -->', localRecordTime);
  128. // localRecordTime = hasFulfilled && localRecordTime >= videoItem.duration ? 0 : localRecordTime;
  129. //课程已学习的时长(秒), 取本地和数据中记录时长的最大值
  130. let nodeTime = hasFulfilled ? 0 : Math.max(localRecordTime, serviceRecordTime);
  131. this.setData({
  132. oldNodeTime: serviceRecordTime,
  133. nodeTime,
  134. lessStatus,
  135. videoItem
  136. });
  137. if (!hasFulfilled && localRecordTime > serviceRecordTime) {
  138. //如果未完成课程学习 并且 本地记录的时长 > 数据库记录的时长(异常退出) ,更新数据库学习记录
  139. this.changeProgress();
  140. }
  141. //如果已经学完了 删除本地的学习时长缓存
  142. hasFulfilled && this.removeLessonRecordByLocal();
  143. // let lessStatus = less.data.status;
  144. // let nodeTime = lessStatus == logicStatus.YES ? 0 : less.data.nodeTime || 0;
  145. // this.setData({nodeTime, lessStatus, videoItem})
  146. } else {
  147. //如果数据库里面未取到学习记录(学员本节课未学过)
  148. this.setData({
  149. oldNodeTime: -1,
  150. nodeTime: 0,
  151. lessStatus: logicStatus.NO,
  152. videoItem
  153. })
  154. }
  155. } else {
  156. fileList = JSON.parse(res.data.enclosureUrl);
  157. }
  158. }
  159. let videoList = [];
  160. if (this.data.type == courseTypes.PLAYBACK && res.data.videoPlaybackUrl) {
  161. videoList = res.data.videoPlaybackUrl.split(",");
  162. }
  163. this.compare(res.data, this.data.plan);
  164. this.setData({
  165. item: res.data,
  166. fileList,
  167. videoList
  168. })
  169. },
  170. compare(newData, oldData) {
  171. if (!oldData) {
  172. return;
  173. }
  174. if (newData.courseProcess != oldData.courseProcess ||
  175. newData.courseStatus != oldData.courseStatus ||
  176. newData.liveStatus != oldData.liveStatus
  177. ) {
  178. const eventChannel = this.getOpenerEventChannel()
  179. eventChannel.emit('refresh');
  180. }
  181. },
  182. async downloadFile(e) {
  183. let url = getDataSet(e, "url");
  184. let end = url.substr(url.lastIndexOf(".") + 1);
  185. showLoading('下载中...')
  186. let filePath = "";
  187. if (this.data.tempFilePath) {
  188. filePath = this.data.tempFilePath;
  189. } else {
  190. const res = await wxToPromise("downloadFile", {
  191. url
  192. });
  193. if (res.statusCode === 200) {
  194. filePath = res.tempFilePath
  195. this.setData({
  196. tempFilePath: filePath
  197. })
  198. } else {
  199. toast(`下载文档失败,请稍后重试${res.statusCode}:${res.errMsg}`)
  200. }
  201. }
  202. try {
  203. await wx.openDocument({
  204. filePath
  205. })
  206. } catch (e) {
  207. console.log(e)
  208. toast(`打开文档失败,请稍后重试${e.msg}`)
  209. }
  210. wx.hideLoading();
  211. },
  212. toTeacher(e) {
  213. let id = getDataSet(e, "id");
  214. Route.toTeacher(id);
  215. },
  216. clickImg(e) {
  217. let id = this.data.item.id;
  218. let eId = this.data.obj.eduStuId;
  219. Route.toNews(htmlTypes.SEAT, id, "座位图", eId);
  220. },
  221. refresh(e) {
  222. return new Promise(async (resolve, reject) => {
  223. await this.getData();
  224. const eventChannel = this.getOpenerEventChannel()
  225. eventChannel.emit('refresh');
  226. resolve(true);
  227. });
  228. },
  229. changeVideo(e) {
  230. let index = getDataSet(e, "index");
  231. this.setData({
  232. videoIndex: index,
  233. })
  234. },
  235. // 以下是录播课的逻辑算法 待分离
  236. async onUnload() {
  237. await this.changeProgress();
  238. },
  239. async onHide() {
  240. await this.changeProgress();
  241. },
  242. async changeProgress() {
  243. if (this.data.isPre == logicStatus.YES) {
  244. //如果是往期培训,不执行更新学习记录
  245. return;
  246. }
  247. if (this.data.lessStatus == logicStatus.YES) {
  248. //如果已经学完了 不更新学习记录
  249. return;
  250. }
  251. if (this.data.nodeTime === 0 && this.data.oldNodeTime === -1 && !this.data.videoItem.duration) {
  252. //如果课程还没查询到数据就退出当前页面,不更新学习记录
  253. return;
  254. }
  255. if(this.data.updatingStudyRecord) {
  256. //如果正在更新学习记录,不继续调用接口(解决短时间内重复调用更新学习记录接口问题)
  257. return;
  258. }
  259. //只有是录播课的时候才会更新学习记录
  260. if (this.data.type == courseTypes.RECORD) {
  261. this.data.updatingStudyRecord = true;
  262. const res = await Api.changeVideoProgress({
  263. stuId: this.data.obj.eduStuId,
  264. scheduleId: this.data.id,
  265. nodeTime: this.data.nodeTime,
  266. oldNodeTime: this.data.oldNodeTime,
  267. status: this.data.lessStatus,
  268. duration: this.data.videoItem.duration
  269. });
  270. // console.log('res -> ', res);
  271. if (res.data) {
  272. //如果学完了
  273. // await this.getData();
  274. //上面这句是否要删除????????????/
  275. let {
  276. status,
  277. nodeTime
  278. } = res.data;
  279. this.setData({
  280. nodeTime,
  281. lessStatus: status
  282. });
  283. if (status == logicStatus.YES) {
  284. this.setData({
  285. [`item.courseProcess`]: courseProcess.END
  286. });
  287. //设定学习状态
  288. const eventChannel = this.getOpenerEventChannel();
  289. eventChannel.emit('refresh');
  290. wx.showModal({
  291. title: "恭喜你,视频已学完!",
  292. showCancel: false
  293. });
  294. }
  295. } else {
  296. // 学习时长记录到本地缓存 start
  297. // 如果未完成视频学习 将学习记录缓存到本地
  298. this.recordLessonByLocal(this.data.nodeTime);
  299. // 学习时长记录到本地缓存 end
  300. }
  301. this.data.updatingStudyRecord = false;
  302. }
  303. },
  304. async timeUpdate(e) {
  305. if (this.data.isPre == logicStatus.YES) {
  306. //往期培训不记录播放时长
  307. return;
  308. }
  309. let currentTime = parseInt(getEventParam(e, "currentTime"));
  310. let lessStatus = this.data.lessStatus;
  311. let unFulfilledFlag = lessStatus == logicStatus.NO;
  312. console.log("timeUpdate", currentTime, this.data.scanTime)
  313. // if (this.data.scanTime && unFulfilledFlag) { //需要处理验证人脸情况
  314. // let flag = currentTime % this.data.scanTime;
  315. // if (flag == 0 && currentTime != this.data.scanOkTime &&
  316. // currentTime >= this.data.nodeTime && currentTime != this.data.videoItem.duration) {
  317. // console.log("暂停,弹出人脸识别")
  318. // this.data.videoContext.exitFullScreen();
  319. // this.data.videoContext.pause();
  320. // this.setData({
  321. // show: true,
  322. // currentTime
  323. // });
  324. // }
  325. // }
  326. if (currentTime - this.data.nodeTime <= 1) {
  327. // 播放时长和被记录的时长的时间差<=1秒 说明没有快进视频
  328. if (currentTime - this.data.nodeTime >= 0) {
  329. //给学员的学习时长重新赋值 👇
  330. this.data.nodeTime = currentTime;
  331. // 如果学习时间和视频总时长相差在 this.data.looseSecondTime 秒内(现在设置是3s),就算完成
  332. if ((this.data.nodeTime >= (this.data.videoItem.duration - this.data.looseSecondTime)) && unFulfilledFlag) {
  333. await this.changeProgress();
  334. // //设定学习状态
  335. // this.setData({
  336. // lessStatus: logicStatus.YES
  337. // });
  338. // //提示用户
  339. // await wx.showModal({
  340. // title: "恭喜你,视频已学完!",
  341. // showCancel: false
  342. // });
  343. }
  344. // 原来的逻辑 👇
  345. // if (this.data.nodeTime == this.data.videoItem.duration) {
  346. // if (unFulfilledFlag) {
  347. // await this.changeProgress();
  348. // this.setData({
  349. // lessStatus: logicStatus.YES
  350. // });
  351. // await wx.showModal({
  352. // title: "恭喜你,视频已学完!",
  353. // showCancel: false
  354. // });
  355. // }
  356. // }
  357. }
  358. // 学习时长记录到本地缓存 start
  359. if (unFulfilledFlag && currentTime % this.data.localRecordInterval === 0) {
  360. //未学完课程时 才将学习时长记录到本地
  361. this.recordLessonByLocal(currentTime);
  362. }
  363. // 学习时长记录到本地缓存 end
  364. this.setData({
  365. currentTime
  366. });
  367. } else {
  368. // 不可以快进的情况(课程未学完)
  369. if (unFulfilledFlag) {
  370. wx.showToast({
  371. title: '视频未学完,不可以快进!',
  372. icon: 'none'
  373. });
  374. this.data.videoContext.seek(this.data.nodeTime);
  375. }
  376. }
  377. },
  378. recordLessonByLocal(time) {
  379. console.log('缓存视频学习时长', time);
  380. wx.setStorageSync(`stu${this.data.obj.eduStuId}-course${this.data.id}`, time);
  381. },
  382. getLessonRecordByLocal() {
  383. return wx.getStorageSync(`stu${this.data.obj.eduStuId}-course${this.data.id}`) || 0;
  384. },
  385. removeLessonRecordByLocal() {
  386. //如果课程已学完,移除本地学习时长的记录
  387. wx.removeStorageSync(`stu${this.data.obj.eduStuId}-course${this.data.id}`);
  388. },
  389. scanOk(e) {
  390. this.data.scanOkTime = getEventParam(e, "currentTime");
  391. this.setData({
  392. show: false,
  393. });
  394. setTimeout(() => {
  395. this.data.videoContext.play();
  396. }, 500)
  397. },
  398. async toLive() {
  399. // if (this.data.item.liveStatus != liveStatus.LIVEEND) {
  400. // //如果直播课程的课程状态不为已结束,则点击按钮时先重新查询课程信息
  401. // await this.refresh();
  402. // }
  403. await this.refresh();
  404. let item = this.data.item;
  405. let obj = this.data.obj;
  406. let courseLiveStatus = item.liveStatus;
  407. if (courseLiveStatus == liveStatus.LIVING) {
  408. Route.toLive(liveSources.DEFAULT, item.courseName || item.ceremonyName,
  409. item.id, obj.eduStuId, obj.eduStuName, item.courseThumbnailUrl);
  410. } else if (courseLiveStatus == liveStatus.LIVEEND) {
  411. toast('直播已结束!');
  412. } else {
  413. toast('直播未开始!');
  414. }
  415. }
  416. });