时空网前端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

751 lines
18 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. <template>
  2. <view>
  3. <skeleton :loading="skeletonLoading" :row="12" :showAvatar="false" :showTitle="true">
  4. <block v-if="isRight(goods_detail)">
  5. <!-- 商品图片轮播 -->
  6. <swiper :current="current" :indicator-dots="goods_detail.banners.length > 1 ? true : false"
  7. :circular="true" class="swiper-box" indicator-active-color="#FE9903">
  8. <swiper-item v-for="(item, index) in goods_detail.banners" :key="item.id">
  9. <image mode="aspectFill" :src="item.cover" style="width: 100%; height: 100%;"
  10. @click="lookImg(index)"></image>
  11. </swiper-item>
  12. </swiper>
  13. <view class="bill-position" @tap='formSubmit()'>
  14. <button class="cu-btn1 margin-left-sm lf-font-28 lf-m-20">
  15. 分享海报
  16. </button>
  17. </view>
  18. <!-- 商品主要信息 -->
  19. <view class="head-info">
  20. <view class="lf-font-40">{{ goods_detail.name }}</view>
  21. <view class="lf-row-between lf-font-24 lf-m-t-30 lf-p-b-20">
  22. <view class="lf-flex price">
  23. <lf-price :price="goods_detail.specs[0].selling_price"></lf-price>
  24. <view class="lf-m-l-20">¥{{ goods_detail.specs[0].original_price }}</view>
  25. <view v-if="goods_detail.specs[0].cost">{{ goods_detail.specs[0].cost }}</view>
  26. </view>
  27. <view class="lf-font-24 lf-text-right">
  28. <view class="lf-color-gray">{{ goods_detail.specs[0].sold_stock_text }}</view>
  29. <view class="lf-color-primary">{{ goods_detail.specs[0].stock_text }}</view>
  30. </view>
  31. </view>
  32. <view class="label-box" v-if="goods_detail.tags && goods_detail.tags.length">
  33. <view class="label-item" v-for="(item, index) in goods_detail.tags" :key="index">{{ item }}
  34. </view>
  35. </view>
  36. </view>
  37. <!-- 地址信息 -->
  38. <view class="address-box">
  39. <view class="lf-font-32 lf-font-bold">适用门店</view>
  40. <view class="lf-m-t-20 lf-row-between">
  41. <view class="lf-flex">
  42. <image mode="aspectFill" class="lf-fle shop-img" :src="goods_detail.store.cover"
  43. v-if="goods_detail.store.cover"></image>
  44. <image mode="aspectFill" class="lf-fle shop-img" src="../../static/center/shop-logo.png"
  45. v-else></image>
  46. <view class="lf-font-32 lf-m-l-20 lf-line-1" style="max-width: 512rpx;">
  47. {{ goods_detail.store.name }}</view>
  48. </view>
  49. <view @click="makePhoneCall(goods_detail.store.tel)">
  50. <text class="lf-iconfont lf-icon-dianhua lf-font-40" style="color: #3A62FF;"></text>
  51. </view>
  52. </view>
  53. <view class="lf-flex lf-m-t-20" @click="openMap">
  54. <view style="width: 60rpx; height: 60rpx;" class="lf-row-center">
  55. <text class="lf-iconfont lf-icon-dizhi lf-font-40" style="color: #555555;"></text>
  56. </view>
  57. <view class="lf-m-l-20 lf-font-28" style="color: #555555;">{{ goods_detail.store.address }}
  58. </view>
  59. </view>
  60. </view>
  61. <!-- 商品详情 -->
  62. <view class="goods-detail">
  63. <view class="lf-font-32 lf-font-bold lf-m-b-20">商品详情</view>
  64. <rich-text :nodes="afterDone"
  65. v-if="goods_detail.content_type == 'rich_text'"></rich-text>
  66. <image class="goods-img" :src="item" v-for="(item, index) in goods_detail.content" :key="index"
  67. v-if="goods_detail.content_type == 'img'"></image>
  68. </view>
  69. <!-- 修饰专用 -->
  70. <view class="extra"></view>
  71. <!-- 吸底操作按钮 -->
  72. <view class="lf-row-between fixed-bottom">
  73. <view class="lf-flex lf-p-t-10 lf-p-b-10">
  74. <view class="lf-flex-column lf-row-center icon-item"
  75. @click="$url('/pages/index/index', {type: 'switch'})">
  76. <image class="icon-img" src="../../static/center/home.png"></image>
  77. <view class="lf-m-t-1">首页</view>
  78. </view>
  79. <view class="lf-flex-column lf-row-center icon-item"
  80. @click="$url('/pages/contactService/index')">
  81. <image class="icon-img" src="../../static/center/service.png"></image>
  82. <view class="lf-m-t-1">客服</view>
  83. </view>
  84. <view class="lf-flex-column lf-row-center icon-item" @click="switchCollect">
  85. <image class="icon-img" src="../../static/center/collect-active.png" v-if="is_collect">
  86. </image>
  87. <image class="icon-img" src="../../static/center/collect.png" v-else></image>
  88. <view class="lf-m-t-1">{{ is_collect ? '已收藏' : '收藏' }}</view>
  89. </view>
  90. <button class="lf-flex-column lf-row-center icon-item" open-type="share">
  91. <image class="icon-img" src="../../static/center/share.png"></image>
  92. <view class="lf-m-t-1">分享</view>
  93. </button>
  94. </view>
  95. <button class="btn" @click="toAddOrder">立即抢购</button>
  96. </view>
  97. <!-- 回到顶部 -->
  98. <!-- <u-back-top :scroll-top="pageScrollTop" :custom-style="{background: 'rgba(51, 51 51, 0.3)'}" :icon-style="{color: '#ffffff'}"></u-back-top> -->
  99. <u-back-top :scroll-top="pageScrollTop" :custom-style="{background: 'rgba(51, 51 51, 0.3)'}">
  100. </u-back-top>
  101. </block>
  102. </skeleton>
  103. <view class="canvas-box">
  104. <canvas style="width: 375px;height: 667px;position:fixed;top:9999px" canvas-id="mycanvas" />
  105. </view>
  106. <view class='imagePathBox' v-if="maskHidden == true && imagePath" @click="maskHidden = false ">
  107. <image :src="imagePath" class='shengcheng' mode="widthFix"></image>
  108. <button class='baocun' @click.stop="saveBill()">保存相册分享到朋友圈</button>
  109. </view>
  110. </view>
  111. </template>
  112. <script>
  113. let SparkMD5 = require("@/common/SparkMD5.js"); // 签名加密js文件
  114. export default {
  115. data() {
  116. return {
  117. current: 0, // 轮播下标
  118. goods_id: 0,
  119. goods_detail: {},
  120. is_collect: 0, // 1为当前收藏商品了,0为否
  121. skeletonLoading: true,
  122. base64Img: '',
  123. checkArea: 'Cannot find module',
  124. maskHidden: false,
  125. info: {
  126. avatar: '',
  127. nickname: '',
  128. id: '',
  129. tel: '',
  130. tags: []
  131. },
  132. showLogin: true,
  133. imagePath: '',
  134. userToken: '',
  135. wxCode: '',
  136. onceCode: '',
  137. pt: 1,
  138. afterDone: '',
  139. backgroundImg: ''
  140. }
  141. },
  142. computed: {
  143. isRight() {
  144. return function(val) {
  145. return this.$shared.isRight(val);
  146. }
  147. }
  148. },
  149. onLoad(options) {
  150. this.goods_id = options.id;
  151. this.pt = options.pt || 1;
  152. this.getGoodsDetail();
  153. this.getWxCode()
  154. this.getBackground()
  155. },
  156. methods: {
  157. getBackground() {
  158. let _this = this
  159. _this.$http(_this.API.API_BILLBACKGROUND, {
  160. type: 'goods'
  161. }).then(res => {
  162. let img = res.data.img_url
  163. if (img) {
  164. wx.getImageInfo({
  165. src: img,
  166. success: function(sres) {
  167. _this.backgroundImg = sres.path
  168. }
  169. })
  170. }
  171. })
  172. },
  173. //商品绑定
  174. bindGoods() {
  175. var _this = this;
  176. let yy = new Date().getFullYear();
  177. let mm = new Date().getMonth()+1;
  178. let dd = new Date().getDate();
  179. let hh = new Date().getHours();
  180. let mf = new Date().getMinutes()<10 ? '0'+new Date().getMinutes() : new Date().getMinutes();
  181. let ss = new Date().getSeconds()<10 ? '0'+new Date().getSeconds() : new Date().getSeconds();
  182. let gettime = yy+'-'+mm+'-'+dd+' '+hh+':'+mf+':'+ss;
  183. console.log(gettime)
  184. console.log(_this.goods_detail.name)
  185. let userInfo = uni.getStorageSync('userinfo') || {};
  186. let timeDate = Math.round(new Date().getTime() / 1000).toString();
  187. console.log(SparkMD5)
  188. let md5TimeDate = SparkMD5.hash(timeDate)
  189. let nowTime = new Date().toLocaleString();
  190. _this.$http(_this.API.API_BINDGOODS, {
  191. deed: md5TimeDate,
  192. sid: userInfo.id,
  193. gid: _this.goods_id,
  194. gn: _this.goods_detail.name,
  195. t: gettime
  196. }).then(res => {
  197. console.log(res)
  198. }).catch(err => {
  199. console.log(err)
  200. })
  201. },
  202. getWxCode() {
  203. let userInfo = uni.getStorageSync('userinfo') || {};
  204. this.$http(this.API.API_WXBILL, {
  205. scene: 'route=goods&pt=2&id='+this.goods_id+'&share_id='+userInfo.id,
  206. page: 'pages/route/index',
  207. width: '2800'
  208. }).then(res => {
  209. this.wxCode = res.data.base_url
  210. if (this.wxCode) {
  211. this.getwxCodeImg()
  212. }
  213. })
  214. },
  215. //海报开始
  216. //保存头像
  217. getwxCodeImg() {
  218. var imgSrc = this.wxCode; //base64编码
  219. var save = wx.getFileSystemManager();
  220. var number = Math.random();
  221. save.writeFile({
  222. filePath: wx.env.USER_DATA_PATH + '/pic' + number + '.jpg',
  223. data: imgSrc,
  224. encoding: 'base64',
  225. success: res => {
  226. this.onceCode = wx.env.USER_DATA_PATH + '/pic' + number + '.jpg'
  227. },
  228. fail: err => {
  229. console.log(err)
  230. }
  231. })
  232. },
  233. createNewImg() {
  234. var that = this;
  235. var context = wx.createCanvasContext('mycanvas');
  236. var path = that.backgroundImg;
  237. context.drawImage(path, 0, 0, 375, 667);
  238. //绘制二维码
  239. let wxcode = that.onceCode
  240. context.drawImage(wxcode, 18, 530, 120, 120);
  241. //绘制名字
  242. // context.setFontSize(24);
  243. // context.setFillStyle('#fff');
  244. // context.setTextAlign('center');
  245. // context.fillText(name, 34, 620);
  246. context.stroke();
  247. context.draw();
  248. //将生成好的图片保存到本地,需要延迟一会,绘制期间耗时
  249. setTimeout(function() {
  250. wx.canvasToTempFilePath({
  251. canvasId: 'mycanvas',
  252. success: function(res) {
  253. that.imagePath = res.tempFilePath;
  254. if (that.imagePath) {
  255. that.canvasHidden = true
  256. that.maskHidden = true
  257. }
  258. console.log('海报生成成功')
  259. console.log(res)
  260. console.log('图片链接', that.imagePath)
  261. },
  262. fail: function(res) {
  263. console.log(res);
  264. }
  265. });
  266. }, 200);
  267. },
  268. saveBill() {
  269. var that = this
  270. wx.saveImageToPhotosAlbum({
  271. filePath: that.imagePath,
  272. success(res) {
  273. wx.showModal({
  274. content: '图片已保存到相册,赶紧晒一下吧~',
  275. showCancel: false,
  276. confirmText: '好的',
  277. confirmColor: '#333',
  278. success: function(res) {
  279. if (res.confirm) {
  280. console.log('用户点击确定');
  281. that.maskHidden = false
  282. }
  283. },
  284. fail: function(res) {
  285. that.maskHidden = false
  286. }
  287. })
  288. }
  289. })
  290. },
  291. formSubmit() {
  292. var that = this;
  293. wx.showToast({
  294. title: '生成海报中...',
  295. icon: 'loading',
  296. duration: 1000
  297. });
  298. wx.hideToast()
  299. console.log(that.onceCode)
  300. console.log(that.backgroundImg)
  301. if(that.onceCode&&that.backgroundImg) {
  302. that.createNewImg()
  303. }else if(!that.onceCode){
  304. this.$msg('小程序码生成失败!')
  305. }else if(!that.backgroundImg){
  306. this.$msg('海报背景图生成失败!')
  307. }
  308. },
  309. //海报结束
  310. getGoodsDetail() {
  311. let that = this;
  312. this.$http(this.API.API_GOODS_DETAIL, {
  313. goods_id: this.goods_id
  314. }).then(res => {
  315. this.skeletonLoading = false;
  316. this.goods_detail = res.data;
  317. if(this.goods_detail) {
  318. if(this.pt == 2) {
  319. this.bindGoods()
  320. }
  321. }
  322. this.afterDone = this.formatRichText(this.goods_detail.content)
  323. this.is_collect = Boolean(res.data.user.is_collect);
  324. }).catch(err => {
  325. this.skeletonLoading = false;
  326. setTimeout(() => {
  327. that.$toBack();
  328. }, 1000);
  329. })
  330. },
  331. // 切换商品收藏
  332. switchCollect() {
  333. let userInfo = uni.getStorageSync('userinfo') || {};
  334. if (!userInfo.id || !userInfo.nickname || !userInfo.avatar) {
  335. this.$url('/pages/login/index?type=userinfo');
  336. return;
  337. }
  338. this.$http(this.API.API_COLLECT_DEAL, {
  339. goods_id: this.goods_id
  340. }).then(res => {
  341. this.$msg(res.msg);
  342. this.is_collect = Boolean(res.data.user.is_collect);
  343. })
  344. },
  345. // 拨打电话
  346. makePhoneCall(phoneStr) {
  347. uni.makePhoneCall({
  348. phoneNumber: String(phoneStr)
  349. })
  350. },
  351. // 打开地图
  352. openMap() {
  353. // return;
  354. uni.openLocation({
  355. longitude: 108.36637,
  356. latitude: 22.817746,
  357. scale: 18,
  358. name: this.goods_detail.store.address
  359. })
  360. },
  361. // 跳转到确认下单页面
  362. toAddOrder() {
  363. let goods_id = this.goods_detail.id;
  364. let goods_specs_id = this.goods_detail.specs[0].id
  365. this.$url('/pages/order/confirm-order?goods_id=' + goods_id + '&goods_specs_id=' + goods_specs_id +'&pt='+ this.pt);
  366. },
  367. // 预览图片
  368. lookImg(index) {
  369. this.$u.throttle(() => {
  370. let goods_banner = this.goods_detail.banners || [];
  371. let banners = goods_banner.map(item => item.cover);
  372. uni.previewImage({
  373. urls: banners,
  374. current: index
  375. })
  376. }, 200);
  377. },
  378. // 富文本处理
  379. formatRichText(richText) {
  380. if (richText != null) {
  381. let newRichText = richText.replace(/<img[^>]*>/gi, function(match, capture) {
  382. match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
  383. match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
  384. match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
  385. return match;
  386. });
  387. newRichText = newRichText.replace(/style="[^"]+"/gi, function(match, capture) {
  388. match = match.replace(/width:[^;]+;/gi, 'width:100%;').replace(/width:[^;]+;/gi,
  389. 'width:100%;');
  390. return match;
  391. });
  392. newRichText = newRichText.replace(/<br[^>]*\/>/gi, '');
  393. newRichText = newRichText.replace(/\<img/gi,
  394. '<img style="width:100%;height:auto;display:block;margin:10px 0;"');
  395. return newRichText;
  396. } else {
  397. return null;
  398. }
  399. }
  400. },
  401. onShareAppMessage() {
  402. let goods = this.goods_detail;
  403. let title = goods.name;
  404. let imageUrl = goods.share_cover || goods.cover;
  405. let path = '/pages/route/index?route=goods_detail&id=' + goods.id;
  406. return {
  407. title,
  408. path,
  409. imageUrl
  410. }
  411. }
  412. }
  413. </script>
  414. <style>
  415. page {
  416. background-color: #f5f5f5;
  417. overflow-x: hidden;
  418. }
  419. </style>
  420. <style lang="scss" scoped="scoped">
  421. .bill-position {
  422. position: absolute;
  423. top: 0;
  424. right: 0;
  425. }
  426. .cu-btn1 {
  427. position: relative;
  428. display: inline-flex;
  429. align-items: center;
  430. justify-content: center;
  431. box-sizing: border-box;
  432. padding: 0 30rpx;
  433. font-size: 28rpx;
  434. height: 64rpx;
  435. line-height: 1;
  436. text-align: center;
  437. text-decoration: none;
  438. overflow: visible;
  439. margin-left: initial;
  440. transform: translate(0upx, 0upx);
  441. margin-right: initial;
  442. background-color: rgba(0, 0, 0, 0.5);
  443. color: #FFFFFF;
  444. border-radius: 33rpx;
  445. }
  446. .swiper-box {
  447. width: 750rpx;
  448. height: 520rpx;
  449. background-color: #FFFFFF;
  450. }
  451. .head-info {
  452. width: 750rpx;
  453. height: auto;
  454. box-sizing: border-box;
  455. padding: 0 32rpx;
  456. padding-top: 20rpx;
  457. background-color: #FFFFFF;
  458. // .price>view:nth-of-type(1){
  459. // color: #FF0000;
  460. // margin-right: 22rpx;
  461. // font-weight: bold;
  462. // }
  463. .price>view:nth-of-type(1) {
  464. text-decoration: line-through;
  465. color: #777777;
  466. margin-right: 22rpx;
  467. }
  468. .price>view:nth-of-type(2) {
  469. width: max-content;
  470. padding: 0 18rpx;
  471. height: 46rpx;
  472. background-color: #FE9903;
  473. border-radius: 10rpx;
  474. display: flex;
  475. justify-content: center;
  476. align-items: center;
  477. color: #FFFFFF;
  478. }
  479. .label-box {
  480. min-height: 130rpx;
  481. width: 100%;
  482. border-top: 1rpx solid #E5E5E5;
  483. display: flex;
  484. flex-wrap: wrap;
  485. padding: 30rpx 0 10rpx 0;
  486. .label-item {
  487. width: 156rpx;
  488. height: 70rpx;
  489. border-radius: 10rpx;
  490. border: 2rpx solid #FE9903;
  491. display: flex;
  492. justify-content: center;
  493. align-items: center;
  494. font-size: 28rpx;
  495. color: #FE9903;
  496. margin-right: 20rpx;
  497. margin-bottom: 20rpx;
  498. }
  499. }
  500. }
  501. .address-box {
  502. width: 750rpx;
  503. height: auto;
  504. box-sizing: border-box;
  505. background-color: #FFFFFF;
  506. padding: 32rpx;
  507. margin-top: 20rpx;
  508. .shop-img {
  509. width: 60rpx;
  510. height: 60rpx;
  511. border-radius: 50%;
  512. }
  513. }
  514. .goods-detail {
  515. width: 750rpx;
  516. height: auto;
  517. background-color: #FFFFFF;
  518. padding: 32rpx;
  519. box-sizing: border-box;
  520. margin-top: 20rpx;
  521. .goods-img {
  522. width: 100%;
  523. }
  524. }
  525. .extra {
  526. width: 100%;
  527. height: 120rpx;
  528. padding-bottom: constant(safe-area-inset-bottom);
  529. padding-bottom: env(safe-area-inset-bottom);
  530. box-sizing: content-box;
  531. }
  532. .fixed-bottom {
  533. position: fixed;
  534. bottom: 0;
  535. left: 0;
  536. background-color: #FFFFFF;
  537. width: 100%;
  538. height: auto;
  539. padding: 0 32rpx;
  540. border-top: 1rpx solid #e5e5e5;
  541. padding-bottom: constant(safe-area-inset-bottom);
  542. padding-bottom: env(safe-area-inset-bottom);
  543. .icon-item {
  544. margin-right: 16rpx;
  545. background-color: transparent;
  546. margin: 0;
  547. line-height: initial;
  548. font-size: 28rpx;
  549. font-weight: inherit;
  550. margin-right: 10rpx;
  551. padding: 0;
  552. width: 88rpx;
  553. &:first-child {
  554. padding-left: 0;
  555. }
  556. .icon-img {
  557. height: 50rpx;
  558. width: 50rpx;
  559. }
  560. }
  561. .btn {
  562. margin: 0;
  563. padding: 0;
  564. width: 208rpx;
  565. height: 80rpx;
  566. background-color: #FE9903;
  567. color: #FFFFFF;
  568. line-height: 80rpx;
  569. font-size: 32rpx;
  570. border-radius: 42rpx;
  571. }
  572. }
  573. //海报
  574. .bgImg {
  575. display: block;
  576. width: 100%;
  577. height: 366rpx;
  578. }
  579. .mine {
  580. display: block;
  581. text-align: center;
  582. color: #333;
  583. margin-top: 44rpx;
  584. }
  585. .code {
  586. display: block;
  587. text-align: center;
  588. color: #333;
  589. font-size: 76rpx;
  590. font-weight: bold;
  591. margin-top: 30rpx;
  592. }
  593. .who {
  594. display: block;
  595. margin-top: 80rpx;
  596. font-size: 32rpx;
  597. color: #333;
  598. text-align: center;
  599. }
  600. .inputBox {
  601. text-align: center;
  602. margin-top: 44rpx;
  603. }
  604. .input {
  605. text-align: center;
  606. width: 440rpx;
  607. height: 88rpx;
  608. border-radius: 44rpx;
  609. background: #f5f5f5;
  610. font-size: 32rpx;
  611. display: inline-block;
  612. }
  613. .btn {
  614. width: 160rpx;
  615. height: 88rpx;
  616. border-radius: 44rpx;
  617. background: rgba(254, 153, 3, 1);
  618. color: #333;
  619. font-size: 32rpx;
  620. display: inline-block;
  621. line-height: 88rpx;
  622. margin-left: 40rpx;
  623. }
  624. button[class="btn"]::after {
  625. border: 0;
  626. }
  627. .tishi {
  628. display: block;
  629. text-align: center;
  630. color: #999;
  631. margin-top: 30rpx;
  632. }
  633. .shareText {
  634. display: block;
  635. text-align: center;
  636. color: #333;
  637. font-size: 28rpx;
  638. margin-top: 100rpx;
  639. }
  640. .imgBox {
  641. text-align: center;
  642. width: 100%;
  643. margin-top: 60rpx;
  644. padding-bottom: 120rpx;
  645. }
  646. .img {
  647. display: inline-block;
  648. width: 100%;
  649. height: 100%;
  650. }
  651. .m_l {
  652. margin-left: 180rpx;
  653. }
  654. .zfbtn {
  655. display: inline-block;
  656. width: 120rpx;
  657. height: 120rpx;
  658. border-radius: 50%;
  659. background: transparent;
  660. outline: none;
  661. border: 0;
  662. padding: 0;
  663. }
  664. button[class="zfbtn"]::after {
  665. border: 0;
  666. }
  667. button[class="zfbtn m_l"]::after {
  668. border: 0;
  669. }
  670. .imagePathBox {
  671. width: 100%;
  672. height: 100%;
  673. background: rgba(0, 0, 0, 0.7);
  674. position: fixed;
  675. top: 0;
  676. left: 0;
  677. right: 0;
  678. bottom: 0;
  679. z-index: 10;
  680. }
  681. .shengcheng {
  682. width: 80%;
  683. height: 80%;
  684. position: fixed;
  685. top: 50rpx;
  686. left: 50%;
  687. margin-left: -40%;
  688. z-index: 10;
  689. }
  690. .baocun {
  691. display: block;
  692. width: 80%;
  693. height: 80rpx;
  694. padding: 0;
  695. line-height: 80rpx;
  696. text-align: center;
  697. position: fixed;
  698. bottom: 50rpx;
  699. left: 10%;
  700. background: rgba(254, 153, 3, 1);
  701. color: #fff;
  702. font-size: 32rpx;
  703. border-radius: 44rpx;
  704. }
  705. button[class="baocun"]::after {
  706. border: 0;
  707. }
  708. </style>