goods-detail.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. <template>
  2. <view class="product-detail">
  3. <tui-skeleton v-if="isRequest" :loadingType="3" :isLoading="true"></tui-skeleton>
  4. <template v-else>
  5. <!-- 顶部导航 -->
  6. <goods-top-tabs @change="onTabChange" :current="currentTab" v-show="scrollTop > 100"></goods-top-tabs>
  7. <simple-safe-view>
  8. <!-- 锚点0 -->
  9. <view id="anchor-0" class="anchor"></view>
  10. <!-- 轮播 -->
  11. <goods-image-swiper
  12. :list="imageList"
  13. @click="onSwiperClick"
  14. @change="onSwiperChange"
  15. :current="current"
  16. :autoplay="autoplay"
  17. ></goods-image-swiper>
  18. <!-- 价格 -->
  19. <goods-price :productData="productInfo"></goods-price>
  20. <!-- 活动优惠券 -->
  21. <goods-coupon-list
  22. @click="couponVisiable = true"
  23. :couponList="couponList"
  24. v-if="couponList.length > 0"
  25. ></goods-coupon-list>
  26. <!-- 商品基本信息:商品名称 && 分享 && 标签 && 备注 && 服务-->
  27. <goods-info @share="onShare" :productData="productInfo"></goods-info>
  28. <view class="section" v-if="productInfo.skus.length > 0">
  29. <goods-unit-section :skuList="productInfo.skus" @click="onUnitClick"></goods-unit-section>
  30. </view>
  31. <!-- 参数 -->
  32. <view class="section" v-if="productInfo.parametersList.length > 0">
  33. <goods-params-section :paramList="productInfo.parametersList"></goods-params-section>
  34. </view>
  35. <!-- 优惠券 -->
  36. <view class="section" v-if="couponList.length > 0">
  37. <goods-coupon-section @click="couponVisiable = true" :couponList="couponList"></goods-coupon-section>
  38. </view>
  39. <!-- 特殊商品退货须知 -->
  40. <view class="section" v-if="returnGoodsStutas">
  41. <goods-return-instructions :content="helpContent"></goods-return-instructions>
  42. </view>
  43. <!-- 锚点1 -->
  44. <view id="anchor-1" class="anchor"></view>
  45. <!-- 商品详情 -->
  46. <view class="section detail">
  47. <view class="title">商品详情</view>
  48. <template v-if="productDetail && productDetail.detailInfo">
  49. <uParse :content="productDetail.detailInfo" />
  50. </template>
  51. <!-- 空 -->
  52. <view class="section-empty" v-else>暂无商品详情</view>
  53. </view>
  54. <!-- 锚点2 -->
  55. <view id="anchor-2" class="anchor"></view>
  56. <!-- 服务项目 -->
  57. <view class="section service-items">
  58. <view class="title">服务项目</view>
  59. <view v-if="productDetail && productDetail.serviceInfo" v-html="productDetail.serviceInfo"></view>
  60. <!-- 空 -->
  61. <view class="section-empty">暂无服务项目</view>
  62. </view>
  63. </simple-safe-view>
  64. <!-- 商品操作导航 -->
  65. <goods-navbar class="navbar" :class="navbarType" @rightClick="navbarRightClick" @leftClick="navbarLeftClick">
  66. <template v-slot:left>
  67. <view class="left-btn text1" v-text="leftButton[0]"></view>
  68. <view class="left-btn" v-if="leftButton[1]">
  69. <text class="prefix">券后</text>
  70. <text class="text2" v-text="leftButton[1]"></text>
  71. </view>
  72. </template>
  73. <template v-slot:right>
  74. <view class="right-btn text1" v-text="rightButton[0]"></view>
  75. <view class="right-btn" v-if="rightButton[1]">
  76. <text class="prefix">券后</text>
  77. <text class="text2" v-text="rightButton[1]"></text>
  78. </view>
  79. </template>
  80. </goods-navbar>
  81. <!-- 活动价 -->
  82. <cm-goods-activity-popup
  83. ref="activitypPopup"
  84. :ladderList="ladderPriceList"
  85. @open="goodsBuyPopup = false"
  86. @close="goodsBuyPopup = true"
  87. ></cm-goods-activity-popup>
  88. <!-- 分享弹窗 -->
  89. <cm-share-popup ref="sharePopup" :data="posterData" type="product"></cm-share-popup>
  90. <!-- 优惠券弹窗 -->
  91. <cm-coupon-popup
  92. :list="couponList"
  93. :visiable="couponVisiable"
  94. :hasSafeArea="true"
  95. @close="couponVisiable = false"
  96. @couponClick="onCouponClick"
  97. ></cm-coupon-popup>
  98. <!-- 返回顶部 -->
  99. <tui-scroll-top :scrollTop="scrollTop" :bottom="80"></tui-scroll-top>
  100. <!-- 购买弹窗 -->
  101. <cm-goods-buy-popup
  102. v-model="goodsBuyPopup"
  103. :product="productInfo"
  104. :couponList="couponList"
  105. :useType="useType"
  106. :buttonType="buttonType"
  107. @activityClick="handleShowActivity"
  108. @confirm="onSubmit"
  109. v-if="productInfo"
  110. ></cm-goods-buy-popup>
  111. </template>
  112. </view>
  113. </template>
  114. <script>
  115. import uParse from './uParse/src/wxParse.vue'
  116. import { debounce } from '@/common/utils.js'
  117. import { shareDataResult } from '@/common/share.helper.js'
  118. import { queryStringify } from '@/common/utils.js'
  119. import { mapGetters, mapActions } from 'vuex'
  120. import {
  121. generateNavbarButtonText,
  122. fetchPorductInfo,
  123. fetchCouponListByProduct,
  124. generateNavbarType
  125. } from '@/common/goods.helper.js'
  126. export default {
  127. components: {
  128. uParse
  129. },
  130. data() {
  131. return {
  132. isRequest: true,
  133. jumpState: 0,
  134. productId: '',
  135. // 轮播图
  136. currentTab: 0,
  137. current: 0,
  138. leftButton: {},
  139. rightButton: {},
  140. // 锚点列表
  141. anchorList: [],
  142. scrollTop: 0,
  143. productInfo: {},
  144. couponList: [],
  145. couponVisiable: false,
  146. navbarTypeFlag: '',
  147. // 海报数据
  148. posterData: {},
  149. collageId: '',
  150. groupBuyFlag: false, // 用户是否拼团购买
  151. autoplay: true,
  152. // 购买商品弹窗
  153. goodsBuyPopup: false,
  154. useType: 'buy',
  155. buttonType: 'left',
  156. ladderPriceList: []
  157. }
  158. },
  159. computed: {
  160. ...mapGetters(['userId']),
  161. // 轮播图
  162. imageList() {
  163. return this.productInfo.imageList
  164. },
  165. // 活动价
  166. // ladderList() {
  167. // return this.productInfo.ladderList
  168. // },
  169. // 商品详情
  170. productDetail() {
  171. return this.productInfo.productDetail
  172. },
  173. // 商品导航类型
  174. navbarType() {
  175. return generateNavbarType(this.productInfo)
  176. },
  177. // 当前商品默认可以使的优惠券
  178. couponTip() {
  179. if (this.productInfo.couponId) {
  180. const currentCoupon = this.couponList.find(coupon => this.productInfo.couponId === coupon.couponId)
  181. return currentCoupon?.couponTitle
  182. } else {
  183. return this.productInfo?.couponInfo?.split('|')[1]
  184. }
  185. },
  186. returnGoodsStutas() {
  187. return this.productInfo.returnGoodsStutas && this.productInfo.returnGoodsStutas === 2
  188. },
  189. helpContent() {
  190. return this.productInfo.helpContent
  191. }
  192. },
  193. onPageScroll(e) {
  194. this.scrollTop = e.scrollTop
  195. this.getAnchorList()
  196. },
  197. onShareAppMessage() {
  198. const shareData = {
  199. type: 1,
  200. productId: this.productInfo.productId,
  201. inviteUserId: this.userId,
  202. jumpState: this.jumpState
  203. }
  204. return shareDataResult(shareData, this.productInfo.name, this.imageList[0])
  205. },
  206. onLoad(options) {
  207. this.productId = parseInt(options.productId)
  208. this.jumpState = parseInt(options.jumpState)
  209. },
  210. onShow() {
  211. this.initPage()
  212. },
  213. onHide() {
  214. this.autoplay = false
  215. },
  216. methods: {
  217. ...mapActions('cart', ['addToCart']),
  218. // 选择规格
  219. onUnitClick() {
  220. this.useType = 'unit'
  221. this.goodsBuyPopup = true
  222. },
  223. async initPage() {
  224. this.autoplay = true
  225. // 获取商品详情
  226. this.fetchProductDetail()
  227. // 获取商品可用优惠券
  228. this.couponList = await fetchCouponListByProduct(this.productId)
  229. },
  230. // 获取商品详情
  231. async fetchProductDetail() {
  232. this.productInfo = await fetchPorductInfo(this.productId)
  233. this.isRequest = false
  234. this.productInfo.heUserId = this.jumpState === 1 ? 0 : this.userId
  235. this.initNavbarButton()
  236. },
  237. // 活动价弹窗
  238. handleShowActivity(current) {
  239. if (!current.ladderPriceList) return
  240. this.ladderPriceList = current.ladderPriceList
  241. this.$refs.activitypPopup.open()
  242. },
  243. // 商品提交
  244. onSubmit(detail) {
  245. // return uni.navigateTo({
  246. // url:'/pages/views/goods/Update-Maintenance'
  247. // })
  248. // 用户未登录
  249. if (!this.userId) {
  250. const pages = getCurrentPages()
  251. const page = pages[pages.length - 1]
  252. uni.setStorageSync('LOGIN_REDIRECT_URL', page.$page.fullPath)
  253. uni.redirectTo({ url: '/pages/authorize/login-custom' })
  254. this.goodsBuyPopup = false
  255. return
  256. }
  257. // 加入购物车
  258. if (detail.type === 'cart') {
  259. this.addToCart({
  260. skuId: detail.sku.skuId,
  261. productCount: detail.count,
  262. heUserId: this.productInfo.heUserId
  263. })
  264. this.goodsBuyPopup = false
  265. return
  266. }
  267. console.log(detail)
  268. // 立即购买
  269. if (detail.type === 'buy') {
  270. const submitData = {
  271. productId: detail.sku.productId, // 产品id
  272. skuId: detail.sku.skuId, // sku id
  273. productCount: detail.count, // 产品购买数量
  274. heUserId: this.productInfo.heUserId, // 协销用户id
  275. collageFlag: this.groupBuyFlag ? 1 : 0, // 是否拼团购买
  276. collageId: this.collageId, // 拼团id
  277. couponId: detail.sku.couponId, // 默认使用优惠券id
  278. allCount: detail.count // 商品总数
  279. }
  280. uni.setStorageSync('COMMIT_PRODUCT_INFO', submitData)
  281. this.$router.navigateTo('order/order-create?type=product')
  282. this.goodsBuyPopup = false
  283. return
  284. }
  285. },
  286. // 优惠券点击事件
  287. onCouponClick(couponData) {
  288. if (couponData.controlType === 'receive') {
  289. this.fetchCouponList()
  290. }
  291. },
  292. // 分享事件
  293. onShare() {
  294. const query = queryStringify({
  295. type: 1,
  296. productId: this.productInfo.productId,
  297. inviteUserId: this.userId,
  298. jumpState: this.jumpState
  299. })
  300. this.posterData = {
  301. porductName: this.productInfo.name,
  302. productPrice: this.productInfo.price,
  303. productOriginPrice:
  304. this.productInfo.normalPrice === this.productInfo.price ? 0 : this.productInfo.normalPrice,
  305. productImage: this.productInfo.mainImage,
  306. query: query
  307. }
  308. this.$refs.sharePopup.open()
  309. },
  310. // 轮播图事件
  311. onSwiperClick() {
  312. uni.previewImage({
  313. urls: this.imageList,
  314. current: this.current,
  315. loop: true
  316. })
  317. },
  318. // 轮播图切换
  319. onSwiperChange(current) {
  320. this.current = current
  321. },
  322. // 初始化导航按钮文案
  323. initNavbarButton() {
  324. const navbarButton = generateNavbarButtonText(this.productInfo)
  325. this.leftButton = navbarButton.left
  326. this.rightButton = navbarButton.right
  327. },
  328. // 导航菜单右侧按钮点击
  329. navbarRightClick(index) {
  330. this.navbarTypeFlag = index > 0 || this.productInfo.collageStatus > 0 ? 'buy' : 'cart'
  331. if (this.productInfo.activityType === 'group') {
  332. this.groupBuyFlag = index > 0
  333. }
  334. this.useType = 'buy'
  335. this.buttonType = index === 0 ? 'left' : 'right'
  336. this.goodsBuyPopup = true
  337. },
  338. // 导航栏菜单左侧按钮点击
  339. navbarLeftClick(index) {
  340. if (index === 0) {
  341. this.$router.switchTab('home')
  342. }
  343. if (index === 2) {
  344. if (!this.userId) {
  345. const pages = getCurrentPages()
  346. const page = pages[pages.length - 1]
  347. uni.setStorageSync('LOGIN_REDIRECT_URL', page.$page.fullPath)
  348. uni.redirectTo({ url: '/pages/authorize/login-custom' })
  349. return
  350. }
  351. this.$router.navigateTo('cart/cart')
  352. }
  353. },
  354. // 顶部tab切换
  355. onTabChange(index) {
  356. const offset = this.anchorList[index].top
  357. uni.pageScrollTo({
  358. scrollTop: this.scrollTop + offset - 40 - 10,
  359. duration: 300
  360. })
  361. },
  362. // 初始化锚点
  363. getAnchorList: debounce(
  364. function() {
  365. const query = uni.createSelectorQuery().in(this)
  366. query
  367. .selectAll('.anchor')
  368. .boundingClientRect(data => {
  369. this.anchorList = data
  370. this.setCurrentTabIndex()
  371. })
  372. .exec()
  373. },
  374. 300,
  375. false
  376. ),
  377. // 设置tab索引
  378. setCurrentTabIndex() {
  379. this.anchorList.forEach((item, index) => {
  380. if (item.bottom < 100) {
  381. this.currentTab = index
  382. }
  383. })
  384. }
  385. }
  386. }
  387. </script>
  388. <style lang="scss">
  389. .section.detail {
  390. image {
  391. display: block;
  392. }
  393. }
  394. </style>
  395. <style lang="scss" scoped>
  396. .product-detail {
  397. min-height: 100vh;
  398. padding-bottom: 100rpx;
  399. box-sizing: border-box;
  400. .section {
  401. margin: 24rpx 0;
  402. background-color: #fff;
  403. &.detail {
  404. padding: 24rpx;
  405. }
  406. &.service-items {
  407. padding: 24rpx;
  408. }
  409. .title {
  410. margin-bottom: 24rpx;
  411. font-size: 26rpx;
  412. font-weight: bold;
  413. }
  414. .section-empty {
  415. font-size: 24rpx;
  416. color: #999;
  417. }
  418. }
  419. .navbar {
  420. line-height: 1;
  421. text-align: center;
  422. &.group,
  423. &.normal {
  424. .prefix {
  425. display: none !important;
  426. }
  427. }
  428. .left-btn {
  429. color: #ff457b;
  430. &.text1 {
  431. font-size: 24rpx;
  432. margin-bottom: 4rpx;
  433. }
  434. .text2 {
  435. font-size: 24rpx;
  436. font-weight: bold;
  437. }
  438. .prefix {
  439. font-size: 20rpx;
  440. }
  441. }
  442. .right-btn {
  443. color: #fff;
  444. &.text1 {
  445. font-size: 24rpx;
  446. margin-bottom: 4rpx;
  447. }
  448. .text2 {
  449. font-size: 24rpx;
  450. font-weight: bold;
  451. }
  452. .prefix {
  453. font-size: 20rpx;
  454. }
  455. }
  456. }
  457. }
  458. </style>