lifanagju_citu 5 miesięcy temu
rodzic
commit
f41ec6fc06

+ 15 - 6
src/components/FormUI/su-number-box/su-number-box.vue

@@ -33,12 +33,6 @@
     <!-- <view @click="_calcValue('plus')" class="uni-numbox__plus uni-numbox-btns">
 			<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }">+</text>
 		</view> -->
-    <v-btn
-      icon="mdi-plus" 
-      size="x-small" 
-      :disabled="inputValue >= max || disabled" 
-      @click="_calcValue('plus')"
-    ></v-btn>
     <!-- <text
       class="cicon-add-round"
       :class="{
@@ -48,6 +42,13 @@
       }"
       @click="_calcValue('plus')"
     ></text> -->
+    <v-btn
+      icon="mdi-plus" 
+      size="x-small" 
+      :disabled="inputValue >= max || disabled" 
+      @click="_calcValue('plus')"
+    ></v-btn>
+    <div class="ml-3" style="color: #b7b7b7; font-size: 14px;">库存:{{ stock }}</div>
   </view>
 </template>
 <script>
@@ -111,6 +112,7 @@
     data() {
       return {
         inputValue: 0,
+        stock: 0,
       };
     },
     watch: {
@@ -120,6 +122,13 @@
       modelValue(val) {
         this.inputValue = +val;
       },
+			// stock: {
+			// 	handler(val) {
+			// 		this.stock = +val
+			// 	},
+			// 	immediate: true,
+			// 	deep: true
+			// },
     },
     created() {
       if (this.value === 1) {

+ 126 - 29
src/views/mall/components/details.vue

@@ -1,30 +1,58 @@
 <!-- 商品详情 -->
 <template>
-  <div class="default-width py-5" v-if="goodsInfo && Object.keys(goodsInfo).length">
-    <v-card class="carousel border-radius-8 white-bgc pa-5 d-flex" style="width: 100%;">
-      <!-- 图片展示-轮播 -->
-      <div style="width: 500px; height: 500px;">
-        <v-carousel show-arrows="hover" cycle :model-value="0" :hide-delimiters="!carouselHover" @mouseover="carouselHover = true" @mouseleave="carouselHover = false">
-          <v-carousel-item v-for="(imgUrl, i) in goodsInfo.sliderPicUrls" :key="'wareImg'+i" @click="null">
-            <v-img :src="imgUrl" :aspect-ratio="1" style="border-radius: 8px;"></v-img>
-          </v-carousel-item>
-        </v-carousel>
+  <div class="default-width py-5" v-if="state.goodsInfo && Object.keys(state.goodsInfo).length">
+    <v-card class="carousel border-radius-8 white-bgc pa-5" style="width: 100%;">
+      <div class=" d-flex">
+        <!-- 图片展示-轮播 -->
+        <div style="width: 500px; height: 500px;">
+          <div v-if="selectedSkuPicUrl" class="selectedSkuImgBox" @mouseover="showSelectedSkuImg = true" @mouseleave="showSelectedSkuImg = false">
+            <v-img :src="selectedSkuPicUrl" :aspect-ratio="1" style="border-radius: 8px;"></v-img>
+            <v-btn
+              v-show="showSelectedSkuImg"
+              size="x-small" 
+              class="close px-3"
+              :disabled="inputValue <= min || disabled" 
+              @click="selectedSkuPicUrl = ''"
+            >关闭</v-btn>
+          </div>
+          <v-carousel v-else show-arrows="hover" cycle :model-value="0" :hide-delimiters="!carouselHover" @mouseover="carouselHover = true" @mouseleave="carouselHover = false">
+            <v-carousel-item v-for="(imgUrl, i) in state.goodsInfo.sliderPicUrls" :key="'wareImg'+i" @click="null">
+              <v-img :src="imgUrl" :aspect-ratio="1" style="border-radius: 8px;"></v-img>
+            </v-carousel-item>
+          </v-carousel>
+        </div>
+        <!-- s-select-sku 商品属性选择 -->
+        <div style="flex: 1;" class="pl-10">
+          <!-- 大标题 -->
+          <div class="title-name">{{ state.goodsInfo?.name || '--' }}</div>
+          <!-- 小标题 -->
+          <div class="title-introduction">{{ state.goodsInfo?.introduction || '--' }}</div>
+          <!-- 价格 -->
+          <div class="prices my-5">
+            <div class="price mr-3"><span>¥</span>{{ selectedSkuPrice}}</div>
+            <div class="marketPrice" v-if="state.goodsInfo?.marketPrice">{{ selectedSkuMarketPrice}}</div>
+          </div>
+          <!-- 销量 -->
+          <div class="salesCount mb-5 parameterColor"><span class="l-s-10">已售</span>:{{ state.goodsInfo?.salesCount || 0 }}</div>
+          <!-- 属性选择组件 -->
+          <selectSku
+            v-if="showSelectSku"
+            class="mb-7"
+            :goodsInfo="state.goodsInfo"
+            @change="onSkuChange"
+            @buy="onBuy"
+          ></selectSku>
+        </div>
       </div>
-      <div style="flex: 1;" class="pl-10">
-        <!-- 大标题 -->
-        <div class="title-name">{{ goodsInfo?.name || '--' }}</div>
-        <!-- 小标题 -->
-        <div class="title-introduction">{{ goodsInfo?.introduction || '--' }}</div>
-        <!-- 价格 -->
-        <div class="prices my-5">
-          <div class="price mr-3"><span>¥</span>{{ calcPrice(goodsInfo?.price)}}</div>
-          <div class="marketPrice" v-if="goodsInfo?.marketPrice">{{ calcPrice(goodsInfo?.marketPrice)}}</div>
+    </v-card>
+    <!-- 详情描述 detail-content-card -->
+    <v-card class="carousel border-radius-8 white-bgc pa-5 mt-3" style="width: 100%;">
+      <div class="resume-header">
+        <div class="resume-title">
+          详情
         </div>
-        <!-- 销量 -->
-        <div class="salesCount mb-5 parameterColor"><span class="l-s-10">已售</span>:{{ goodsInfo?.salesCount || 0 }}</div>
-        <!-- 属性选择组件 -->
-        <selectSku v-if="skus?.length" :goodsInfo="goodsInfo" class="mb-7"></selectSku>
       </div>
+      <describe v-if="state.goodsInfo?.description" class="detail-content-selector" :content="state.goodsInfo.description"></describe>
     </v-card>
   </div>
 </template>
@@ -33,26 +61,87 @@
 defineOptions({name: 'goods-details'})
 import { getProductDetail } from '@/api/mall'
 import selectSku from './detailsComponents/s-select-sku.vue'
-import { ref } from 'vue'
+import describe from './detailsComponents/describe.vue'
+import { ref, reactive } from 'vue'
 import { useRouter } from 'vue-router'
+import Snackbar from '@/plugins/snackbar'
 
 const router = useRouter()
 const { id } = router.currentRoute.value.params
 
+const selectedSkuPicUrl = ref('')
+const selectedSkuPrice = ref('')
+const selectedSkuMarketPrice = ref('')
+const showSelectSku = ref(false)
 const carouselHover = ref(false)
-const goodsInfo = ref({})
-const skus = ref([])
+const showSelectedSkuImg = ref(false)
 // 获取商品详情
 const getData = async () => {
   const obj = await getProductDetail({ id })
+  if (!obj) return Snackbar.warning('未找到商品!')
+  //
+  // 加载到商品
   obj.sliderPicUrls = obj.sliderPicUrls || []
-  skus.value = obj.skus || []
-  goodsInfo.value = obj
-  // console.log('getProductDetail:', goodsInfo.value)
+  state.skeletonLoading = false;
+  state.goodsInfo = obj
+  showSelectSku.value = true
+  // // 加载是否收藏
+  // if (isLogin.value) {
+  //   FavoriteApi.isFavoriteExists(state.goodsId, 'goods').then((res) => {
+  //     if (res.code !== 0) {
+  //       return;
+  //     }
+  //     state.goodsInfo.favorite = res.data;
+  //   });
+  // }
 }
 getData()
 
-const calcPrice = (price) => { return price && (price-0) ? (price-0)/100 : '' }
+const calcPrice = (price) => { return price && (price-0) ? (price-0)/100 : '--' }
+
+const state = reactive({
+  goodsId: 0,
+  skeletonLoading: true, // SPU 加载中
+  goodsInfo: {}, // SPU 信息
+  showSelectSku: false, // 是否展示 SKU 选择弹窗
+  selectedSku: {}, // 选中的 SKU
+  settlementSku: {}, // 结算的 SKU:由于 selectedSku 不进行默认选中,所以初始使用结算价格最低的 SKU 作为基础展示
+  showModel: false, // 是否展示 Coupon 优惠劵的弹窗
+  couponInfo: [], // 可领取的 Coupon 优惠劵的列表
+  showActivityModel: false, // 【满减送/限时折扣】是否展示 Activity 营销活动的弹窗
+  rewardActivity: {}, // 【满减送】活动
+  activityList: [], // 【秒杀/拼团/砍价】可参与的 Activity 营销活动的列表
+});
+
+// 规格变更
+function onSkuChange(e) {
+  state.selectedSku = e;
+  state.settlementSku = e;
+  // console.log('onSkuChange:', e)
+  selectedSkuPicUrl.value = state.selectedSku?.picUrl || ''
+  if (selectedSkuPicUrl.value) showSelectedSkuImg.value = true
+  selectedSkuPrice.value = calcPrice(state.selectedSku?.price || state.goodsInfo.price)
+  selectedSkuMarketPrice.value = calcPrice(state.selectedSku?.marketPrice || state.goodsInfo.marketPrice)
+}
+
+const onBuy = async () => {
+  Snackbar.warning('购买功能暂未开放,敬请期待!')
+}
+// function onBuy(sku) {
+//   sheep.$router.go('/pages/order/confirm', {
+//     data: JSON.stringify({
+//       order_type: 'goods',
+//       combinationActivityId: state.activity.id,
+//       combinationHeadId: state.combinationHeadId,
+//       items: [
+//         {
+//           skuId: sku.id,
+//           count: sku.count,
+//         },
+//       ],
+//     }),
+//   });
+// }
 
 </script>
 <style lang="scss" scoped>
@@ -93,4 +182,12 @@ const calcPrice = (price) => { return price && (price-0) ? (price-0)/100 : '' }
   line-height: 34px;
   text-decoration: line-through;
 }
+.selectedSkuImgBox {
+  position: relative;
+  .close {
+    position: absolute;
+    bottom: 10px;
+    right: 10px;
+  }
+}
 </style>

+ 19 - 0
src/views/mall/components/detailsComponents/describe.vue

@@ -0,0 +1,19 @@
+<!--  -->
+<template>
+  <div class="detailsBox">
+    <span v-if="props.content.indexOf('style')" v-html="props.content"></span>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'goods-details-describe'})
+const props = defineProps({
+  content: {
+    type: String,
+    default: '',
+  },
+});
+console.log('content:', props.content)
+</script>
+<style lang="scss" scoped>
+</style>

+ 22 - 11
src/views/mall/components/detailsComponents/s-select-sku.vue

@@ -86,6 +86,7 @@
             :min="1"
             :max="state.selectedSku.stock"
             :step="1"
+            ref="selectSkuRef"
             v-model="state.selectedSku.goods_num"
             @change="onNumberChange($event)"
           />
@@ -103,17 +104,25 @@
 defineOptions({name: 'wares-s-select-sku'})
 import Snackbar from '@/plugins/snackbar'
 import suNumberBox from '@/components/FormUI/su-number-box/su-number-box.vue'
-import { computed, reactive, watch } from 'vue'
+import { computed, reactive, watch, ref, onMounted } from 'vue'
 import { convertProductPropertyList } from '@/views/mall/utils'
 
-  const emits = defineEmits(['change', 'addCart', 'buy', 'close']);
-  const props = defineProps({
-    goodsInfo: {
-      type: Object,
-      default() {},
-    },
-  });
-
+const emits = defineEmits(['change', 'addCart', 'buy', 'close']);
+const props = defineProps({
+  goodsInfo: {
+    type: Object,
+    default() {},
+  },
+});
+
+  const totalStock = ref(props.goodsInfo?.stock-0 || 0)
+  function setTotalStock() {
+    if (selectSkuRef.value) selectSkuRef.value.stock = totalStock.value
+  }
+  onMounted(() => {
+    setTotalStock()
+  })
+  const selectSkuRef = ref()
   const state = reactive({
     selectedSku: {}, // 选中的 SKU
     currentPropertyArray: [], // 当前选中的属性,实际是个 Map。key 是 property 编号,value 是 value 编号
@@ -132,6 +141,8 @@ import { convertProductPropertyList } from '@/views/mall/utils'
   watch(
     () => state.selectedSku,
     (newVal) => {
+      if (newVal?.stock) selectSkuRef.value.stock = newVal.stock
+      else setTotalStock()
       emits('change', newVal);
     },
     {
@@ -163,11 +174,11 @@ import { convertProductPropertyList } from '@/views/mall/utils'
 
   // 立即购买
   function onBuy() {
-    if (state.selectedSku.id <= 0) {
+    if (!state?.selectedSku?.id || state.selectedSku.id <= 0) {
       Snackbar.warning('请选择商品规格')
       return;
     }
-    if (state.selectedSku.stock <= 0) {
+    if (!state?.selectedSku?.stock || state.selectedSku.stock <= 0) {
       Snackbar.warning('库存不足')
       return;
     }