detail.vue 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948
  1. <template>
  2. <view class="polyv_detail">
  3. <uni-nav-bar
  4. left-icon="back"
  5. :statusBar="true"
  6. fixed="true"
  7. :title="detail.courseName || '课程详情'"
  8. @clickLeft="clickLeft"
  9. ></uni-nav-bar>
  10. <view id="top">
  11. <view class="video_box" v-if="!playVid">
  12. <image
  13. :src="$method.splitImgHost(goodsData.coverUrl)"
  14. mode="widthFix"
  15. style="width: 100%; height: 421rpx"
  16. >
  17. </image>
  18. </view>
  19. <view v-else>
  20. <my-player
  21. ref="player"
  22. :playVid="playVid"
  23. :autoplay="autoplay"
  24. :allowSeek="isAllowSeek"
  25. :playbackRate="playbackRate"
  26. :videoCurrentTime="videoCurrentTime || 0"
  27. @playing="playing"
  28. @pause="pause"
  29. @ended="ended"
  30. @loadedmetadata="loadedmetadata"
  31. @timeupdate="timeupdate"
  32. @playerError="playerError"
  33. @studyLog="studyLog"
  34. ></my-player>
  35. </view>
  36. <view class="course_name">
  37. <view class="course_titles">
  38. <view class="video_t1" :class="{ one: !goodsData.buyNote }">{{
  39. detail.courseName
  40. }}</view>
  41. <view class="notice_wrap" v-if="goodsData.buyNote">
  42. <view class="video_t1_t" @click="studyNotice"> 学员须知 </view>
  43. </view>
  44. <view
  45. class="toggle_course"
  46. v-if="goodsTeacher.length > 1"
  47. @click="changeCourses()"
  48. >
  49. <image
  50. class="img"
  51. src="/pages3/static/imgs/toggle.png"
  52. mode="widthFix"
  53. ></image>
  54. <view class="toggle_name">切换课程</view>
  55. <view class="numbers">共{{ goodsTeacher.length }}门</view>
  56. </view>
  57. </view>
  58. </view>
  59. <u-line color="#D6D6DB" />
  60. <view>
  61. <view>
  62. <u-tabs
  63. :item-width="itemWidth()"
  64. :list="list"
  65. font-size="32"
  66. bar-width="24"
  67. :current="current"
  68. @change="change"
  69. active-color="#007AFF"
  70. ></u-tabs>
  71. </view>
  72. </view>
  73. <u-line color="#D6D6DB" />
  74. </view>
  75. <view class="box" :class="{ first_ml: current == 0 }">
  76. <scroll-view class="box_in" scroll-y="true">
  77. <!--目录 -->
  78. <view v-show="current == 0">
  79. <view
  80. class="top__header"
  81. v-if="livingItem"
  82. @click="goLive(livingItem)"
  83. >
  84. <image
  85. class="img"
  86. src="/pages3/static/imgs/live.png"
  87. mode="widthFix"
  88. ></image>
  89. <view class="note">正在直播中</view>
  90. <view class="title">{{ livingItem.sectionName }}</view>
  91. </view>
  92. <course-tree
  93. v-if="sectionItem.id || sectionItem.sectionId"
  94. :sectionMaxNum="goodsData.sectionMaxNum"
  95. :isRebuild="false"
  96. :isBuy="true"
  97. @isHaverebuild="isHaverebuild = true"
  98. ></course-tree>
  99. </view>
  100. <!--讲义 -->
  101. <view v-if="current == 1">
  102. <handouts-box :handoutsId="goodsData.handoutsId"></handouts-box>
  103. </view>
  104. <!--笔记 -->
  105. <view v-if="current == 2">
  106. <note-Box @jumpNote="jumpNote"></note-Box>
  107. </view>
  108. <!--答疑 -->
  109. <view v-if="current == 3">
  110. <answer-box :userId="userInfo ? userInfo.userId : 0"></answer-box>
  111. </view>
  112. <!--重修目录 -->
  113. <view v-if="current == 4">
  114. <course-tree
  115. v-if="sectionItem.id || sectionItem.sectionId"
  116. :sectionMaxNum="goodsData.sectionMaxNum"
  117. :isRebuild="true"
  118. :isBuy="true"
  119. ></course-tree>
  120. </view>
  121. </scroll-view>
  122. </view>
  123. <u-popup
  124. v-model="noticeShow"
  125. class="notice_modal"
  126. mode="center"
  127. border-radius="28"
  128. width="650rpx"
  129. height="622rpx"
  130. :mask-close-able="false"
  131. >
  132. <view class="content">
  133. <view class="title">学员须知</view>
  134. <scroll-view scroll-y="true">
  135. <view
  136. class="text"
  137. v-html="
  138. goodsData.buyNote && goodsData.buyNote.replace(/\n|\r\n/g, '<br>')
  139. "
  140. >
  141. </view>
  142. </scroll-view>
  143. <view
  144. class="had_read"
  145. :class="{ gray: CountTo > 0 }"
  146. @click="noticeConfirm()"
  147. >
  148. <text v-if="CountTo > 0"
  149. >请阅读学员须知,{{ " " + CountTo + "s" }}后可关闭</text
  150. >
  151. <text v-else>我已阅读学员须知</text>
  152. </view>
  153. </view>
  154. </u-popup>
  155. <!-- 倒计时提交 -->
  156. <u-popup
  157. v-model="noticeShow1"
  158. class="notice_modal"
  159. mode="center"
  160. border-radius="28"
  161. width="650rpx"
  162. height="262rpx"
  163. :mask-close-able="false"
  164. >
  165. <view class="content">
  166. <view class="title">提示</view>
  167. <view class="had_read">
  168. <text v-if="CountTo1 >= 0">视频学习时长不达标,请等待</text>
  169. <text v-if="CountTo1 >= 0">{{ " " + CountTo1 + "s" }}</text>
  170. </view>
  171. </view>
  172. </u-popup>
  173. <u-modal
  174. v-model="showMark"
  175. title="提示"
  176. @confirm="markConfirm"
  177. @cancel="toBack"
  178. confirm-text="复制学习网址"
  179. :show-cancel-button="true"
  180. cancel-text="关闭"
  181. >
  182. <view class="slot-content">
  183. <view>您的学习账号已经开通,请按照步骤操作,进行学习。</view>
  184. <view>1.复制学习地址:{{ markContent }}</view>
  185. <view>2.在【浏览器中】打开复制的学习网址</view>
  186. <view>3.打开学习网址后,选择【个人用户】进行登录</view>
  187. <view>(1)账号:您个人的身份证号码</view>
  188. <view>(2)密码:身份证号码,再加111111</view>
  189. </view>
  190. </u-modal>
  191. <!-- 切换课程弹窗 -->
  192. <u-popup
  193. v-model="toggleCourseShow"
  194. mode="bottom"
  195. border-radius="40"
  196. :mask-close-able="false"
  197. >
  198. <view class="popup_box">
  199. <view class="check_head">
  200. <view class="headers">
  201. <view class="grade">切换课程</view>
  202. <u-icon
  203. name="close"
  204. color="#9C9C9C"
  205. size="40"
  206. @click="closePop()"
  207. ></u-icon>
  208. </view>
  209. <view class="coruse_num">共{{ goodsTeacher.length }}门</view>
  210. <view class="menuSel">
  211. <scroll-view class="sub_sliper" scroll-x="true">
  212. <view
  213. v-for="(item, index) in subList"
  214. :key="index"
  215. style="margin-right: 50rpx; display: inline-block"
  216. >
  217. <view
  218. class="r_t1"
  219. :class="{ nactive: subIndex == index }"
  220. @click="cMenu(item, index)"
  221. >
  222. {{ item.subjectName }}
  223. </view>
  224. </view>
  225. </scroll-view>
  226. </view>
  227. </view>
  228. <view class="check_con">
  229. <scroll-view scroll-y="true" style="height: 700rpx">
  230. <view v-for="(courseItem, gTindex) in goodsTeacher" :key="gTindex">
  231. <view
  232. v-for="(item, index) in courseItem.courseList"
  233. :key="index"
  234. v-show="
  235. item.subjectId === newActiveSubjectId || !newActiveSubjectId
  236. "
  237. >
  238. <view class="course_items" v-if="item.show && item.show == 1">
  239. <view class="course_lefts">
  240. <view class="course_title">
  241. {{ item.courseName }}
  242. </view>
  243. <view
  244. v-if="courseItem.teaList && courseItem.teaList.length > 0"
  245. class="teacher_names"
  246. >
  247. <view
  248. v-for="(tea, tindex) in courseItem.teaList"
  249. :key="tindex"
  250. class="names"
  251. >{{ tea.aliasName }}
  252. </view>
  253. </view>
  254. <view class="course_pros">
  255. 学习进度
  256. <text>
  257. {{ item.stuAllNum + item.recordNum }}/{{
  258. item.secAllNum + item.examNum
  259. }}</text
  260. >
  261. </view>
  262. </view>
  263. <view
  264. class="course_rights"
  265. @click="jump(item, gTindex, 'jump')"
  266. >
  267. <view class="cicles">
  268. <u-icon
  269. name="arrow-right"
  270. color="#498AFE"
  271. size="20"
  272. ></u-icon>
  273. </view>
  274. <view class="intoStudy">进入学习</view>
  275. </view>
  276. </view>
  277. </view>
  278. </view>
  279. </scroll-view>
  280. </view>
  281. </view>
  282. </u-popup>
  283. <!-- 摄像头 -->
  284. <Popup-camera
  285. @submitPhoto="submitPhoto"
  286. :visible.sync="showCamera"
  287. ref="camera"
  288. />
  289. </view>
  290. </template>
  291. <script>
  292. import handoutsBox from "@/components/course/handoutsBox.vue";
  293. import courseTree from "@/components/course/courseTree.vue";
  294. import PopupCamera from "../../components/popup/camera.vue";
  295. import myPlayer from "@/components/myPlayer/polyvPlayer.vue";
  296. import noteBox from "@/components/course/noteBox.vue";
  297. import answerBox from "@/components/course/answerBox.vue";
  298. import { mapGetters, mapMutations } from "vuex";
  299. import { lockAction } from "../../utils/lock";
  300. import { debounce, throttle, reload } from "../../utils/common";
  301. export default {
  302. components: {
  303. handoutsBox,
  304. myPlayer,
  305. noteBox,
  306. answerBox,
  307. courseTree,
  308. PopupCamera,
  309. },
  310. data() {
  311. return {
  312. markContent: "http://admin.zhujianpeixun.com/",
  313. showMark: false,
  314. lockTimer: null,
  315. orderGoodsId: 0,
  316. noticeShow: false,
  317. showSet: false,
  318. courseId: 0,
  319. current: 0,
  320. vid: "",
  321. goodsId: 0,
  322. goodsData: {},
  323. showCamera: false,
  324. goodsPlayConfig: null,
  325. autoplay: false,
  326. isAllowSeek: "no",
  327. playbackRate: [1.0],
  328. timer: null,
  329. goodsPhotographConfig: null,
  330. playTime: 0, // 播放时刻
  331. currentTime: 0,
  332. avatarUrl: "",
  333. ossAvatarUrl: "",
  334. gradeId: 0,
  335. chapterId: 0,
  336. moduleId: 0,
  337. videoCurrentTime: 0,
  338. photoIndex: 0,
  339. photoNum: 0,
  340. photoList: [], //拍照的时间点
  341. photoConfig: false, //是否配置好拍照次数
  342. photoHistoryList: [], //已拍照历史的下标点
  343. sectionItem: {},
  344. isRebuild: false, //视频是否从重修目录点击
  345. livingItem: "",
  346. option: null,
  347. toggleCourseShow: false, // 切换课程弹窗
  348. courseList: [], // 课程列表
  349. subList: [],
  350. subIndex: 0,
  351. goodsTeacher: [],
  352. teacherList: [],
  353. teacherIndex: 0,
  354. newActiveSubjectId: "", //当前选中ID
  355. compareFaceData: 0, // 拍照匹配相似度
  356. prendreAutoCarme: false, // 是否发起授权相机
  357. CountTo: 0, // 倒计时
  358. CountTo1: 0,
  359. menuAllList: [],
  360. curPlayIndex: 0, // 正在播放的节的下标
  361. faceUrl: "",
  362. erJianErZao: false,
  363. jjShiGongYuan: false,
  364. pauseTime: 0,
  365. pauseTimer: null,
  366. isReach: false,
  367. noticeShow1: false,
  368. refPlv: null,
  369. isCache: false,
  370. isHaverebuild: false,
  371. isPlaying: false,
  372. beforeHideIsPlaying: false,
  373. isLeave: false,
  374. text: "",
  375. throttleFn: throttle(this.postStudyRecord, 15000),
  376. };
  377. },
  378. computed: {
  379. ...mapGetters(["userInfo", "config"]),
  380. playSecIsLearn() {
  381. if (!this.playVid) {
  382. return false;
  383. }
  384. return this.sectionItem.learning != 1;
  385. },
  386. orderNum() {
  387. return this.goodsData.goodsLearningOrder;
  388. },
  389. list() {
  390. let list = [
  391. {
  392. name: "目录",
  393. },
  394. {
  395. name: "讲义",
  396. },
  397. {
  398. name: "笔记",
  399. },
  400. {
  401. name: "答疑",
  402. },
  403. ];
  404. if (this.isHaverebuild) {
  405. list.push({ name: "重修目录" });
  406. }
  407. return list;
  408. },
  409. params: function () {
  410. return (keys = ["orderGoodsId", "goodsId", "courseId"]) => {
  411. let params = {};
  412. for (const key of keys) {
  413. params[key] = this[key];
  414. }
  415. return params;
  416. };
  417. },
  418. detail() {
  419. if (!this.courseId || !this.courseList.length) {
  420. return {};
  421. }
  422. return this.courseList.find((e) => e.courseId == this.courseId);
  423. },
  424. sectionId() {
  425. return this.sectionItem.sectionId || this.sectionItem.id;
  426. },
  427. playVid() {
  428. return this.sectionItem.recordingUrl;
  429. },
  430. isPlayRebuild() {
  431. return this.sectionItem.rebuild != 1;
  432. },
  433. },
  434. async onLoad(option) {
  435. if (option.isOther) {
  436. this.showMark = true;
  437. return;
  438. }
  439. this.option = option;
  440. let { skipPort, id, goodsId, orderGoodsId, gradeId, informId } = option;
  441. this.orderGoodsId = Number(orderGoodsId) || "";
  442. this.courseId = Number(id) || "";
  443. this.goodsId = Number(goodsId);
  444. this.gradeId = Number(gradeId);
  445. this.isLeave = false;
  446. uni.addInterceptor("navigateTo", {
  447. success: (e) => {
  448. this.isLeave = true;
  449. this.isCache = true;
  450. },
  451. });
  452. if (skipPort) {
  453. await this.$method.skipLogin(skipPort);
  454. }
  455. if (this.$method.isGoLogin()) {
  456. return;
  457. }
  458. if (!this.lockTimer) {
  459. lockAction();
  460. this.lockTimer = setInterval(lockAction, 10000);
  461. }
  462. // 公众号模板消息的数据埋点
  463. informId && this.clickOfficial(informId);
  464. this.init();
  465. },
  466. async onShow() {
  467. if (!this.$method.isLogin()) {
  468. return;
  469. }
  470. if (this.isCache) {
  471. console.log("返回刷新数据");
  472. this.isCache = false;
  473. this.sectionItem = {};
  474. this.beforeHideIsPlaying = false;
  475. reload(this.option);
  476. return;
  477. }
  478. if (this.beforeHideIsPlaying) {
  479. console.log("显示重新播放");
  480. if (this.text) {
  481. this.refPlv.playPause();
  482. return;
  483. }
  484. this.refPlv.resumeVideo();
  485. }
  486. },
  487. onUnload() {
  488. console.log("onUnloadonUnloadonUnloadonUnload");
  489. this.originUnload();
  490. clearInterval(this.lockTimer);
  491. uni.removeInterceptor("navigateTo");
  492. },
  493. onHide() {
  494. this.beforeHideIsPlaying = this.isPlaying;
  495. if (this.isPlaying) {
  496. this.refPlv.playPause();
  497. }
  498. },
  499. mounted() {},
  500. methods: {
  501. ...mapMutations(["updateChapterOpen", "updateLiveLast"]),
  502. async init() {
  503. // #ifdef MP-WEIXIN
  504. this.isAllowSeek = "no";
  505. // #endif
  506. // #ifdef H5
  507. this.isAllowSeek = "on";
  508. // #endif
  509. await this.isCanLearn();
  510. this.courseCourseList();
  511. },
  512. // 七大员是否能进入学习
  513. async qCheckIsCanLearn() {
  514. let res = await this.$api.qCheckIsCanLearn(this.orderGoodsId);
  515. if (res.data.code !== 200) {
  516. uni.showModal({
  517. showCancel: false,
  518. title: "提示",
  519. content: res.data.msg,
  520. success: (resultst) => {
  521. uni.navigateBack();
  522. },
  523. });
  524. return Promise.reject();
  525. }
  526. let res1 = await this.$api.syncSevenPublicClass({
  527. orderGoodsId: this.orderGoodsId,
  528. });
  529. if (res1.data.code !== 200) {
  530. uni.showModal({
  531. showCancel: false,
  532. title: "提示",
  533. content: "无法进入学习!",
  534. success: (resultst) => {
  535. uni.navigateBack();
  536. },
  537. });
  538. return Promise.reject();
  539. }
  540. },
  541. // 新增微信公众号模板消息点击数据
  542. clickOfficial(informId) {
  543. this.$http({
  544. url: "/data/click",
  545. method: "post",
  546. data: { informId },
  547. });
  548. },
  549. // 点击课程目录
  550. cMenu(item, index) {
  551. this.subIndex = index;
  552. this.newActiveSubjectId = item.subjectId;
  553. },
  554. courseCourseList() {
  555. this.photoConfig = false;
  556. this.courseList = [];
  557. this.$api
  558. .courseCourseList({
  559. pageNum: 1,
  560. pageSize: 200,
  561. ...this.params(["goodsId", "orderGoodsId", "gradeId"]),
  562. })
  563. .then((res) => {
  564. if (res.data.code == 200) {
  565. this.courseList = res.data.rows;
  566. if (res.data.total > 1) {
  567. // 科目
  568. this.subList = [{ subjectId: 0, subjectName: "所有" }];
  569. this.courseList.forEach((item) => {
  570. if (!this.subList.find((e) => e.subjectId == item.subjectId)) {
  571. this.subList.push(item);
  572. }
  573. });
  574. this.getUserWatchLast();
  575. } else {
  576. this.originOnShow();
  577. this.originMounted();
  578. }
  579. }
  580. });
  581. },
  582. // 查询用户最后一次看的录播的信息
  583. getUserWatchLast() {
  584. this.$http({
  585. url: "/study/record/getUserWatchLast",
  586. method: "get",
  587. data: {
  588. orderGoodsId: this.orderGoodsId,
  589. },
  590. }).then((res) => {
  591. if (res.data.code == 200 && res.data.data) {
  592. this.courseId = res.data.data.courseId;
  593. }
  594. if (!this.courseId) {
  595. this.courseId = this.courseList[0].courseId;
  596. }
  597. this.originOnShow();
  598. this.originMounted();
  599. //获取商品双师资模板
  600. this.getCourseTeacher(this.courseList);
  601. });
  602. },
  603. getCourseTeacher(rows) {
  604. this.goodsTeacher = [];
  605. //获取商品双师资模板
  606. this.$api
  607. .courseTeacherList({
  608. goodsId: this.goodsId,
  609. })
  610. .then((res1) => {
  611. if (res1.data.data && res1.data.data.length > 0) {
  612. //课程老师模板
  613. let teacherTel = res1.data.data;
  614. //商品课程
  615. let courses = rows;
  616. teacherTel.forEach((tea) => {
  617. let dataList = [];
  618. let teacherList = [];
  619. courses.forEach((item) => {
  620. let data = tea.courseList.filter(
  621. (x) => x.courseId == item.courseId
  622. );
  623. if (data && data.length > 0) {
  624. dataList.push(item);
  625. teacherList = tea.courseList;
  626. }
  627. });
  628. let result = {
  629. teaList: teacherList,
  630. courseList: dataList,
  631. };
  632. this.goodsTeacher.push(result);
  633. });
  634. if (this.goodsTeacher && this.goodsTeacher.length > 0) {
  635. let courseIds = [];
  636. this.goodsTeacher.forEach((item) => {
  637. item.courseList.forEach((course) => {
  638. courseIds.push(course.courseId);
  639. });
  640. });
  641. if (courseIds.length > 0) {
  642. courses.forEach((item) => {
  643. if (!courseIds.includes(item.courseId)) {
  644. let data = {
  645. teaList: [],
  646. courseList: [],
  647. };
  648. data.courseList.push(item);
  649. this.goodsTeacher.push(data);
  650. }
  651. });
  652. }
  653. this.goodsTeacher.forEach((item) => {
  654. if (item.courseList && item.courseList.length > 0) {
  655. item.courseList[0].show = 1;
  656. }
  657. });
  658. }
  659. } else {
  660. //没有双师资模板
  661. rows.forEach((item) => {
  662. item.show = 1;
  663. let data = {
  664. teaList: [],
  665. courseList: [],
  666. };
  667. data.courseList.push(item);
  668. this.goodsTeacher.push(data);
  669. });
  670. }
  671. this.goodsTeacher.forEach((item) => {
  672. if (item.courseList.some((x) => x.courseId == this.courseId)) {
  673. this.teacherList = item.teaList;
  674. }
  675. });
  676. });
  677. },
  678. erJianErZaoPauseTip() {
  679. if (this.playSecIsLearn && this.erJianErZao && !this.isLeave) {
  680. if (this.pauseTimer) {
  681. return;
  682. }
  683. this.pauseTime = Date.now();
  684. this.pauseTimer = setInterval(() => {
  685. console.log("暂停时间", Date.now() - this.pauseTime, this.pauseTime);
  686. if (Date.now() - this.pauseTime > 20 * 1000) {
  687. // 5 * 60 * 1000
  688. this.refPlv.exitFullScreen();
  689. this.text = "暂停";
  690. if (this.showCamera) {
  691. this.text = "拍照停留";
  692. this.closeCamera();
  693. }
  694. uni.showModal({
  695. title: "提示",
  696. showCancel: false,
  697. content: `检测${this.text}时间过长,刷新当前页面`,
  698. cancelText: "取消",
  699. confirmText: "确定",
  700. success: (res) => {
  701. if (res.confirm) {
  702. this.text = "";
  703. reload(this.option);
  704. // #ifdef MP-WEIXIN
  705. this.sectionItem = {};
  706. // #endif
  707. }
  708. },
  709. });
  710. this.clearPauseTimer();
  711. }
  712. }, 5000);
  713. }
  714. },
  715. // 原来onshow里面的内容
  716. async originOnShow() {
  717. await this.studyRecordMenuAllList();
  718. // 消息过来 定位某个节
  719. if (this.option.noteSecond) {
  720. this.jumpNote({
  721. sectionType: 1,
  722. ...this.option,
  723. });
  724. return;
  725. }
  726. this.studyRecordQueryLiveLast();
  727. },
  728. // 原来的mouted内容
  729. originMounted() {
  730. uni.$on(
  731. "getSection",
  732. debounce((item) => {
  733. if (!this.refPlv && this.playVid) {
  734. this.refPlv = this.$refs.player;
  735. }
  736. if (this.isPlaying) {
  737. this.refPlv.playPause();
  738. }
  739. this.postStudyRecord(0);
  740. this.photoConfig = false;
  741. this.photoList = [];
  742. this.moduleId = item.moduleId || null;
  743. this.chapterId = item.chapterId || null;
  744. this.playVideo(item);
  745. })
  746. );
  747. uni.$on("isRebuild", (item) => {
  748. this.isRebuild = item;
  749. });
  750. this.updateChapterOpen(true);
  751. },
  752. clearPauseTimer() {
  753. if (this.pauseTimer) {
  754. this.pauseTime = 0;
  755. clearInterval(this.pauseTimer);
  756. this.pauseTimer = null;
  757. }
  758. },
  759. // 原来onUnload里面的内容
  760. originUnload() {
  761. if (this.sectionId > 0 && this.isPlaying) {
  762. //退出提交记录
  763. this.postStudyRecord();
  764. }
  765. this.refPlv = null;
  766. //移除所有的事件监听器
  767. uni.$off();
  768. this.clearPauseTimer();
  769. if (this.lockTimer) {
  770. clearInterval(this.lockTimer);
  771. this.$api
  772. .lockDelLock({
  773. action: "jxjy",
  774. uuid: this.$method.getUuid(),
  775. })
  776. .then((res) => {
  777. uni.hideLoading();
  778. });
  779. }
  780. },
  781. changeCourses() {
  782. this.toggleCourseShow = true;
  783. },
  784. closePop() {
  785. this.toggleCourseShow = false;
  786. },
  787. async activeFunc(item, index) {
  788. this.teacherIndex = index;
  789. let findResult = "";
  790. this.goodsTeacher.forEach((citem, index) => {
  791. citem.courseList.forEach((e, e_index) => {
  792. if (e.courseId == item.courseId) {
  793. findResult = e;
  794. }
  795. });
  796. });
  797. this.jump(findResult, 1);
  798. },
  799. // 进入学习
  800. async jump(item, index, type) {
  801. if (this.courseId === item.courseId) {
  802. this.toggleCourseShow = false;
  803. return;
  804. }
  805. if (this.orderNum == 2 && index != 0) {
  806. let prevItem = this.courseList[index - 1];
  807. if (
  808. prevItem.stuAllNum + prevItem.recordNum <
  809. prevItem.secAllNum + prevItem.examNum
  810. ) {
  811. uni.showToast({
  812. icon: "none",
  813. title: "请按顺序学完上一课再学习这一课",
  814. });
  815. return;
  816. }
  817. }
  818. // 打回 需重修
  819. if (item.rebuild === 0) {
  820. this.$navTo.togo("/pages2/learn/details", {
  821. id: item.courseId,
  822. ...this.params(["goodsId", "orderGoodsId", "gradeId"]),
  823. });
  824. return;
  825. }
  826. if (type) {
  827. this.teacherIndex = 0;
  828. this.goodsTeacher.forEach((citem) => {
  829. if (citem.courseList.some((x) => x.courseId == item.courseId)) {
  830. this.teacherList = citem.teaList;
  831. }
  832. });
  833. }
  834. this.sectionItem = {};
  835. this.toggleCourseShow = false;
  836. this.courseId = item.courseId;
  837. this.originOnShow();
  838. },
  839. clickLeft() {
  840. // uni.navigateBack()
  841. uni.switchTab({
  842. url: "/pages/learn/index",
  843. });
  844. },
  845. toBack(delta = 1) {
  846. uni.navigateBack({
  847. delta,
  848. });
  849. },
  850. markConfirm() {
  851. uni.setClipboardData({
  852. data: this.markContent,
  853. success: () => {
  854. setTimeout(this.toBack, 1000);
  855. },
  856. });
  857. },
  858. noticeConfirm() {
  859. if (this.CountTo <= 0) {
  860. this.noticeShow = false;
  861. if (this.autoplay) {
  862. this.refPlv && this.refPlv.resumeVideo();
  863. }
  864. this.$api.baseHandoutTip({
  865. orderGoodsId: this.orderGoodsId,
  866. });
  867. }
  868. },
  869. baseHandoutTipList() {
  870. this.$api
  871. .baseHandoutTipList({
  872. orderGoodsId: this.orderGoodsId,
  873. })
  874. .then((res) => {
  875. if (res.data.rows.length == 0) {
  876. this.noticeShow = true;
  877. this.CountTo = 30;
  878. var timer = setInterval(() => {
  879. this.CountTo--;
  880. if (this.CountTo < 0) {
  881. clearInterval(timer);
  882. }
  883. }, 1000);
  884. }
  885. });
  886. },
  887. /**
  888. * 获取上次观看的直播
  889. */
  890. studyRecordGetLastLive() {
  891. this.$api
  892. .studyRecordGetLastLive({
  893. orderGoodsId: this.orderGoodsId,
  894. courseId: this.courseId,
  895. })
  896. .then((res) => {
  897. this.updateLiveLast(res.data.data);
  898. });
  899. },
  900. async initPlayVideo(sectionItem) {
  901. this.moduleId = sectionItem.moduleId;
  902. this.chapterId = sectionItem.chapterId;
  903. this.sectionItem = sectionItem;
  904. if (sectionItem.sectionType == 1) {
  905. //录播
  906. this.playVideo(sectionItem);
  907. } else if (sectionItem.sectionType == 2) {
  908. //直播
  909. this.studyRecordGetLastLive();
  910. } else if (sectionItem.sectionType == 3) {
  911. //回放
  912. this.playVideo(sectionItem);
  913. } else if (sectionItem.doType == 2) {
  914. uni.showModal({
  915. title: "温馨提示",
  916. content: "当前节视频已学完,是否进入考试?",
  917. success: (res) => {
  918. if (res.confirm) {
  919. this.toQuestionBank(sectionItem);
  920. }
  921. },
  922. });
  923. return;
  924. }
  925. },
  926. async submitPhoto(url, compareFaceData) {
  927. this.ossAvatarUrl = url;
  928. this.compareFaceData = compareFaceData;
  929. this.postCoursePhotoRecord()
  930. .then(async (res) => {
  931. this.photoHistoryList[this.photoIndex] = true;
  932. await this.postStudyRecord();
  933. if (this.erJianErZao && this.isReach) {
  934. await this.postStudyRecord(1);
  935. this.closeCamera();
  936. this.nextSection();
  937. return;
  938. }
  939. this.closeCamera();
  940. this.refPlv.resumeVideo();
  941. })
  942. .catch((err) => {
  943. uni.showToast({
  944. title: "上传接口报错,请重新拍照上传" + err,
  945. icon: "none",
  946. });
  947. this.$refs["camera"].reTake();
  948. });
  949. },
  950. toQuestionBank(sectionItem) {
  951. // type 1模块 2章 3节 4章卷 5模块卷
  952. uni.navigateTo({
  953. url:
  954. "/pages2/class/questionBank?courseId=" +
  955. this.courseId +
  956. "&gradeId=" +
  957. this.gradeId +
  958. "&isFromVideo=1&id=" +
  959. sectionItem.id +
  960. "&goodsid=" +
  961. this.goodsId +
  962. "&moduleId=" +
  963. (sectionItem.moduleId || 0) +
  964. "&chapterId=" +
  965. (sectionItem.chapterId || 0) +
  966. "&orderGoodsId=" +
  967. this.orderGoodsId +
  968. "&type=" +
  969. (sectionItem.type == 4 ? 1 : 3) +
  970. "&learning=" +
  971. sectionItem.studyStatus +
  972. "&isBackVideo=" +
  973. 1,
  974. });
  975. },
  976. studyRecordQueryLiveLast() {
  977. this.$api
  978. .studyRecordQueryLiveLast({
  979. gradeId: this.gradeId,
  980. orderGoodsId: this.orderGoodsId,
  981. courseId: this.courseId,
  982. })
  983. .then((res) => {
  984. let { data } = res.data;
  985. if (!data.sectionId) {
  986. data = this.menuAllList[0];
  987. }
  988. if (data.learning == 1 && this.orderNum == 2) {
  989. let next = this.menuAllList.find((e) => e.studyStatus != 1);
  990. next && (data = next);
  991. }
  992. this.initPlayVideo(data);
  993. });
  994. },
  995. goLive(item) {
  996. let moduleId = item.moduleId || 0;
  997. let chapterId = item.chapterId || 0;
  998. let sectionId = item.sectionId || item.menuId;
  999. let uuid = new Date().valueOf() + "";
  1000. // buyCourse 是否购买课程:1是 0否
  1001. let encode = encodeURIComponent(
  1002. this.config.hostLive +
  1003. "/pages/live/index?token=" +
  1004. uni.getStorageSync("token") +
  1005. "&userInfo=" +
  1006. JSON.stringify(this.userInfo) +
  1007. "&channelId=" +
  1008. item.liveUrl +
  1009. "&gradeId=" +
  1010. this.gradeId +
  1011. "&courseId=" +
  1012. this.courseId +
  1013. "&goodsId=" +
  1014. this.goodsId +
  1015. "&orderGoodsId=" +
  1016. this.orderGoodsId +
  1017. "&sectionId=" +
  1018. sectionId +
  1019. "&chapterId=" +
  1020. chapterId +
  1021. "&moduleId=" +
  1022. moduleId +
  1023. "&buyCourse=1" +
  1024. "&ident=" +
  1025. uuid
  1026. );
  1027. uni.navigateTo({
  1028. url: `../../pages/webview/index?url=` + encode,
  1029. });
  1030. },
  1031. studyRecordMenuAllList() {
  1032. // study/record/menuAllList
  1033. return this.$api
  1034. .studMenuAllList({
  1035. courseId: this.courseId,
  1036. ...this.params(["goodsId", "orderGoodsId", "gradeId"]),
  1037. })
  1038. .then((res) => {
  1039. let nowTime = Number(new Date().getTime() / 1000).toFixed(0);
  1040. if (res.data.data) {
  1041. this.menuAllList = res.data.data.filter(
  1042. (e) => e.doType != 1 || (e.doType == 2 && e.studyStatus == 1)
  1043. );
  1044. this.livingItem = res.data.data.find(
  1045. (item) =>
  1046. item.liveStartTime <= nowTime && item.liveEndTime > nowTime
  1047. );
  1048. }
  1049. return Promise.resolve();
  1050. });
  1051. },
  1052. async getbaseprofiletplists() {
  1053. let {
  1054. data: { code, rows },
  1055. } = await this.$api.getbaseprofiletplists({
  1056. goodsId: this.goodsId,
  1057. orderGoodsId: this.orderGoodsId,
  1058. });
  1059. if (code === 200 && rows.length && rows[0].keyValue) {
  1060. let baseRes = await this.$api.getbaseprofiletpId(this.goodsId);
  1061. if (baseRes.data.code === 200 && baseRes.data.data) {
  1062. let {
  1063. data: { code, data },
  1064. } = await this.$api.getbaseprofiletpgetInfo({
  1065. goodsId: this.goodsId,
  1066. orderGoodsId: this.orderGoodsId,
  1067. });
  1068. if (
  1069. code === 200 &&
  1070. (!data || (data.status === 3 && data.changeStatus === 1))
  1071. ) {
  1072. uni.showModal({
  1073. content: !data
  1074. ? "请前往填写资料"
  1075. : "资料审核不通过,请前往重新填写",
  1076. cancelText: "返回",
  1077. success: (resultst) => {
  1078. if (resultst.confirm) {
  1079. this.$navTo.togo("/pages2/verify/input", {
  1080. id: this.goodsId,
  1081. orderGoodsId: this.orderGoodsId,
  1082. });
  1083. }
  1084. if (resultst.cancel) {
  1085. uni.navigateBack();
  1086. }
  1087. },
  1088. });
  1089. return Promise.reject();
  1090. } else if (data.status === 1 && JSON.parse(rows[0].keyValue2)[0]) {
  1091. let {
  1092. data: { code, data },
  1093. } = await this.$api.getbaseprofileStampgetInfo({
  1094. goodsId: this.goodsId,
  1095. orderGoodsId: this.orderGoodsId,
  1096. });
  1097. if (
  1098. code === 200 &&
  1099. (!data || (data.status === 3 && data.changeStatus === 1))
  1100. ) {
  1101. uni.showModal({
  1102. content: !data
  1103. ? "请前往填写盖章资料"
  1104. : "资料盖章审核不通过,请前往重新填写",
  1105. cancelText: "返回",
  1106. success: (resultst) => {
  1107. if (resultst.confirm) {
  1108. this.$navTo.togo("/pages2/verify/input2", {
  1109. id: this.goodsId,
  1110. orderGoodsId: this.orderGoodsId,
  1111. });
  1112. }
  1113. if (resultst.cancel) {
  1114. uni.navigateBack();
  1115. }
  1116. },
  1117. });
  1118. return Promise.reject();
  1119. }
  1120. }
  1121. }
  1122. }
  1123. return Promise.resolve();
  1124. },
  1125. async isCanLearn() {
  1126. this.option.isQ !== "" && (await this.qCheckIsCanLearn());
  1127. await this.getbaseprofiletplists();
  1128. await this.getGradeInfo();
  1129. await this.getGoodsDetail();
  1130. },
  1131. /**
  1132. * 计算tabs宽度
  1133. */
  1134. itemWidth() {
  1135. return 100 / this.list.length + "%";
  1136. },
  1137. loadedmetadata(event) {
  1138. this.refPlv = this.$refs.player;
  1139. },
  1140. getPhotoLastRecord() {
  1141. let { chapterId, sectionId, moduleId } = this.sectionItem;
  1142. let data = {
  1143. ...this.params(),
  1144. sectionId: sectionId || 0,
  1145. gradeId: this.gradeId,
  1146. chapterId: chapterId || 0,
  1147. moduleId: moduleId || 0,
  1148. };
  1149. this.$api.getPhotoLastRecord(data).then((res) => {
  1150. if (res.data.code == 200) {
  1151. //清空历史数据
  1152. this.photoHistoryList = [];
  1153. this.photoList = [];
  1154. for (let i = 0; i < res.data.data.length; i++) {
  1155. //-2存储随机拍照数组
  1156. if (res.data.data[i].photoIndex == -2) {
  1157. this.photoList =
  1158. res.data.data[i].timeInterval &&
  1159. res.data.data[i].timeInterval.split(",");
  1160. } else {
  1161. this.photoHistoryList.push(res.data.data[i].photoIndex);
  1162. }
  1163. }
  1164. }
  1165. });
  1166. },
  1167. //postTime 只提交随机时间
  1168. postCoursePhotoRecord(postTime = false) {
  1169. return new Promise((resolve, reject) => {
  1170. let data = {
  1171. ...this.params(["orderGoodsId", "goodsId", "courseId", "gradeId"]),
  1172. photo: this.ossAvatarUrl,
  1173. sectionId: this.sectionId,
  1174. photoTime: this.playTime || 0,
  1175. photoIndex: postTime ? -2 : this.photoIndex, //从0算起,-2只提交随机时间
  1176. photoNum: this.photoNum,
  1177. chapterId: this.chapterId,
  1178. moduleId: this.moduleId,
  1179. timeInterval: postTime ? this.photoList.join(",") : "",
  1180. };
  1181. this.$api
  1182. .coursePhotoRecord(data)
  1183. .then((res) => {
  1184. if (res.data.code == 200) {
  1185. resolve();
  1186. } else {
  1187. reject();
  1188. }
  1189. })
  1190. .catch((err) => {
  1191. reject();
  1192. });
  1193. });
  1194. },
  1195. randomNum(minNum, maxNum) {
  1196. switch (arguments.length) {
  1197. case 1:
  1198. return parseInt(Math.random() * minNum + 1, 10);
  1199. break;
  1200. case 2:
  1201. return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
  1202. break;
  1203. default:
  1204. return 0;
  1205. break;
  1206. }
  1207. },
  1208. //配置随机拍照时间
  1209. configPhoto(duration) {
  1210. if (this.photoConfig && this.photoList.length) {
  1211. return;
  1212. }
  1213. let totalVideoTime = this.refPlv.getDuration();
  1214. this.photoConfig = true;
  1215. if (this.erJianErZao) {
  1216. this.photoList = this.randomConfig(totalVideoTime, duration);
  1217. console.log(this.photoList, "this.photoList");
  1218. return;
  1219. }
  1220. //没有历史拍照间隔数据
  1221. if (!this.photoList || this.photoList.length == 0) {
  1222. // 46 * 60
  1223. this.photoList = this.commonConfig(
  1224. totalVideoTime,
  1225. this.jjShiGongYuan ? 10 : undefined
  1226. );
  1227. this.postCoursePhotoRecord(true); //提交随机拍照时间数组
  1228. }
  1229. console.log(this.photoList, "this.photoList");
  1230. },
  1231. // 二建随机拍摄时间
  1232. randomConfig(totalVideoTime, duration) {
  1233. this.photoHistoryList = [];
  1234. let photoList = [duration];
  1235. let pre = duration;
  1236. if (totalVideoTime > 300) {
  1237. while (pre <= totalVideoTime) {
  1238. pre += this.randomNum(780, 900);
  1239. pre <= totalVideoTime && photoList.push(pre);
  1240. }
  1241. if (totalVideoTime - 300 > photoList.slice(-1)[0]) {
  1242. photoList.push(this.randomNum(totalVideoTime - 180, totalVideoTime));
  1243. }
  1244. }
  1245. return photoList;
  1246. },
  1247. // 随机前后五分钟
  1248. commonConfig(totalVideoTime, fixS) {
  1249. let photoList = [0];
  1250. // 固定间隔时间取
  1251. if (fixS) {
  1252. let num = Math.ceil(totalVideoTime / fixS);
  1253. for (let i = 1; i < num; i++) {
  1254. photoList.push(i * fixS);
  1255. }
  1256. this.photoNum = num;
  1257. } else {
  1258. if (this.photoNum == 3) {
  1259. if (totalVideoTime >= 900) {
  1260. //大于15分钟
  1261. let centerTime = Math.floor(totalVideoTime / 2); //获取中间时间
  1262. let centerMinTime = centerTime - 300; //前后5分钟
  1263. let centerMaxTime = centerTime + 300;
  1264. let centerTakeTime = this.randomNum(centerMinTime, centerMaxTime);
  1265. photoList.push(centerTakeTime); //中间拍一张
  1266. let endMaxTime = totalVideoTime - 60;
  1267. let endMinTime = totalVideoTime - 300;
  1268. let endTakeTime = this.randomNum(endMinTime, endMaxTime);
  1269. photoList.push(endTakeTime); //最后拍一张
  1270. } else {
  1271. //小于15分钟
  1272. let centerTime = this.randomNum(
  1273. (1 / 3) * totalVideoTime,
  1274. (2 / 3) * totalVideoTime
  1275. );
  1276. photoList.push(centerTime);
  1277. let endTakeTime = this.randomNum(
  1278. (2 / 3) * totalVideoTime,
  1279. totalVideoTime
  1280. );
  1281. photoList.push(endTakeTime);
  1282. }
  1283. }
  1284. }
  1285. return photoList;
  1286. },
  1287. studyNotice() {
  1288. this.noticeShow = true;
  1289. },
  1290. //正常播放视频
  1291. async playVideo(item) {
  1292. this.sectionItem = item;
  1293. let { learning, videoCurrentTime, sectionId, recordingUrl } = item;
  1294. this.videoCurrentTime =
  1295. videoCurrentTime || (await this.getRecordLast(item));
  1296. // 往前播3秒
  1297. if (this.videoCurrentTime > 3) {
  1298. this.videoCurrentTime -= 3;
  1299. }
  1300. // 查找拍照历史
  1301. if ((this.photoNum > 0 || this.jjShiGongYuan) && learning != 1) {
  1302. await this.getPhotoLastRecord();
  1303. }
  1304. if (this.refPlv) {
  1305. this.refPlv.changeVid({
  1306. vid: recordingUrl,
  1307. videoCurrentTime: this.videoCurrentTime,
  1308. });
  1309. }
  1310. },
  1311. getRecordLast(sectionItem) {
  1312. let { chapterId, sectionId, courseId, moduleId } = sectionItem;
  1313. return new Promise((resolve) => {
  1314. let data = {
  1315. ...this.params(),
  1316. gradeId:
  1317. this.gradeId || this.gradeId == 0 ? Number(this.gradeId) : null,
  1318. sectionId: sectionId || 0,
  1319. chapterId: chapterId || 0,
  1320. moduleId: moduleId || 0,
  1321. };
  1322. this.$api.recordLast(data).then((res) => {
  1323. resolve(res.data.data.videoCurrentTime);
  1324. });
  1325. });
  1326. },
  1327. jumpNote(item) {
  1328. this.$u.toast("即将跳到笔记位置");
  1329. if (this.sectionId != item.sectionId) {
  1330. this.initPlayVideo({
  1331. sectionType: 1,
  1332. ...item,
  1333. videoCurrentTime: Number(item.noteSecond),
  1334. });
  1335. } else {
  1336. this.refPlv.seekVideo(item.noteSecond);
  1337. }
  1338. },
  1339. getGradeInfo() {
  1340. // 即刻 1 待定2 日期3
  1341. return this.$api.goodsGradeInfo(this.gradeId).then((res) => {
  1342. if (res.data.code == 200) {
  1343. let { data } = res.data;
  1344. if (
  1345. data.learningStatus == 2 ||
  1346. (data.learningStatus == 3 &&
  1347. Number(data.learningTimeStart) > Number(new Date() / 1000))
  1348. ) {
  1349. uni.showModal({
  1350. showCancel: false,
  1351. confirmText: "确定",
  1352. content:
  1353. "当前课程正在申请中,正式开班后方可进行学习,请耐心等候!",
  1354. success: function (resultst) {
  1355. uni.navigateBack();
  1356. },
  1357. });
  1358. return Promise.reject();
  1359. }
  1360. }
  1361. });
  1362. },
  1363. postStudyRecord(status = 0, sectionId = this.sectionId) {
  1364. if (!this.refPlv && this.playVid) {
  1365. return;
  1366. }
  1367. let currentTime = this.refPlv.playCurrentTime();
  1368. let PlayDuration = this.refPlv.playVideoTime();
  1369. if (currentTime < 10 && !this.ossAvatarUrl) {
  1370. return;
  1371. }
  1372. let self = this;
  1373. let fromPlat = 1;
  1374. // #ifdef H5
  1375. fromPlat = 3;
  1376. // #endif
  1377. let data = {
  1378. ...this.params([
  1379. "orderGoodsId",
  1380. "goodsId",
  1381. "courseId",
  1382. "erJianErZao",
  1383. "gradeId",
  1384. ]),
  1385. fromPlat, //来源平台 1小程序 2网站
  1386. photo: self.ossAvatarUrl,
  1387. sectionId: sectionId || 0,
  1388. studyDuration: parseInt(PlayDuration),
  1389. chapterId: this.chapterId || 0,
  1390. moduleId: this.moduleId || 0,
  1391. videoCurrentTime: parseInt(currentTime),
  1392. };
  1393. if (this.ossAvatarUrl) {
  1394. data.similarity = this.compareFaceData; // 相似度
  1395. }
  1396. if (status > 0) {
  1397. data.status = status;
  1398. }
  1399. console.log(data, "记录参数");
  1400. return new Promise((resolve, reject) => {
  1401. this.$api
  1402. .studyRecord(data)
  1403. .then((res) => {
  1404. console.log(res, "记录返回");
  1405. let { code, msg } = res.data;
  1406. if (code == 200) {
  1407. if (status > 0) {
  1408. // 刷新数据
  1409. uni.$emit("playEnd");
  1410. this.studyRecordMenuAllList();
  1411. }
  1412. self.ossAvatarUrl = "";
  1413. } else if (code == 600) {
  1414. uni.showModal({
  1415. showCancel: false,
  1416. title: "提示",
  1417. content: msg,
  1418. success: (resultst) => {
  1419. uni.navigateBack();
  1420. },
  1421. });
  1422. } else if (code == 558) {
  1423. this.CountTo1 = msg.split(",")[1];
  1424. this.noticeShow1 = true;
  1425. var timer = setInterval(() => {
  1426. this.CountTo1--;
  1427. if (this.CountTo1 < 0) {
  1428. this.noticeShow1 = false;
  1429. clearInterval(timer);
  1430. this.postStudyRecord(1);
  1431. }
  1432. }, 1000);
  1433. reject("中断执行");
  1434. } else {
  1435. uni.showToast({
  1436. icon: "none",
  1437. title: res.data.msg,
  1438. duration: 2000,
  1439. });
  1440. if (!this.erJianErZao && code == 559) {
  1441. this.isReach = true;
  1442. this.$refs["camera"].reTake();
  1443. }
  1444. if (code == 559 || code == 588) {
  1445. reject(res.data.msg);
  1446. }
  1447. }
  1448. resolve();
  1449. })
  1450. .catch((err) => {
  1451. this.studyRecordMenuAllList();
  1452. });
  1453. });
  1454. },
  1455. timeEvent(time) {
  1456. this.clearPauseTimer();
  1457. if (this.playSecIsLearn && (this.erJianErZao || this.photoNum > 0)) {
  1458. this.configPhoto(time);
  1459. this.isReach = false;
  1460. const index = this.photoList.findIndex((e) => e < time && e > time - 8);
  1461. if (index != -1 && !this.photoHistoryList[index]) {
  1462. this.photoIndex = index;
  1463. // 拍照
  1464. this.openCamera();
  1465. }
  1466. }
  1467. },
  1468. // 新增用户视频学习日志
  1469. studyLog() {
  1470. this.$http({
  1471. url: "/user/study/log",
  1472. method: "post",
  1473. data: {
  1474. ...this.params(),
  1475. moduleId: this.moduleId || 0,
  1476. chapterId: this.chapterId || 0,
  1477. sectionId: this.sectionId || 0,
  1478. fromPlat: 1, //来源平台 1小程序 2PC网站
  1479. goodsType: 1, // 商品类型 1视频2题库 3补考 4前培 5虚拟赠送题库 6直播
  1480. },
  1481. }).then((res) => {});
  1482. },
  1483. timeupdate(time) {
  1484. console.log("🚀 ~ file: detail.vue:1498 ~ timeupdate ~ time:");
  1485. if (this.noticeShow) {
  1486. this.refPlv.playPause();
  1487. return;
  1488. }
  1489. this.playTime = time;
  1490. this.throttleFn();
  1491. this.timeEvent(time);
  1492. },
  1493. playing() {
  1494. console.log("palying");
  1495. this.isPlaying = true;
  1496. this.clearPauseTimer();
  1497. },
  1498. openCamera() {
  1499. console.log("拍照");
  1500. this.showCamera = true;
  1501. this.refPlv.playPause();
  1502. this.refPlv.exitFullScreen();
  1503. },
  1504. closeCamera() {
  1505. this.showCamera = false;
  1506. },
  1507. pause() {
  1508. console.log("暂停");
  1509. this.isPlaying = false;
  1510. this.erJianErZaoPauseTip();
  1511. },
  1512. async ended() {
  1513. console.log("结束");
  1514. this.isPlaying = false;
  1515. uni.showToast({
  1516. icon: "none",
  1517. title: "播放完毕",
  1518. });
  1519. this.clearPauseTimer();
  1520. await this.postStudyRecord(1);
  1521. this.nextSection();
  1522. },
  1523. playerError() {
  1524. console.log("播放错误");
  1525. },
  1526. //播放下一节
  1527. nextSection() {
  1528. console.log("播放下一节");
  1529. if (!this.menuAllList.length) {
  1530. return;
  1531. }
  1532. this.curPlayIndex = this.menuAllList.findIndex((item) => {
  1533. let i_sectionId = item.sectionId || 0;
  1534. let i_chapterId = item.chapterId || 0;
  1535. let i_moduleId = item.moduleId || 0;
  1536. return (
  1537. i_sectionId == this.sectionId &&
  1538. i_chapterId == this.chapterId &&
  1539. i_moduleId == this.moduleId
  1540. );
  1541. });
  1542. let data = this.menuAllList[this.curPlayIndex + 1];
  1543. if (!data) {
  1544. //第二个弹窗
  1545. uni.showModal({
  1546. title: "温馨提示",
  1547. content:
  1548. "恭喜您课程学习全部完成,教务会在1-3个工作日内完成学习初审,请耐心等待。",
  1549. showCancel: !this.erJianErZao,
  1550. success: (res) => {
  1551. if (res.confirm) {
  1552. uni.switchTab({
  1553. url: "/pages/learn/index",
  1554. });
  1555. }
  1556. },
  1557. });
  1558. } else {
  1559. if (data.doType == 2) {
  1560. if (data.studyStatus == 1) {
  1561. uni.showToast({
  1562. title: "试卷已合格!",
  1563. duration: 2000,
  1564. icon: "none",
  1565. });
  1566. return;
  1567. }
  1568. uni.showModal({
  1569. title: "温馨提示",
  1570. content: "当前节视频已学完,是否进入考试?",
  1571. success: (res) => {
  1572. if (res.confirm) {
  1573. this.toQuestionBank(data);
  1574. }
  1575. },
  1576. });
  1577. return;
  1578. }
  1579. uni.showModal({
  1580. title: "温馨提示",
  1581. content: "当前节视频已学完,继续学习下一节?",
  1582. success: async (res) => {
  1583. if (res.confirm) {
  1584. this.moduleId = data.moduleId;
  1585. this.chapterId = data.chapterId;
  1586. this.sectionId = data.sectionId;
  1587. if (data.sectionType == 1) {
  1588. //录播
  1589. this.photoConfig = false;
  1590. this.sectionItem = data;
  1591. this.playVideo(data);
  1592. } else if (data.sectionType == 2) {
  1593. //直播
  1594. this.studyRecordGetLastLive();
  1595. } else if (data.sectionType == 3) {
  1596. //回放
  1597. this.sectionItem = data;
  1598. this.playVideo(data);
  1599. }
  1600. this.updateChapterOpen(true);
  1601. }
  1602. },
  1603. });
  1604. }
  1605. },
  1606. playError(e) {
  1607. console.log(e);
  1608. },
  1609. async userConfirmInfoDetail() {
  1610. let info = await this.$api.userConfirmInfoDetail({
  1611. orderGoodsId: this.orderGoodsId,
  1612. });
  1613. if (!info.data.data || info.data.data.pushInfo !== 1) {
  1614. uni.showModal({
  1615. showCancel: false,
  1616. title: "提示",
  1617. content: "开通信息推送不成功,无法进入学习!",
  1618. success: (resultst) => {
  1619. uni.navigateBack();
  1620. },
  1621. });
  1622. return Promise.reject();
  1623. }
  1624. },
  1625. checkFinishRequiredCourse() {
  1626. return this.$api
  1627. .checkFinishRequiredCourse({
  1628. businessId: this.goodsData.businessId,
  1629. goodsId: this.goodsId,
  1630. })
  1631. .then((res) => {
  1632. if (res.data.data > 0) {
  1633. uni.showModal({
  1634. showCancel: false,
  1635. confirmText: "确定",
  1636. content: "该业务层次下有未学完的商品,无法学习新商品!",
  1637. success: function (resultst) {
  1638. uni.navigateBack();
  1639. },
  1640. });
  1641. return Promise.reject();
  1642. }
  1643. return Promise.resolve();
  1644. });
  1645. },
  1646. async getGoodsDetail() {
  1647. let { data } = await this.$api.goodsDetail(this.goodsId);
  1648. this.goodsData = data.data;
  1649. this.erJianErZao = this.goodsData.erJianErZao;
  1650. this.jjShiGongYuan = this.goodsData.jjShiGongYuan;
  1651. this.erJianErZao && (await this.userConfirmInfoDetail());
  1652. this.option.periodWaitTime && (await this.checkFinishRequiredCourse());
  1653. this.goodsData.buyNote && this.baseHandoutTipList();
  1654. !this.gradeId && (this.gradeId = this.goodsData.gradeId);
  1655. if (this.goodsData.goodsPlayConfig) {
  1656. this.goodsPlayConfig = JSON.parse(this.goodsData.goodsPlayConfig);
  1657. if (this.goodsPlayConfig.autoPlay > 0) {
  1658. this.autoplay = true;
  1659. }
  1660. if (this.goodsPlayConfig.drag > 0 && !this.erJianErZao) {
  1661. // #ifdef MP-WEIXIN
  1662. this.isAllowSeek = "yes";
  1663. // #endif
  1664. // #ifdef H5
  1665. this.isAllowSeek = "off";
  1666. // #endif
  1667. }
  1668. if (this.goodsPlayConfig.speed > 0) {
  1669. this.playbackRate = [0.5, 0.8, 1.0, 1.25, 1.5, 2.0];
  1670. }
  1671. }
  1672. if (this.goodsData.goodsPhotographConfig) {
  1673. this.goodsPhotographConfig = JSON.parse(
  1674. this.goodsData.goodsPhotographConfig
  1675. );
  1676. if (this.goodsPhotographConfig.photoNum > 0) {
  1677. this.photoNum = this.goodsPhotographConfig.photoNum;
  1678. }
  1679. }
  1680. },
  1681. change(index) {
  1682. this.current = index;
  1683. },
  1684. paramsFn(
  1685. keys = ["orderGoodsId", "goodsId", "courseId", "gradeId", "sectionItem"]
  1686. ) {
  1687. return this.params(keys);
  1688. },
  1689. },
  1690. provide() {
  1691. return {
  1692. paramsFn: this.paramsFn,
  1693. };
  1694. },
  1695. };
  1696. </script>
  1697. <style lang="scss" scope>
  1698. @import "./css/detail.scss";
  1699. .top {
  1700. &__header {
  1701. position: relative;
  1702. width: 100%;
  1703. height: 150rpx;
  1704. padding: 24rpx 150rpx 24rpx 24rpx;
  1705. .img {
  1706. position: absolute;
  1707. left: 0;
  1708. top: 0;
  1709. width: 100%;
  1710. }
  1711. .note {
  1712. position: relative;
  1713. z-index: 10;
  1714. font-size: 24rpx;
  1715. font-family: PingFang SC;
  1716. font-weight: bold;
  1717. color: #efdbff;
  1718. }
  1719. .title {
  1720. position: relative;
  1721. z-index: 10;
  1722. font-size: 26rpx;
  1723. font-family: PingFang SC;
  1724. font-weight: bold;
  1725. color: #ffffff;
  1726. }
  1727. }
  1728. }
  1729. #top {
  1730. position: relative;
  1731. z-index: 99;
  1732. }
  1733. .polyv_detail {
  1734. display: flex;
  1735. flex-direction: column;
  1736. height: 100vh;
  1737. position: relative;
  1738. top: 0;
  1739. left: 0;
  1740. .pops {
  1741. position: absolute;
  1742. top: 0;
  1743. left: 0;
  1744. background: #ccc;
  1745. opacity: 0.5;
  1746. width: 100%;
  1747. height: 300rpx;
  1748. z-index: 9999;
  1749. }
  1750. .box {
  1751. flex: 1;
  1752. overflow: hidden;
  1753. margin: 16rpx 16rpx 100rpx 16rpx;
  1754. .box_in {
  1755. height: 100%;
  1756. }
  1757. }
  1758. .first_ml {
  1759. margin: 16rpx 16rpx 16rpx 16rpx;
  1760. }
  1761. }
  1762. .title {
  1763. font-size: 24rpx;
  1764. color: #999999;
  1765. }
  1766. page {
  1767. background: #eaeef1;
  1768. }
  1769. .ts {
  1770. font-size: 24rpx;
  1771. color: #999;
  1772. margin: 14rpx 0rpx;
  1773. padding-right: 29rpx;
  1774. padding-left: 34rpx;
  1775. }
  1776. .content {
  1777. padding: 24rpx;
  1778. text-align: left;
  1779. }
  1780. .sc_t {
  1781. font-size: 22rpx;
  1782. color: #000000;
  1783. }
  1784. .sc {
  1785. width: 29rpx;
  1786. height: 29rpx;
  1787. }
  1788. .buy {
  1789. width: 138rpx;
  1790. height: 48rpx;
  1791. line-height: 48rpx;
  1792. background: #32467b;
  1793. border-radius: 10rpx;
  1794. color: #ffffff;
  1795. font-size: 28rpx;
  1796. text-align: center;
  1797. vertical-align: middle;
  1798. position: absolute;
  1799. right: 30rpx;
  1800. }
  1801. .video_body {
  1802. padding-bottom: 96rpx;
  1803. }
  1804. .tj_box {
  1805. width: 50%;
  1806. display: inline-block;
  1807. text-align: center;
  1808. margin: 10rpx 0;
  1809. }
  1810. .t2 {
  1811. font-size: 24rpx;
  1812. font-family: PingFang SC;
  1813. color: #666666;
  1814. line-height: 36rpx;
  1815. margin: 15rpx;
  1816. }
  1817. .r_t2 {
  1818. width: 201rpx;
  1819. height: 49rpx;
  1820. background: rgba(22, 119, 255, 0.05);
  1821. border: 1rpx solid #32467b;
  1822. border-radius: 16rpx;
  1823. color: #666666;
  1824. font-size: 23rpx;
  1825. text-align: center;
  1826. display: flex;
  1827. align-items: center;
  1828. padding: 5rpx;
  1829. }
  1830. .scroll_box {
  1831. width: 100%;
  1832. height: 60rpx;
  1833. background: #ffffff;
  1834. box-shadow: 0rpx 0rpx 16rpx 4rpx rgba(145, 156, 178, 0.1);
  1835. white-space: nowrap;
  1836. overflow: hidden;
  1837. margin: 15rpx 0;
  1838. }
  1839. .r_sliper {
  1840. padding: 0 20rpx;
  1841. }
  1842. .top_line {
  1843. width: 6rpx;
  1844. height: 22rpx;
  1845. background: #32467b;
  1846. margin-right: 10rpx;
  1847. }
  1848. .video_t2 {
  1849. font-size: 24rpx;
  1850. font-family: PingFang SC;
  1851. font-weight: 500;
  1852. color: #666666;
  1853. }
  1854. .video_play {
  1855. position: absolute;
  1856. width: 95rpx;
  1857. height: 95rpx;
  1858. top: 0;
  1859. left: 0;
  1860. right: 0;
  1861. bottom: 0;
  1862. margin: auto;
  1863. }
  1864. .video_box {
  1865. position: relative;
  1866. }
  1867. .slot-content {
  1868. padding: 0 20rpx;
  1869. }
  1870. .notice_modal {
  1871. .content {
  1872. width: 100%;
  1873. height: 100%;
  1874. padding: 56rpx 56rpx 56rpx 64rpx;
  1875. .title {
  1876. color: #222;
  1877. line-height: 40rpx;
  1878. font-size: 36rpx;
  1879. text-align: center;
  1880. font-weight: bold;
  1881. margin-bottom: 24rpx;
  1882. }
  1883. .text {
  1884. height: 340rpx;
  1885. line-height: 40rpx;
  1886. text-indent: 2em;
  1887. font-size: 32rpx;
  1888. color: #222;
  1889. }
  1890. .had_read {
  1891. width: 100%;
  1892. height: 88rpx;
  1893. line-height: 88rpx;
  1894. text-align: center;
  1895. background: #3577e8;
  1896. border-radius: 240rpx;
  1897. font-size: 32rpx;
  1898. font-weight: 500;
  1899. color: #fff;
  1900. margin-top: 20rpx;
  1901. &.gray {
  1902. background: #bbbec5;
  1903. }
  1904. }
  1905. }
  1906. }
  1907. </style>