details.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <!-- 商品详情 -->
  2. <template>
  3. <div class="default-width py-5" v-if="state.goodsInfo && Object.keys(state.goodsInfo).length">
  4. <v-card class="carousel border-radius-8 white-bgc pa-5" style="width: 100%;">
  5. <div class=" d-flex">
  6. <!-- 图片展示-轮播 -->
  7. <div style="width: 500px; height: 500px;">
  8. <div v-if="selectedSkuPicUrl" class="selectedSkuImgBox" @mouseover="showSelectedSkuImg = true" @mouseleave="showSelectedSkuImg = false">
  9. <v-img :src="selectedSkuPicUrl" :aspect-ratio="1" style="border-radius: 8px;"></v-img>
  10. <v-btn
  11. v-show="showSelectedSkuImg"
  12. size="x-small"
  13. class="close px-3"
  14. @click="selectedSkuPicUrl = ''"
  15. >关闭</v-btn>
  16. </div>
  17. <v-carousel v-else show-arrows="hover" cycle :model-value="0" :hide-delimiters="!carouselHover" @mouseover="carouselHover = true" @mouseleave="carouselHover = false">
  18. <v-carousel-item v-for="(imgUrl, i) in state.goodsInfo.sliderPicUrls" :key="'wareImg'+i" @click="null">
  19. <v-img :src="imgUrl" :aspect-ratio="1" style="border-radius: 8px;"></v-img>
  20. </v-carousel-item>
  21. </v-carousel>
  22. </div>
  23. <!-- s-select-sku 商品属性选择 -->
  24. <div style="flex: 1;" class="pl-10">
  25. <!-- 大标题 -->
  26. <div class="title-name">{{ state.goodsInfo?.name || '--' }}</div>
  27. <!-- 小标题 -->
  28. <div class="title-introduction">{{ state.goodsInfo?.introduction || '--' }}</div>
  29. <!-- 价格 -->
  30. <div class="prices my-5">
  31. <div class="price mr-5"><span>¥</span>{{ selectedSkuPrice}}</div>
  32. <div class="marketPrice" v-if="selectedSkuMarketPrice && selectedSkuMarketPrice !== selectedSkuPrice">优惠前¥{{ selectedSkuMarketPrice}}</div>
  33. </div>
  34. <!-- 销量 -->
  35. <div class="salesCount mb-5 parameterColor"><span class="l-s-10">已售</span>:{{ state.goodsInfo?.salesCount || 0 }}</div>
  36. <!-- 属性选择组件 -->
  37. <selectSku
  38. v-if="showSelectSku"
  39. class="mb-7"
  40. :goodsInfo="state.goodsInfo"
  41. @change="onSkuChange"
  42. @buy="onBuy"
  43. ></selectSku>
  44. </div>
  45. </div>
  46. </v-card>
  47. <!-- 详情描述 detail-content-card -->
  48. <v-card class="carousel border-radius-8 white-bgc pa-5 mt-3" style="width: 100%;">
  49. <div>
  50. <div class="mb-3">
  51. <v-tabs v-model="describeTab" align-tabs="start" color="primary" bg-color="#f7f8fa" @update:model-value="null">
  52. <v-tab :value="0">商品介绍</v-tab>
  53. <v-tab :value="1">商品评价</v-tab>
  54. </v-tabs>
  55. </div>
  56. <describe v-if="describeTab === 0 && state.goodsInfo?.description" :content="state.goodsInfo.description"></describe>
  57. <comment v-if="describeTab === 1 && state.goodsId" class="detail-comment-selector" :goodsId="state.goodsId" />
  58. </div>
  59. </v-card>
  60. </div>
  61. <!-- 快速登录 -->
  62. <loginPage v-if="showLogin" @loginSuccess="loginSuccess" @close="loginClose"></loginPage>
  63. </template>
  64. <script setup>
  65. defineOptions({name: 'goods-details'})
  66. import { getProductDetail } from '@/api/mall/product'
  67. import selectSku from './details/s-select-sku.vue'
  68. import describe from './details/describe.vue'
  69. import comment from './details/detail-comment-card.vue'
  70. import { ref, reactive } from 'vue'
  71. import { useRouter } from 'vue-router'
  72. import Snackbar from '@/plugins/snackbar'
  73. import { getToken } from '@/utils/auth'
  74. import loginPage from '@/views/common/loginDialog.vue'
  75. const router = useRouter()
  76. const { id } = router.currentRoute.value.params
  77. const describeTab = ref(1)
  78. const selectedSkuPicUrl = ref('')
  79. const selectedSkuPrice = ref('')
  80. const selectedSkuMarketPrice = ref('')
  81. const showSelectSku = ref(false)
  82. const carouselHover = ref(false)
  83. const showSelectedSkuImg = ref(false)
  84. // 获取商品详情
  85. const getData = async () => {
  86. const obj = await getProductDetail({ id })
  87. if (!obj) return Snackbar.warning('未找到商品!')
  88. //
  89. // 加载到商品
  90. obj.sliderPicUrls = obj.sliderPicUrls || []
  91. state.skeletonLoading = false;
  92. state.goodsInfo = obj
  93. showSelectSku.value = true
  94. // // 加载是否收藏
  95. // if (isLogin.value) {
  96. // FavoriteApi.isFavoriteExists(state.goodsId, 'goods').then((res) => {
  97. // if (res.code !== 0) {
  98. // return;
  99. // }
  100. // state.goodsInfo.favorite = res.data;
  101. // });
  102. // }
  103. }
  104. getData()
  105. const calcPrice = (price) => { return price && (price-0) ? (price-0)/100 : '--' }
  106. const state = reactive({
  107. goodsId: id || 0,
  108. skeletonLoading: true, // SPU 加载中
  109. goodsInfo: {}, // SPU 信息
  110. showSelectSku: false, // 是否展示 SKU 选择弹窗
  111. selectedSku: {}, // 选中的 SKU
  112. settlementSku: {}, // 结算的 SKU:由于 selectedSku 不进行默认选中,所以初始使用结算价格最低的 SKU 作为基础展示
  113. showModel: false, // 是否展示 Coupon 优惠劵的弹窗
  114. couponInfo: [], // 可领取的 Coupon 优惠劵的列表
  115. showActivityModel: false, // 【满减送/限时折扣】是否展示 Activity 营销活动的弹窗
  116. rewardActivity: {}, // 【满减送】活动
  117. activityList: [], // 【秒杀/拼团/砍价】可参与的 Activity 营销活动的列表
  118. });
  119. // 规格变更
  120. function onSkuChange(e) {
  121. state.selectedSku = e;
  122. state.settlementSku = e;
  123. // console.log('onSkuChange:', e)
  124. selectedSkuPicUrl.value = state.selectedSku?.picUrl || ''
  125. if (selectedSkuPicUrl.value) showSelectedSkuImg.value = true
  126. selectedSkuPrice.value = calcPrice(state.selectedSku?.price || state.goodsInfo.price)
  127. selectedSkuMarketPrice.value = calcPrice(state.selectedSku?.marketPrice || state.goodsInfo.marketPrice)
  128. }
  129. const selectedSkuInfo = ref(null) // 购买规格信息
  130. const onBuy = async (info) => {
  131. if (!getToken()) return handleLogin()
  132. selectedSkuInfo.value = info
  133. console.log('购买规格信息:', info)
  134. }
  135. // Snackbar.warning('购买功能暂未开放,敬请期待!')
  136. // function onBuy(sku) {
  137. // sheep.$router.go('/pages/order/confirm', {
  138. // data: JSON.stringify({
  139. // order_type: 'goods',
  140. // combinationActivityId: state.activity.id,
  141. // combinationHeadId: state.combinationHeadId,
  142. // items: [
  143. // {
  144. // skuId: sku.id,
  145. // count: sku.count,
  146. // },
  147. // ],
  148. // }),
  149. // });
  150. // }
  151. const showLogin = ref(false)
  152. const handleLogin = () => {
  153. Snackbar.warning('您还未登录,请先登录后再试')
  154. showLogin.value = true // 打开快速登录弹窗
  155. }
  156. // 快速登录
  157. const loginSuccess = () => {
  158. showLogin.value = false
  159. Snackbar.success('登录成功')
  160. // router.go(0)
  161. }
  162. const loginClose = () => {
  163. showLogin.value = false
  164. Snackbar.warning('您已取消登录')
  165. }
  166. </script>
  167. <style lang="scss" scoped>
  168. .border-radius-8 {
  169. border-radius: 8px;
  170. }
  171. .title-name {
  172. font-size: 24px;
  173. line-height: 1.25;
  174. color: #000;
  175. margin-bottom: 12px;
  176. font-weight: bold;
  177. }
  178. .title-introduction {
  179. font-size: 18px;
  180. line-height: 1.5;
  181. color: #6b6b6b;
  182. }
  183. .parameterColor {
  184. color: #7a7a7a;
  185. }
  186. .prices {
  187. display: flex;
  188. align-items: flex-end;
  189. color: #ff5000;
  190. font-size: 28px;
  191. margin: 8px 0;
  192. }
  193. .price {
  194. font-weight: 600;
  195. span {
  196. font-size: 16px;
  197. }
  198. }
  199. .marketPrice {
  200. color: #b7b7b7;
  201. font-size: 16px;
  202. line-height: 34px;
  203. // text-decoration: line-through;
  204. }
  205. .selectedSkuImgBox {
  206. position: relative;
  207. .close {
  208. position: absolute;
  209. bottom: 10px;
  210. right: 10px;
  211. }
  212. }
  213. </style>