source_detail.html 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. <!-- +---------------------------------------------------------------------- -->
  2. <!-- | CRMEB [ CRMEB赋能开发者,助力企业发展 ] -->
  3. <!-- +---------------------------------------------------------------------- -->
  4. <!-- | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved. -->
  5. <!-- +---------------------------------------------------------------------- -->
  6. <!-- | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权 -->
  7. <!-- +---------------------------------------------------------------------- -->
  8. <!-- | Author: CRMEB Team <admin@crmeb.com> -->
  9. <!-- +---------------------------------------------------------------------- -->
  10. {extend name="public/container"}
  11. {block name="title"}素材详情{/block}
  12. {block name="head_top"}
  13. <style>
  14. p {
  15. box-sizing: border-box;
  16. }
  17. .prism-player video {
  18. object-fit: cover;
  19. }
  20. .source-detail-page .handle-wrap svg {
  21. color: #191C6E;
  22. }
  23. .player-box {
  24. position: relative;
  25. }
  26. .player-box .try {
  27. position: absolute;
  28. right: .3rem;
  29. bottom: .3rem;
  30. z-index: 13;
  31. padding: .11rem .16rem .11rem .14rem;
  32. border-radius: .23rem;
  33. background-color: rgba(0, 0, 0, .6);
  34. font-size: .22rem;
  35. line-height: .24rem;
  36. color: #FFFFFF;
  37. }
  38. .player-box .try .iconfont {
  39. margin-right: .08rem;
  40. vertical-align: middle;
  41. font-size: .24rem;
  42. }
  43. </style>
  44. {/block}
  45. {block name="content"}
  46. <div v-cloak id="app">
  47. <div v-if="source" class="source-detail-page">
  48. <div class="player-box">
  49. <img v-if="source.type !== 3" :style="{ height: height }" :src="source.image" class="cover">
  50. <div v-if="source.type !== 1" :style="{ height: height }" :hidden="source.type === 2" class="prism-player" id="J_prismPlayer"></div>
  51. <div v-show="tryShow" class="try"><i class="iconfont iconbofang"></i>免费试看</div>
  52. </div>
  53. <div class="title-wrap">
  54. <div class="title">{{ source.title }}</div>
  55. <div class="wrap">
  56. <div class="learn">{{ source.play_count +1 }}次学习</div>
  57. <div v-if="isWeiXin" class="share" @click="share = true">
  58. <div class="iconfont iconfenxiang"></div>
  59. 分享
  60. </div>
  61. </div>
  62. </div>
  63. <div v-if="source.type === 2" class="audio-wrap">
  64. <div class="progress">
  65. <div class="time">{{ currentTime | format }}</div>
  66. <div ref="track" class="track" @click="onSeek">
  67. <div :style="{ width: width + '%' }" class="range" @touchmove.self="onSeeking" @touchend="onSeekEnd">
  68. <div class="thumb"></div>
  69. </div>
  70. </div>
  71. <div class="time">{{ duration | format }}</div>
  72. </div>
  73. <div class="handle-wrap">
  74. <button class="iconfont iconleft" disabled></button>
  75. <button @click="onPlay">
  76. <svg class="icon" aria-hidden="true">
  77. <use :xlink:href="paused ? '#iconbofang1' : '#iconzanting'"></use>
  78. </svg>
  79. </button>
  80. <button class="iconfont iconright" disabled></button>
  81. </div>
  82. <audio ref="audio" :src="source.link" @durationchange="onDurationChange" @timeupdate="onTimeUpdate" @ended="onEnded">您的浏览器不支持 H5 audio</audio>
  83. </div>
  84. <div class="main">
  85. <div class="title">详情</div>
  86. <div class="wrap" v-html="source.type == 1 && source.is_try ? source.try_content : source.detail"></div>
  87. </div>
  88. <div class="footer" @touchmove.prevent>
  89. <div>
  90. <a href="{:url('wap/index/index')}">
  91. <img src="{__WAP_PATH}zsff/images/special01.png">
  92. 首页
  93. </a>
  94. </div>
  95. <div @click="customerService">
  96. <img src="{__WAP_PATH}zsff/images/special02.png">
  97. 客服
  98. </div>
  99. <button v-if="course.length>0" @click="relatedCourses">相关课程</button>
  100. <button v-else @click="relatedCourses">更多课程</button>
  101. </div>
  102. <img v-show="share" class="share-mask" src="{__WAP_PATH}zsff/images/share-info.png" @touchmove.prevent @click="share = false">
  103. <div :class="{ mask: dialog }" @touchmove.prevent @click="dialog = false"></div>
  104. <div class="dialog" :class="{ active: dialog }" @touchmove.prevent>
  105. <ul v-if="course.length">
  106. <li v-for="item in course" :key="item.id">
  107. <a :href="'{:url('special/details')}?id=' + item.id">
  108. <div class="figure">
  109. <img :src="item.image">
  110. <span>{{ item.type_name }}</span>
  111. </div>
  112. <div class="figcaption">
  113. <div class="title">{{ item.title }}</div>
  114. <div class="mark">
  115. <span v-for="itm in item.label">{{ itm }}</span>
  116. </div>
  117. <div class="info">
  118. <div class="money">¥<span>{{ item.money }}</span></div>
  119. <div class="lesson">共{{ item.count }}节</div>
  120. <div class="learn">{{ item.record }}次学习</div>
  121. </div>
  122. </div>
  123. </a>
  124. </li>
  125. </ul>
  126. <img v-else class="empty" src="/wap/first/zsff/images/no_data_available.png">
  127. </div>
  128. </div>
  129. <quick-menu></quick-menu>
  130. </div>
  131. <script>
  132. var id={$id};
  133. require(['vue', 'helper', 'store', 'layer', 'quick', 'aliplayer'], function(Vue, $h, api) {
  134. var isWechat = {$isWechat ? 'true' : 'false'};
  135. var uid={$userInfo['uid'] ? $userInfo['uid']:0};
  136. var vm = new Vue({
  137. el: '#app',
  138. filters: {
  139. format: function (time) {
  140. if (!time) {
  141. return '00:00';
  142. }
  143. var minutes = Math.floor(time / 60),
  144. seconds = Math.floor(time % 60);
  145. if (minutes < 10) {
  146. minutes = '0' + minutes;
  147. }
  148. if (seconds < 10) {
  149. seconds = '0' + seconds;
  150. }
  151. return minutes + ':' + seconds;
  152. }
  153. },
  154. data: {
  155. id: id,
  156. source: null,
  157. course: [],
  158. dialog: false,
  159. appear: true,
  160. share: false,
  161. width: 0,
  162. height: 'auto',
  163. track: null,
  164. audio: null,
  165. currentTime: 0,
  166. duration: 0,
  167. paused: true,
  168. aliplayer: null,
  169. isWeiXin: false,
  170. tryShow: true
  171. },
  172. created: function () {
  173. var ua = navigator.userAgent.toLowerCase();
  174. if (ua.match(/MicroMessenger/i) == 'micromessenger') {
  175. this.isWeiXin = true;
  176. }
  177. this.getSourceDetail();
  178. this.getRelateCourse();
  179. },
  180. mounted: function() {
  181. var that = this;
  182. that.$nextTick(function () {
  183. var clientWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  184. that.height = clientWidth * 14 / 25 + 'px';
  185. });
  186. },
  187. destroyed: function () {
  188. this.aliplayer.dispose();
  189. },
  190. methods: {
  191. relatedCourses:function()
  192. {
  193. var that=this;
  194. if(that.course.length>0){
  195. that.dialog = true;
  196. }else{
  197. window.location.href = $h.U({ c: 'special', a: 'special_cate' });
  198. }
  199. },
  200. // 联系客服
  201. customerService: function (){
  202. api.baseGet($h.U({
  203. c: 'public_api',
  204. a: 'public_data'
  205. }), function (res) {
  206. var data = res.data.data;
  207. if (data.customer_service === '3') {
  208. if (data.site_service_phone) {
  209. layer.confirm('是否拨打 <a href="tel:' + data.site_service_phone + '">' + data.site_service_phone + '</a> 进行咨询?', {
  210. title: false,
  211. closeBtn: false,
  212. btn: ['拨打', '取消']
  213. }, function (index) {
  214. window.location.assign('tel:' + data.site_service_phone);
  215. layer.close(index);
  216. });
  217. } else {
  218. layer.msg('抱歉,无法联系客服');
  219. }
  220. } else {
  221. window.location.assign($h.U({
  222. c: 'service',
  223. a: 'service_list'
  224. }));
  225. }
  226. });
  227. },
  228. // 获取素材
  229. getSourceDetail: function () {
  230. var that = this;
  231. $h.loadFFF();
  232. api.baseGet($h.U({
  233. c: 'special',
  234. a: 'getSourceDetail',
  235. q: {
  236. source_id: id
  237. }
  238. }), function (res) {
  239. $h.loadClear();
  240. that.source = res.data.data;
  241. // 音频
  242. if (that.source.type === 2) {
  243. that.$nextTick(function () {
  244. that.track = that.$refs.track;
  245. });
  246. if (that.source.videoId) {
  247. $h.loadFFF();
  248. api.baseGet($h.U({
  249. c: 'special',
  250. a: 'get_video_playback_credentials',
  251. q: {
  252. type: 2,
  253. videoId: that.source.videoId
  254. }
  255. }), function (res) {
  256. var xhr = new XMLHttpRequest();
  257. xhr.onreadystatechange = function () {
  258. $h.loadClear();
  259. if (xhr.readyState === 4 && xhr.status === 200) {
  260. var data = JSON.parse(xhr.responseText);
  261. that.$nextTick(function () {
  262. that.aliplayer = new Aliplayer({
  263. id: 'J_prismPlayer',
  264. vid: data.VideoMeta.VideoId,
  265. playauth: data.PlayAuth,
  266. format: 'mp3',
  267. mediaType: 'audio',
  268. encryptType: 1,
  269. autoplay: false
  270. }, function (player) {
  271. console.log('播放器创建好了。');
  272. if (that.source.is_try) {
  273. player.setPreviewTime(that.source.try_time * 1000);
  274. }
  275. });
  276. that.aliplayer.on('ready', function () {
  277. that.duration = data.VideoMeta.Duration;
  278. });
  279. that.aliplayer.on('ended', function () {
  280. that.paused = false;
  281. });
  282. that.aliplayer.on('timeupdate', function () {
  283. that.currentTime = that.aliplayer.getCurrentTime();
  284. that.width = Math.floor(that.aliplayer.getCurrentTime() / that.aliplayer.getDuration() * 100);
  285. });
  286. that.aliplayer.on('error', function (event) {
  287. console.error(event);
  288. });
  289. });
  290. }
  291. };
  292. xhr.open('GET', res.data.msg);
  293. xhr.send();
  294. }, function () {
  295. $h.loadClear();
  296. });
  297. } else {
  298. that.$nextTick(function () {
  299. that.audio = that.$refs.audio;
  300. });
  301. }
  302. }
  303. // 视频
  304. if (that.source.type === 3) {
  305. if (that.source.videoId) {
  306. $h.loadFFF();
  307. api.baseGet($h.U({
  308. c: 'special',
  309. a: 'get_video_playback_credentials',
  310. p: {
  311. type: 2,
  312. videoId: that.source.videoId
  313. }
  314. }), function (res) {
  315. var xhr = new XMLHttpRequest();
  316. xhr.onreadystatechange = function () {
  317. $h.loadClear();
  318. if (xhr.readyState === 4 && xhr.status === 200) {
  319. var data = JSON.parse(xhr.responseText);
  320. that.$nextTick(function () {
  321. that.aliplayer = new Aliplayer({
  322. id: 'J_prismPlayer',
  323. width: '100%',
  324. autoplay: false,
  325. vid: data.VideoMeta.VideoId,
  326. playauth: data.PlayAuth,
  327. cover: that.source.image,
  328. playsinline: true,
  329. x5_type: 'h5',
  330. skinLayout: [
  331. { name: "bigPlayButton", align: "cc" },
  332. {
  333. name: "H5Loading", align: "cc"
  334. },
  335. { name: "errorDisplay", align: "tlabs", x: 0, y: 0 },
  336. { name: "infoDisplay" },
  337. { name: "tooltip", align: "blabs", x: 0, y: 56 },
  338. { name: "thumbnail" },
  339. {
  340. name: "controlBar", align: "blabs", x: 0, y: 0,
  341. children: [
  342. { name: "progress", align: "blabs", x: 0, y: 44 },
  343. { name: "playButton", align: "tl", x: 15, y: 12 },
  344. { name: "timeDisplay", align: "tl", x: 10, y: 7 },
  345. { name: "fullScreenButton", align: "tr", x: 10, y: 12 },
  346. { name: "subtitle", align: "tr", x: 15, y: 12 },
  347. { name: "setting", align: "tr", x: 15, y: 12 },
  348. { name: "volume", align: "tr", x: 5, y: 10 }
  349. ]
  350. }
  351. ]
  352. }, function (player) {
  353. console.log('播放器创建好了。');
  354. player.on('hideBar', function () {
  355. that.tryShow = true;
  356. });
  357. player.on('showBar', function () {
  358. that.tryShow = false;
  359. });
  360. if (that.source.is_try) {
  361. player.setPreviewTime(that.source.try_time * 1000);
  362. }
  363. });
  364. });
  365. }
  366. };
  367. xhr.open('GET', res.data.msg);
  368. xhr.send();
  369. }, function () {
  370. $h.loadClear();
  371. });
  372. } else {
  373. that.$nextTick(function () {
  374. that.aliplayer = new Aliplayer({
  375. id: 'J_prismPlayer',
  376. width: '100%',
  377. autoplay: false,
  378. source: that.source.link,
  379. cover: that.source.image,
  380. playsinline: true,
  381. x5_type: 'h5',
  382. skinLayout: [
  383. { name: "bigPlayButton", align: "cc" },
  384. {
  385. name: "H5Loading", align: "cc"
  386. },
  387. { name: "errorDisplay", align: "tlabs", x: 0, y: 0 },
  388. { name: "infoDisplay" },
  389. { name: "tooltip", align: "blabs", x: 0, y: 56 },
  390. { name: "thumbnail" },
  391. {
  392. name: "controlBar", align: "blabs", x: 0, y: 0,
  393. children: [
  394. { name: "progress", align: "blabs", x: 0, y: 44 },
  395. { name: "playButton", align: "tl", x: 15, y: 12 },
  396. { name: "timeDisplay", align: "tl", x: 10, y: 7 },
  397. { name: "fullScreenButton", align: "tr", x: 10, y: 12 },
  398. { name: "subtitle", align: "tr", x: 15, y: 12 },
  399. { name: "setting", align: "tr", x: 15, y: 12 },
  400. { name: "volume", align: "tr", x: 5, y: 10 }
  401. ]
  402. }
  403. ]
  404. }, function (player) {
  405. console.log('播放器创建好了。');
  406. player.on('hideBar', function () {
  407. that.tryShow = true;
  408. });
  409. player.on('showBar', function () {
  410. that.tryShow = false;
  411. });
  412. if (that.source.is_try) {
  413. player.setPreviewTime(that.source.try_time * 1000);
  414. }
  415. });
  416. });
  417. }
  418. }
  419. if (isWechat) {
  420. mapleWx($jssdk(), function () {
  421. this.onMenuShareAll({
  422. title: vm.source.title,
  423. desc: vm.source.title,
  424. imgUrl: vm.source.image,
  425. link: location.href.indexOf('?') == -1 ? location.href + '?spread_uid=' + uid : location.href + '&spread_uid=' + uid
  426. });
  427. });
  428. }
  429. }, function (err) {
  430. $h.loadClear();
  431. });
  432. },
  433. // 获取关联课程
  434. getRelateCourse: function () {
  435. var that = this;
  436. $h.loadFFF();
  437. api.baseGet($h.U({
  438. c: 'Special',
  439. a: 'relatedCourses',
  440. q: {
  441. source_id: that.id
  442. }
  443. }), function (res) {
  444. var course = res.data.data;
  445. course.forEach(function (item) {
  446. switch (item.type) {
  447. case 1:
  448. item.type_name = '图文';
  449. break;
  450. case 2:
  451. item.type_name = '音频';
  452. break;
  453. default:
  454. item.type_name = '视频';
  455. break;
  456. }
  457. });
  458. that.course = course;
  459. $h.loadClear();
  460. }, function () {
  461. $h.loadClear();
  462. });
  463. },
  464. // 音频播放/暂停
  465. onPlay: function () {
  466. this.paused = !this.paused;
  467. if (this.source.videoId) {
  468. this.paused ? this.aliplayer.pause() : this.aliplayer.play();
  469. } else {
  470. this.paused ? this.audio.pause() : this.audio.play();
  471. }
  472. },
  473. // 音频时长
  474. onDurationChange: function (event) {
  475. this.duration = event.target.duration;
  476. },
  477. // 音频正在播放
  478. onTimeUpdate: function (event) {
  479. var target = event.target,
  480. currentTime = target.currentTime;
  481. this.currentTime = currentTime;
  482. this.width = Math.floor(currentTime / target.duration * 100);
  483. },
  484. // 音频播放结束
  485. onEnded: function () {
  486. this.paused = true;
  487. },
  488. // 点击音频进度条
  489. onSeek: function (event) {
  490. this.paused = false;
  491. if (this.source.videoId) {
  492. var width = event.offsetX / this.track.offsetWidth,
  493. currentTime = this.aliplayer.getDuration() * width;
  494. this.width = width * 100;
  495. this.aliplayer.seek(currentTime);
  496. this.aliplayer.play();
  497. } else {
  498. var width = event.offsetX / this.track.offsetWidth,
  499. currentTime = this.duration * width;
  500. this.width = width * 100;
  501. this.audio.currentTime = currentTime;
  502. this.paused ? this.audio.pause() : this.audio.play();
  503. }
  504. },
  505. // 拖动音频进度条
  506. onSeeking: function (event) {
  507. var width = (event.targetTouches[0].pageX - event.target.offsetLeft) / this.track.offsetWidth * 100;
  508. this.paused = true;
  509. if (width > 100) {
  510. width = 100;
  511. }
  512. this.width = width;
  513. if (this.source.videoId) {
  514. this.aliplayer.pause();
  515. } else {
  516. this.paused ? this.audio.pause() : this.audio.play();
  517. }
  518. },
  519. // 停止拖动音频进度条
  520. onSeekEnd: function () {
  521. this.paused = false;
  522. if (this.source.videoId) {
  523. var time = this.aliplayer.getDuration() * this.width / 100;
  524. this.aliplayer.seek(time);
  525. this.aliplayer.play();
  526. } else {
  527. this.currentTime = this.duration * this.width / 100;
  528. this.audio.currentTime = this.currentTime;
  529. this.paused ? this.audio.pause() : this.audio.play();
  530. }
  531. }
  532. }
  533. });
  534. });
  535. </script>
  536. {/block}