detail.html 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  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. <link rel="stylesheet" href="{__PLUG_PATH}vue-photo-preview/skin.css">
  14. <script src="{__PLUG_PATH}vue-photo-preview/vue-photo-preview.js"></script>
  15. <style>
  16. body {
  17. padding-bottom: 1rem;
  18. padding-bottom: calc(1rem + constant(safe-area-inset-bottom));
  19. padding-bottom: calc(1rem + env(safe-area-inset-bottom));
  20. }
  21. .layui-layer-imgsee {
  22. display: none;
  23. }
  24. a[href^="tel"] {
  25. color: #191C6E;
  26. }
  27. </style>
  28. {/block}
  29. {block name="content"}
  30. <div v-cloak id="app" class="goods-detail">
  31. <div class="first">
  32. <div :style="{ height: width + 'px' }" class="swiper-container">
  33. <div class="swiper-wrapper">
  34. <div v-for="(item, index) in storeInfo.slider_image" :key="index" class="swiper-slide">
  35. <img :src="item">
  36. </div>
  37. </div>
  38. <div class="swiper-pagination"></div>
  39. </div>
  40. <div class="detail">
  41. <div class="price">
  42. <div class="now">¥<span>{{ storeInfo.price }}</span></div>
  43. <div class="vip">¥{{ storeInfo.vip_price }}</div>
  44. </div>
  45. <div class="title">{{ storeInfo.store_name }}</div>
  46. <div class="other">
  47. <div>库存:{{ storeInfo.stock }}件</div>
  48. <div>销量:{{ storeInfo.sales }}件</div>
  49. </div>
  50. </div>
  51. </div>
  52. <!-- 相关讲师 -->
  53. <related-lecturer v-if="lecturer" :lecturer="lecturer"></related-lecturer>
  54. <div class="second">
  55. <div class="tab">
  56. <div :class="{ on: tabOn == 1 }" @click="tabOn = 1">详情</div>
  57. <div :class="{ on: tabOn == 2 }" @click="tabOn = 2">评价({{ whole }})</div>
  58. <div :class="{ on: tabOn == 3 }" @click="tabOn = 3">赠送课程({{ specialList.length }})</div>
  59. </div>
  60. <div class="content">
  61. <div v-show="tabOn == 1" class="detail" v-html="storeInfo.description"></div>
  62. <div v-show="tabOn == 2" class="evaluate-section">
  63. <div class="head">
  64. <div class="score">
  65. <div>评分<span v-for="star in 5" :key="star" :class="{ on: star <= rate }" class="iconfont iconxing"></span></div>
  66. <div>{{ positive_rate }}%<span>好评率</span></div>
  67. </div>
  68. <div class="cate">
  69. <div :class="{ on: score === 4 }" @click="cateTab(4)">全部({{ whole }})</div>
  70. <div :class="{ on: score === 3 }" @click="cateTab(3)">好评({{ praise }})</div>
  71. <div :class="{ on: score === 2 }" @click="cateTab(2)">中评({{ review }})</div>
  72. <div :class="{ on: score === 1 }" @click="cateTab(1)">差评({{ difference }})</div>
  73. </div>
  74. </div>
  75. <!-- 评价列表 -->
  76. <evaluate-list :evaluate-list="evaluateList"></evaluate-list>
  77. <div v-if="loading" class="loading">
  78. <i class="fa fa-spinner fa-spin"></i>
  79. </div>
  80. <div v-if="finished && evaluateList.length" class="finished">已全部加载完</div>
  81. <div v-if="finished && !evaluateList.length" class="empty">
  82. <img src="{__WAP_PATH}zsff/images/empty.png" alt="暂无评价">
  83. <div>暂无评价</div>
  84. </div>
  85. </div>
  86. <div v-show="tabOn == 3" class="special">
  87. <a v-for="item in specialList" :key="item.id" :href="item.path">
  88. <div class="image">
  89. <img :src="item.image">
  90. <div>{{ item.typeName }}</div>
  91. </div>
  92. <div class="text">
  93. <div class="title">{{ item.title }}</div>
  94. <div class="label">
  95. <div v-for="label in item.label">{{ label }}</div>
  96. </div>
  97. <div class="price">¥<span>{{ item.money }}</span></div>
  98. </div>
  99. </a>
  100. </div>
  101. </div>
  102. </div>
  103. <div class="third">
  104. <div class="group">
  105. <button type="button" @click="goPage(1)">
  106. <img src="{__WAP_PATH}zsff/images/special01.png">
  107. <div>首页</div>
  108. </button>
  109. <button type="button" @click="goPage(2)">
  110. <img src="{__WAP_PATH}zsff/images/special02.png">
  111. <div>客服</div>
  112. </button>
  113. </div>
  114. <button type="button" @click="isLogin(goBuy)">立即购买</button>
  115. </div>
  116. <div class="share" @click="createSharePoster">
  117. <i class="iconfont iconfenxiang"></i>
  118. </div>
  119. <base-login :login-show="loginShow" :site-name="siteName" @login-close="logComplete"></base-login>
  120. <rebate-guide v-if="rebateMoney && isShareDisplaySwitch" :rebate-money="rebateMoney" @rebate-action="rebateAction"></rebate-guide>
  121. </div>
  122. <script>
  123. require([
  124. 'vue',
  125. 'store',
  126. 'helper',
  127. 'swiper',
  128. 'components/evaluate-list/index',
  129. 'components/base-login/index',
  130. 'components/rebate-guide/index',
  131. 'components/related-lecturer/index',
  132. 'qrcode',
  133. 'layer',
  134. '{__WAP_PATH}zsff/js/quick.js'
  135. ], function (Vue, api, $h, Swiper, evaluateList, BaseLogin, RebateGuide, RelatedLecturer) {
  136. var storeInfo = {$storeInfo};
  137. var siteUrl = '{$site_url}';
  138. var siteName = '{$Auth_site_name}';
  139. var isWeChat = '{$isWechat}';
  140. var uid = {$uid};
  141. var options={
  142. captionEl: false,
  143. fullscreenEl: false,
  144. zoomEl: false,
  145. arrowEl: false
  146. };
  147. Vue.use(vuePhotoPreview, options);
  148. var app = new Vue({
  149. el: '#app',
  150. components: {
  151. 'evaluate-list': evaluateList,
  152. 'base-login': BaseLogin,
  153. 'rebate-guide': RebateGuide,
  154. 'related-lecturer': RelatedLecturer
  155. },
  156. data: {
  157. storeInfo: storeInfo ? storeInfo : [],
  158. width: window.innerWidth,
  159. loginShow: false,
  160. isWeChat: isWeChat,
  161. url: isWeChat ? $h.U({c:'index',a:'login'}) : $h.U({c:'login',a:'phone_check'}),
  162. tabOn: 1,
  163. score: 4,
  164. page: 1,
  165. limit: 16,
  166. loading: false,
  167. finished: false,
  168. evaluateList: [],
  169. rate: 5,
  170. positive_rate: '0',
  171. whole: 0,
  172. praise: 0,
  173. review: 0,
  174. difference: 0,
  175. siteUrl: siteUrl,
  176. siteName: siteName,
  177. rebateMoney: 0,
  178. page2: 1,
  179. specialList: [],
  180. lecturer: null,
  181. isShareDisplaySwitch: {$is_share_display_switch} // 是否显示分享返佣
  182. },
  183. created: function () {
  184. var vm = this;
  185. if (isWeChat) {
  186. mapleWx($jssdk(), function () {
  187. this.onMenuShareAll({
  188. title: vm.storeInfo.store_name,
  189. desc: vm.storeInfo.store_info,
  190. imgUrl: vm.storeInfo.image,
  191. link: window.location.href + (window.location.search ? '&' : '?') + 'spread_uid=' + uid
  192. });
  193. });
  194. }
  195. window.addEventListener('resize', function () {
  196. vm.width = this.innerWidth;
  197. });
  198. $h.EventUtil.listenTouchDirection(document, function () {
  199. if (vm.tabOn === 2) {
  200. vm.getEvaluateList();
  201. }
  202. });
  203. },
  204. mounted: function () {
  205. var vm = this;
  206. this.$nextTick(function () {
  207. this.initSwiper();
  208. this.getLecturer();
  209. this.getEvaluateStatus();
  210. this.getEvaluateList();
  211. this.rebateAmount();
  212. this.getAssociatedTopics();
  213. });
  214. },
  215. updated: function () {
  216. this.$nextTick(function () {
  217. this.$previewRefresh();
  218. });
  219. },
  220. methods: {
  221. goPage: function (value) {
  222. var vm = this;
  223. switch (value) {
  224. case 1:
  225. window.location.assign("{:url('index/index')}");
  226. break;
  227. case 2:
  228. api.baseGet($h.U({
  229. c: 'public_api',
  230. a: 'public_data'
  231. }), function (res) {
  232. var data = res.data.data;
  233. if (data.customer_service === '3') {
  234. if (data.site_service_phone) {
  235. layer.confirm('是否拨打 <a href="tel:' + data.site_service_phone + '">' + data.site_service_phone + '</a> 进行咨询?', {
  236. title: false,
  237. closeBtn: false,
  238. btn: ['拨打', '取消']
  239. }, function (index) {
  240. window.location.assign('tel:' + data.site_service_phone);
  241. layer.close(index);
  242. });
  243. } else {
  244. layer.msg('抱歉,无法联系客服');
  245. }
  246. } else {
  247. window.location.assign($h.U({
  248. c: 'service',
  249. a: 'service_list'
  250. }));
  251. }
  252. });
  253. break;
  254. }
  255. },
  256. initSwiper: function () {
  257. new Swiper('.swiper-container', {
  258. loop: true,
  259. autoplay: true,
  260. pagination: {
  261. el: '.swiper-pagination'
  262. },
  263. observeParents: true
  264. });
  265. },
  266. isLogin: function () {
  267. var vm = this;
  268. var args = arguments;
  269. api.baseGet($h.U({
  270. c: 'index',
  271. a: 'user_login'
  272. }), args[0], function () {
  273. if (typeof args[1] === 'function') {
  274. args[1]();
  275. } else {
  276. vm.loginShow = true;
  277. }
  278. }, !!args[1]);
  279. },
  280. // 立即购买
  281. goBuy: function () {
  282. api.goBuy({
  283. productId: this.storeInfo.id,
  284. cartNum: 1
  285. }, function (cartId) {
  286. window.location.assign("{:url('special/confirm_order')}?cartId=" + cartId);
  287. });
  288. },
  289. enter: function () {
  290. this.loginShow = true;
  291. },
  292. //关闭登录
  293. loginClose:function(value){
  294. this.loginShow=false;
  295. value && this.logComplete();
  296. },
  297. //登录完成回调事件
  298. logComplete:function(){
  299. this.loginShow=false;
  300. },
  301. //所有插件回调处理事件
  302. changeVal: function (opt) {
  303. if (typeof opt != 'object') opt = {};
  304. var action = opt.action || '';
  305. var value = opt.value || '';
  306. this[action] && this[action](value);
  307. },
  308. // 获取评价列表
  309. getEvaluateList: function () {
  310. var vm = this;
  311. if (this.finished) {
  312. return false;
  313. }
  314. api.baseGet($h.U({
  315. c: 'auth_api',
  316. a: 'product_reply_list',
  317. q: {
  318. productId: this.storeInfo.id,
  319. score: this.score,
  320. page: this.page++,
  321. limit: this.limit
  322. }
  323. }), function (res) {
  324. var data = res.data.data;
  325. vm.evaluateList = vm.evaluateList.concat(data);
  326. vm.finished = vm.limit > data.length;
  327. });
  328. },
  329. // 获取各状态评价数量
  330. getEvaluateStatus: function () {
  331. var vm = this;
  332. api.baseGet($h.U({
  333. c: 'auth_api',
  334. a: 'product_reply_data',
  335. q: {
  336. productId: this.storeInfo.id
  337. }
  338. }), function (res) {
  339. var data = res.data.data;
  340. vm.rate = data.star;
  341. vm.positive_rate = data.positive_rate;
  342. vm.whole = data.whole;
  343. vm.praise = data.praise;
  344. vm.review = data.review;
  345. vm.difference = data.difference;
  346. })
  347. },
  348. // 评价列表状态切换
  349. cateTab: function (score) {
  350. if (this.score === score) {
  351. return;
  352. }
  353. this.score = score;
  354. this.evaluateList = [];
  355. this.loading = false;
  356. this.finished = false;
  357. this.page = 1;
  358. this.getEvaluateList();
  359. },
  360. // 生成分享海报
  361. createSharePoster: function () {
  362. var vm = this;
  363. var imagePromise = new Promise(function (resolve, reject) {
  364. var image = new Image();
  365. image.crossOrigin = 'anonymous';
  366. image.src = vm.storeInfo.image + '?' + new Date().getTime();
  367. image.onload = function () {
  368. resolve(image);
  369. },
  370. image.onerror = function () {
  371. reject('error-image');
  372. };
  373. }),
  374. qrcodePromise = new Promise(function (resolve, reject) {
  375. resolve(new QRCode(document.createElement('canvas'), vm.siteUrl));
  376. });
  377. Promise.all([
  378. imagePromise,
  379. qrcodePromise
  380. ]).then(function (sources) {
  381. var canvas = document.createElement('canvas');
  382. var context = canvas.getContext('2d');
  383. canvas.width = 600;
  384. canvas.height = 820;
  385. context.fillStyle = '#FFFFFF';
  386. context.fillRect(0, 0, 600, 820);
  387. context.drawImage(sources[0], 0, 0, 600, 600);
  388. context.drawImage(sources[1]._el.firstElementChild, 108, 635, 150, 150);
  389. context.font = '22px sans-serif';
  390. context.fillStyle = '#999999';
  391. context.textBaseline = 'top';
  392. var text = '邀您加入' + siteName;
  393. var list = [];
  394. var start = 0;
  395. for (var i = 0; i <= text.length; i++) {
  396. if (context.measureText(text.slice(start, i)).width > 198) {
  397. list.push(text.slice(start, i - 1));
  398. start = i - 1;
  399. }
  400. }
  401. if (start !== text.length) {
  402. list.push(text.slice(start));
  403. }
  404. if (list.length > 3) {
  405. list.length = 3;
  406. for (var j = 0; j <= list[2].length; j++) {
  407. if (context.measureText(list[2].slice(0, j) + '……').width > 198) {
  408. list[2] = list[2].slice(0, j - 1) + '……';
  409. break;
  410. }
  411. }
  412. }
  413. list.push('长按识别或扫码进入');
  414. for (var k = 0; k < list.length; k++) {
  415. context.fillText(list[k], 294, 635 + (150 / list.length) * k);
  416. }
  417. layer.photos({
  418. photos: {
  419. data: [
  420. {
  421. src: canvas.toDataURL('image/jpeg')
  422. }
  423. ]
  424. },
  425. anim: 5
  426. });
  427. canvas = null;
  428. }).catch(function (err) {
  429. $h.pushMsg(err);
  430. });
  431. },
  432. // 获取返佣金额
  433. rebateAmount: function () {
  434. var vm = this;
  435. api.baseGet($h.U({
  436. c: 'auth_api',
  437. a: 'rebateAmount',
  438. p: {
  439. type: 2,
  440. id: this.storeInfo.id
  441. }
  442. }), function (res) {
  443. vm.rebateMoney = Number(res.data.data.brokeragePrice);
  444. });
  445. },
  446. rebateAction: function (value) {
  447. switch (value) {
  448. case 'close':
  449. this.rebateMoney = 0;
  450. break;
  451. case 'share':
  452. this.createSharePoster();
  453. break;
  454. }
  455. },
  456. // 获取关联专题
  457. getAssociatedTopics: function () {
  458. var vm = this;
  459. api.baseGet($h.U({
  460. c: 'store',
  461. a: 'getAssociatedTopics',
  462. q: {
  463. page: this.page2,
  464. list: 100,
  465. id: this.storeInfo.id
  466. }
  467. }), function (res) {
  468. var data = res.data.data;
  469. data.forEach(function (item) {
  470. var path = "{:url('special/details')}";
  471. var typeName = '图文';
  472. if (item.is_light) {
  473. path = "{:url('special/single_details')}";
  474. }
  475. if (item.type === 2 || item.light_type === 2) {
  476. typeName = '音频';
  477. } else if (item.type === 3 || item.light_type === 3) {
  478. typeName = '视频';
  479. } else if (item.type === 4) {
  480. typeName = '直播';
  481. } else if (item.type === 5) {
  482. typeName = '专栏';
  483. }
  484. item.path = path + '?id=' + item.id;
  485. item.typeName = typeName;
  486. });
  487. vm.specialList = data;
  488. });
  489. },
  490. // 相关讲师
  491. getLecturer: function () {
  492. var vm = this;
  493. api.baseGet($h.U({
  494. c: 'auth_api',
  495. a: 'getLecturer',
  496. q: {
  497. mer_id: this.storeInfo.mer_id
  498. }
  499. }), function (res) {
  500. vm.lecturer = res.data.data;
  501. });
  502. }
  503. }
  504. });
  505. });
  506. </script>
  507. {/block}