detail.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. <template>
  2. <view>
  3. <nav-bar title="课程详情"></nav-bar>
  4. <view class="videoBox">
  5. <view>
  6. <view class="video_box" v-if="!startStatus">
  7. <image
  8. :src="$method.splitImgHost(detail.coverUrl)"
  9. style="width: 100%; height: 421rpx"
  10. ></image>
  11. <image
  12. v-if="false"
  13. class="video_play"
  14. src="/static/play.png"
  15. @click="startVideo"
  16. ></image>
  17. </view>
  18. <view v-else class="video_box" style="width: 100%; height: 421rpx">
  19. <polyv-player
  20. id="playerVideo"
  21. playerId="playerVideo"
  22. height="421rpx"
  23. :vid="vid"
  24. :showSettingBtn="true"
  25. :enablePlayGesture="true"
  26. :playbackRate="playbackRate"
  27. :isAllowSeek="isAllowSeek"
  28. :autoplay="autoplay"
  29. :startTime="startTime"
  30. @statechange="onStateChange"
  31. ></polyv-player>
  32. </view>
  33. <view style="padding: 20rpx">
  34. <view style="display: flex">
  35. <view class="yearTag" v-if="detail.year">{{ detail.year }}</view>
  36. <view class="titleTag">{{ detail.goodsName }}</view>
  37. </view>
  38. <view
  39. style="
  40. display: flex;
  41. justify-content: space-between;
  42. margin-top: 13rpx;
  43. "
  44. >
  45. <view class="noteTag"
  46. ><image src="/static/icon/wk_icon1.png" class="wk_icon"></image>
  47. <text class="blackFont"
  48. >{{ courseList.length }} 课程
  49. {{ detail.classHours || "-" }}</text
  50. >
  51. 学时</view
  52. >
  53. </view>
  54. </view>
  55. </view>
  56. <u-line color="#D6D6DB" />
  57. <view style="height: 80rpx">
  58. <view
  59. ><u-tabs
  60. :list="list"
  61. :item-width="itemWidth()"
  62. font-size="30"
  63. bar-width="24"
  64. :current="current"
  65. @change="change"
  66. active-color="#007AFF"
  67. ></u-tabs
  68. ></view>
  69. </view>
  70. </view>
  71. <view
  72. style="padding: 20rpx; padding-bottom: 100rpx; position: relative"
  73. v-show="current == 0"
  74. >
  75. <view class="content">
  76. <view
  77. v-html="detail.mobileDetailHtml"
  78. style="width: 100%; overflow: hidden"
  79. ></view>
  80. </view>
  81. </view>
  82. <view
  83. style="padding: 20rpx; padding-bottom: 100rpx; position: relative"
  84. v-show="current == 1"
  85. >
  86. <view>
  87. <view v-for="(item, index) in courseList" :key="index">
  88. <view class="courseItemBox">
  89. <view class="courseItem" @click="openCourse(item)">
  90. <view class="courseName">{{ item.courseName }}</view>
  91. <view>
  92. <image
  93. src="/static/icon/up.png"
  94. class="icon_up"
  95. v-if="item.down"
  96. ></image>
  97. <image
  98. src="/static/icon/down.png"
  99. class="icon_up"
  100. v-if="!item.down"
  101. ></image>
  102. </view>
  103. </view>
  104. <view v-show="!item.down">
  105. <view v-for="(itemM, indexM) in item.menuList" :key="indexM">
  106. <courseModule
  107. :courseId="itemM.courseId"
  108. :needOpen="
  109. isFirstEnter &&
  110. menuIndex[0] === index &&
  111. menuIndex[1] === indexM
  112. ? true
  113. : false
  114. "
  115. v-if="itemM.type == 1"
  116. :menuItem="itemM"
  117. ></courseModule>
  118. <courseChapter
  119. :courseId="itemM.courseId"
  120. :needOpen="
  121. isFirstEnter &&
  122. menuIndex[0] === index &&
  123. menuIndex[1] === indexM
  124. ? true
  125. : false
  126. "
  127. v-if="itemM.type == 2"
  128. :isBuy="false"
  129. :menuItem="itemM"
  130. ></courseChapter>
  131. <courseSection
  132. :courseId="itemM.courseId"
  133. v-if="itemM.type == 3"
  134. :isBuy="false"
  135. :menuItem="itemM"
  136. ></courseSection>
  137. <u-line></u-line>
  138. </view>
  139. </view>
  140. </view>
  141. </view>
  142. </view>
  143. </view>
  144. <view
  145. style="padding: 20rpx; padding-bottom: 100rpx; position: relative"
  146. v-show="current == 2"
  147. >
  148. <view>
  149. <view v-for="(item, index) in freeMenuList" :key="index">
  150. <view class="courseItemBox">
  151. <view class="courseItem">
  152. <view class="courseName">{{ item.freeExamName }}</view>
  153. </view>
  154. </view>
  155. </view>
  156. </view>
  157. </view>
  158. <view class="bottomBox" v-if="!hideBuyState">
  159. <view class="priceTag">¥ {{ toFixed(detail.standPrice) }}</view>
  160. <view style="display: flex; color: #ffffff; align-items: center">
  161. <view class="btn1" @click="addCart">加购物车</view>
  162. <view class="btn2" @click="buy">立即购买 </view>
  163. </view>
  164. </view>
  165. </view>
  166. </template>
  167. <script>
  168. import courseModule from "@/components/course/courseModule.vue";
  169. import courseChapter from "@/components/course/courseChapter.vue";
  170. import courseSection from "@/components/course/courseSection.vue";
  171. import { mapGetters, mapMutations } from "vuex";
  172. export default {
  173. components: {
  174. courseModule,
  175. courseChapter,
  176. courseSection,
  177. },
  178. data() {
  179. return {
  180. id: 0,
  181. list: [],
  182. menuIndex: [],
  183. current: 0,
  184. detail: {},
  185. courseList: [],
  186. menuList: [],
  187. freeMenuList: [],
  188. startStatus: false,
  189. playbackRate: [1.0],
  190. isAllowSeek: "no",
  191. vid: "",
  192. autoplay: true,
  193. listenConfigList: [],
  194. listenSecond: 0,
  195. isFirstEnter: true, //是否首次进入
  196. timer: null,
  197. businessData: {},
  198. startTime: 0,
  199. };
  200. },
  201. computed: {
  202. ...mapGetters([
  203. "userInfo",
  204. "goodsAuditionConfigIdList",
  205. "playSectionId",
  206. "hideBuyState",
  207. ]),
  208. },
  209. onLoad(option) {
  210. this.id = option.id;
  211. this.getDetail();
  212. this.goodsCourseList();
  213. this.appCommonGoodsCourseModuleFreeExamList();
  214. },
  215. onUnload(option) {
  216. this.$store.commit("setPlaySectionId", { playSectionId: 0 });
  217. //移除所有的事件监听器
  218. uni.$off();
  219. },
  220. mounted() {
  221. let self = this;
  222. uni.$on("getSection", (item) => {
  223. console.log(item);
  224. //播放试听
  225. self.listenSecond = 0;
  226. for (var itemChild of self.listenConfigList) {
  227. if (
  228. self.playSectionId == (itemChild.sectionId || itemChild.menuId) &&
  229. item.courseId == itemChild.courseId
  230. ) {
  231. if (itemChild.auditionMinute > 0) {
  232. // self.listenSecond = itemChild.auditionMinute *60 //试听秒数
  233. self.listenSecond = itemChild.auditionMinute; //试听秒数 auditionMinute调整为秒单位
  234. }
  235. }
  236. }
  237. if (self.listenSecond > 0) {
  238. if (self.timer) {
  239. clearInterval(self.timer);
  240. }
  241. if (self.vid) {
  242. //切换视频
  243. var polyvPlayerContext = self.selectComponent("#playerVideo");
  244. polyvPlayerContext.changeVid(item.recordingUrl);
  245. } else {
  246. self.vid = item.recordingUrl;
  247. }
  248. self.startStatus = true;
  249. self.startTime = 0;
  250. } else {
  251. self.$u.toast("试听配置错误");
  252. }
  253. });
  254. this.updateChapterOpen(true);
  255. },
  256. methods: {
  257. ...mapMutations(["updateChapterOpen"]),
  258. itemWidth() {
  259. return 100 / this.list.length + "%";
  260. },
  261. appCommonGoodsCourseModuleFreeExamList() {
  262. this.$api.appCommonGoodsCourseModuleFreeExamList(this.id).then((res) => {
  263. if (res.data.data.length) {
  264. this.freeMenuList = res.data.data;
  265. this.list = [
  266. {
  267. name: "详情",
  268. },
  269. {
  270. name: "大纲",
  271. },
  272. {
  273. name: "赠送",
  274. },
  275. ];
  276. } else {
  277. this.list = [
  278. {
  279. name: "详情",
  280. },
  281. {
  282. name: "大纲",
  283. },
  284. ];
  285. }
  286. console.log(this.list);
  287. });
  288. },
  289. courseBusiness() {
  290. this.$api.courseBusiness(this.detail.businessId).then((res) => {
  291. this.businessData = res.data.data;
  292. });
  293. },
  294. toFixed(number) {
  295. if (number > 0) {
  296. return number.toFixed(2);
  297. } else {
  298. return "0.00";
  299. }
  300. },
  301. onStateChange(newstate, oldstate) {
  302. if (newstate.detail.newstate == "playing") {
  303. //开始播放
  304. if (this.timer) {
  305. clearInterval(this.timer);
  306. }
  307. this.timer = setInterval(this.timeEvent, 1500); //定时器
  308. }
  309. },
  310. closePlay() {
  311. this.$store.commit("setPlaySectionId", { playSectionId: 0 });
  312. this.vid = "";
  313. this.startStatus = false;
  314. },
  315. timeEvent() {
  316. let self = this;
  317. var polyvPlayerContext = this.selectComponent("#playerVideo");
  318. if (polyvPlayerContext != null) {
  319. let PlayCurrentTime = polyvPlayerContext.getCurrentTime();
  320. if (PlayCurrentTime >= this.listenSecond) {
  321. polyvPlayerContext.stop();
  322. polyvPlayerContext.exitFullScreen();
  323. clearInterval(this.timer);
  324. this.timer = null;
  325. uni.showModal({
  326. title: "提示",
  327. content: "试听结束,购买课程可学习全部",
  328. showCancel: false,
  329. success: function (resst) {
  330. self.closePlay();
  331. },
  332. });
  333. }
  334. }
  335. },
  336. openCourse(item) {
  337. item.down = !item.down;
  338. if (!item.down && item.menuList.length == 0) {
  339. this.getMenuList(item);
  340. }
  341. },
  342. addShopCart() {
  343. let self = this;
  344. this.$api.addCart({ goodsId: this.id }).then((res) => {
  345. if (res.data.code == 200) {
  346. uni.setStorageSync("updateCart", 1); //提醒刷新购物车
  347. uni.showToast({
  348. title: "添加成功",
  349. });
  350. } else {
  351. this.$u.toast(res.data.msg);
  352. }
  353. });
  354. },
  355. goodsCourseList() {
  356. let self = this;
  357. this.$api.goodsCourseList(this.id).then((res) => {
  358. if (res.data.code == 200) {
  359. for (let i = 0; i < res.data.rows.length; i++) {
  360. let item = res.data.rows[i];
  361. item.down = true;
  362. item.menuList = [];
  363. }
  364. self.courseList = res.data.rows;
  365. this.getFirstCourse();
  366. }
  367. });
  368. },
  369. /**
  370. * 获取第一个有模块或者章的课程
  371. */
  372. async getFirstCourse() {
  373. for (let i = 0; i < this.courseList.length; i++) {
  374. let menuIndexOrFalse = await this.getCourseMenus(this.courseList[i]);
  375. if (menuIndexOrFalse !== false) {
  376. this.menuIndex = [i, menuIndexOrFalse];
  377. this.openCourse(this.courseList[i]);
  378. break;
  379. }
  380. }
  381. },
  382. getCourseMenus(item) {
  383. return new Promise((resolve) => {
  384. this.$api.menuList({ courseId: item.courseId }).then((res) => {
  385. if (res.data.code == 200) {
  386. for (let i = 0; i < res.data.rows.length; i++) {
  387. if (res.data.rows[i].type == 1 || res.data.rows[i].type == 2) {
  388. resolve(i);
  389. break;
  390. }
  391. }
  392. }
  393. });
  394. });
  395. },
  396. getMenuList(item) {
  397. let self = this;
  398. this.$api.menuList({ courseId: item.courseId }).then((res) => {
  399. if (res.data.code == 200) {
  400. for (let i = 0; i < res.data.rows.length; i++) {
  401. let item = res.data.rows[i];
  402. item.down = true;
  403. item.id = item.menuId;
  404. item.name = item.menuName;
  405. if (item.type == 3) {
  406. //判断是否试听
  407. item.tryListen = false;
  408. if (self.goodsAuditionConfigIdList.indexOf(item.id) !== -1) {
  409. item.tryListen = true;
  410. }
  411. }
  412. }
  413. item.menuList = res.data.rows;
  414. }
  415. });
  416. },
  417. getDetail() {
  418. let self = this;
  419. let sectionIdList = [];
  420. this.$api.commonGoodsDetail(this.id).then((res) => {
  421. if (res.data.code == 200) {
  422. if (res.data.data.mobileDetailHtml) {
  423. res.data.data.mobileDetailHtml =
  424. res.data.data.mobileDetailHtml.replace(
  425. /<img/gi,
  426. '<img style="max-width:100%;"'
  427. );
  428. }
  429. self.detail = res.data.data;
  430. this.courseBusiness();
  431. if (self.detail.goodsAuditionConfig) {
  432. self.listenConfigList = JSON.parse(self.detail.goodsAuditionConfig);
  433. for (var itemChild of self.listenConfigList) {
  434. sectionIdList.push(itemChild.sectionId); //存储试听节ID
  435. }
  436. self.$store.commit("setGoodsAuditionConfigIdList", {
  437. goodsAuditionConfigIdList: sectionIdList,
  438. });
  439. }
  440. }
  441. });
  442. },
  443. buy() {
  444. if (this.$method.isGoLogin()) {
  445. return;
  446. }
  447. this.$navTo.togo("/pages2/order/confirm_list?id=" + this.id);
  448. },
  449. addCart() {
  450. if (this.$method.isGoLogin()) {
  451. return;
  452. }
  453. this.addShopCart();
  454. },
  455. open(item) {
  456. item.showChildren = !item.showChildren;
  457. },
  458. change(index) {
  459. this.current = index;
  460. },
  461. },
  462. };
  463. </script>
  464. <style >
  465. page {
  466. background-color: #eaeef1;
  467. }
  468. </style>
  469. <style scope>
  470. .video_t2 {
  471. font-size: 24rpx;
  472. font-family: PingFang SC;
  473. font-weight: 500;
  474. color: #666666;
  475. }
  476. .video_t1 {
  477. height: 80rpx;
  478. color: #333333;
  479. line-height: 80rpx;
  480. font-size: 30rpx;
  481. font-family: PingFang SC;
  482. font-weight: bold;
  483. color: #333333;
  484. overflow: hidden;
  485. text-overflow: ellipsis;
  486. white-space: nowrap;
  487. }
  488. .video_t1_t {
  489. display: flex;
  490. flex-direction: column;
  491. height: 80rpx;
  492. color: #333333;
  493. text-align: center;
  494. align-items: center;
  495. border-left: solid 1px #d6d6db;
  496. }
  497. .video_play {
  498. position: absolute;
  499. width: 95rpx;
  500. height: 95rpx;
  501. top: 0;
  502. left: 0;
  503. right: 0;
  504. bottom: 0;
  505. margin: auto;
  506. }
  507. .video_box {
  508. position: relative;
  509. }
  510. .courseName {
  511. white-space: nowrap;
  512. overflow: hidden;
  513. text-overflow: ellipsis;
  514. }
  515. .videoBox {
  516. background-color: #ffffff;
  517. width: 100%;
  518. /* height: 680rpx; */
  519. z-index: 999;
  520. }
  521. .icon_up {
  522. width: 32rpx;
  523. height: 32rpx;
  524. }
  525. .contentBox {
  526. }
  527. .courseItemBox {
  528. background: #ffffff;
  529. border-radius: 16rpx;
  530. padding: 0 10rpx;
  531. margin-bottom: 20rpx;
  532. }
  533. .courseItem {
  534. height: 80rpx;
  535. color: #333333;
  536. font-size: 32rpx;
  537. line-height: 80rpx;
  538. font-weight: bold;
  539. display: flex;
  540. justify-content: space-between;
  541. }
  542. .content {
  543. background-color: #ffffff;
  544. width: 100%;
  545. }
  546. .btn2 {
  547. width: 200rpx;
  548. height: 64rpx;
  549. background: linear-gradient(0deg, #ffb102, #fd644f);
  550. box-shadow: 0rpx 10rpx 16rpx 4rpx rgba(1, 99, 235, 0.04);
  551. border-radius: 32rpx;
  552. line-height: 64rpx;
  553. text-align: center;
  554. }
  555. .btn1 {
  556. width: 200rpx;
  557. height: 64rpx;
  558. background: linear-gradient(0deg, #015eea, #00c0fa);
  559. border-radius: 32rpx;
  560. line-height: 64rpx;
  561. text-align: center;
  562. margin-right: 20rpx;
  563. }
  564. .bottomBox {
  565. position: fixed;
  566. bottom: 0;
  567. width: 100%;
  568. left: 0;
  569. height: 98rpx;
  570. background-color: #ffffff;
  571. display: flex;
  572. justify-content: space-between;
  573. align-items: center;
  574. padding: 0 30rpx;
  575. }
  576. .blackFont {
  577. color: #333333;
  578. margin: 0 4rpx;
  579. }
  580. .wk_icon {
  581. width: 24rpx;
  582. height: 24rpx;
  583. margin-right: 12rpx;
  584. }
  585. .noteTag {
  586. ont-size: 24rpx;
  587. font-family: PingFang SC;
  588. font-weight: 500;
  589. color: #999999;
  590. align-items: center;
  591. }
  592. .priceTag {
  593. font-size: 30rpx;
  594. font-family: PingFang SC;
  595. font-weight: bold;
  596. color: #ff2d55;
  597. }
  598. .titleTag {
  599. font-size: 32rpx;
  600. font-weight: bold;
  601. color: #333333;
  602. margin-left: 8rpx;
  603. }
  604. .yearTag {
  605. width: 80rpx;
  606. height: 32rpx;
  607. background: #ebf5ff;
  608. border: 2rpx solid #007aff;
  609. border-radius: 16rpx;
  610. font-size: 24rpx;
  611. color: #007aff;
  612. text-align: center;
  613. line-height: 32rpx;
  614. }
  615. .itemBox {
  616. background: #ffffff;
  617. box-shadow: 0rpx 10rpx 9rpx 1rpx rgba(165, 196, 239, 0.1);
  618. border-radius: 24rpx;
  619. width: 100%;
  620. padding: 20rpx;
  621. margin-bottom: 20rpx;
  622. }
  623. </style>