Просмотр исходного кода

fix(mallApp): 修正规格商品秒杀购买展示

- 区分主商品可选规格与当前规格 SKU,避免详情页和购买弹层重复展示规格\n- 规格 SKU 加购与购买使用当前商品库存及规格数据,避免误取主商品首规格\n- 避免详情加载阶段闪现询价按钮,并统一购买按钮视觉样式
shizhongqi 18 часов назад
Родитель
Сommit
7628783624

+ 18 - 4
mallApp/src/pages/goods/components/buy-foot.vue

@@ -3,7 +3,8 @@
   用途:左侧购物车/咨询入口 + 右侧加购/购买(或询价)
   使用方:pages/goods/detail
   加购/购买点击时先拦商品库存与秒杀活动库存,不足则不打开选规格弹层;
-  无价格商品若处于秒杀进行中,右侧展示加购/购买而不是询价
+  无价格商品若处于秒杀进行中,右侧展示加购/购买而不是询价;
+  详情未返回前右侧先展示加购/购买,避免加载阶段闪「询价」
 -->
 <template>
 	<view class="app-footer">
@@ -59,13 +60,26 @@ export default {
         activityStock: {
             type: Number,
             default: 0
+        },
+        /**
+         * 商品详情是否已返回。
+         * 未就绪时父组件传入的 priceType 在小程序里常变成 0,会误判成询价;
+         * 因此加载完成前默认走加购/购买。
+         */
+        goodsReady: {
+            type: Boolean,
+            default: false
         }
     },
     computed: {
         /**
-         * 是否可直接加购/购买:有价格,或秒杀进行中(无价格商品也用秒杀价,不走询价)
+         * 是否可直接加购/购买:有价格,或秒杀进行中(无价格商品也用秒杀价,不走询价)。
+         * 详情未返回前先按可购买展示,避免加载闪询价。
          */
         canDirectBuy() {
+            if (!this.goodsReady) {
+                return true
+            }
             return Number(this.priceType) === 1 || this.isSeckill
         },
         /** 角标文案:超过 99 显示 99+,与店铺底部 tab 一致 */
@@ -218,8 +232,8 @@ export default {
             border-radius: 8upx;
             
             &.red {
-                background: #FF0036 !important;
-                background-color: #FF0036 !important;
+                background: #FF4D6D !important;
+                background-color: #FF4D6D !important;
                 color: #FFFFFF !important;
             }
 

+ 24 - 11
mallApp/src/pages/goods/components/sel-popup.vue

@@ -1,7 +1,8 @@
 <!--
   商品详情选规格/数量弹层
   用途:加购或立即购买前选择规格与数量;秒杀会按限购、活动库存收紧可选上限;
-  无价格商品作为秒杀时按活动价允许加购/购买,不拦截询价
+  无价格商品作为秒杀时按活动价允许加购/购买,不拦截询价;
+  秒杀挂在某个规格 SKU 上时只展示该规格名(不可切换),不与主商品 specList 叠两套 UI
   使用方:pages/goods/detail
 -->
 <template>
@@ -11,7 +12,8 @@
       <view class="tui-popup-box">
         <scroll-view scroll-y class="tui-popup-scroll">
           <view class="tui-scrolldiv-box">
-            <view v-if="info.specEnabled == 1" class="spec-list">
+            <!-- 主商品多规格:可切换;秒杀挂在某个规格 SKU 上时走下方只读展示,避免两套规格区叠出 -->
+            <view v-if="showSwitchableSpec" class="spec-list">
               <view class="tui-attr-title">规格</view>
               <button v-for="(spec,index) in info.specList" :key="spec.id" class="spec-button" :class="{active:selIndex===index}" @click="selectSpec(index)">{{spec.specName}}</button>
             </view>
@@ -39,10 +41,10 @@
           </view>
         </scroll-view>
         <view class="tui-popup-btn">
-          <button v-if="mode === 'cart'" class="button-com red enhanced-btn" @click="addCartFn">
+          <button v-if="mode === 'cart'" class="button-com enhanced-btn" @click="addCartFn">
             加入购物车
           </button>
-          <button v-else class="button-com red enhanced-btn" @click="nextFn">
+          <button v-else class="button-com enhanced-btn" @click="nextFn">
             下一步
           </button>
         </view>
@@ -132,11 +134,19 @@ export default {
     /**
      * 是否为挂在主商品下的规格 SKU(masterId/master 不为 0)
      * 与主商品开启 specEnabled 选规格不同:此时详情页就是某一个规格本身
+     * 秒杀常挂在某个规格上,接口仍可能带回主商品 specEnabled/specList,两套 UI 必须互斥
      */
     isMasterSpecSku() {
       const master = this.info.masterId != null ? this.info.masterId : this.info.master;
       return master != null && String(master) !== '0' && Number(master) > 0;
     },
+    /**
+     * 是否展示可切换规格列表
+     * 仅主商品多规格展示;当前页已是某个规格 SKU 时不展示,避免和只读规格名叠两套
+     */
+    showSwitchableSpec() {
+      return this.info.specEnabled == 1 && !this.isMasterSpecSku;
+    },
     /** 主商品 id:规格 SKU 场景下用于购物车 goodsId */
     masterGoodsId() {
       if (!this.isMasterSpecSku) return 0;
@@ -144,10 +154,11 @@ export default {
       return Number(master) || 0;
     },
     maxStock() {
-      const spec = this.info.specEnabled == 1 && this.info.specList ? this.info.specList[this.selIndex] : this.info;
+      // 规格 SKU 详情(含秒杀挂在该规格上)按当前商品库存,不用主商品 specList
+      const spec = this.showSwitchableSpec && this.info.specList ? this.info.specList[this.selIndex] : this.info;
       let max = Number(spec && spec.stockSet == 1 ? spec.stock : 9999) || 9999;
       // 多规格按当前规格库存限制;主记录承载首规格后,不能再用主记录库存截断其他规格
-      if (this.info.specEnabled != 1) {
+      if (!this.showSwitchableSpec) {
         const goodsStock = Number(this.info && this.info.stock);
         if (goodsStock > 0) {
           max = Math.min(max, goodsStock);
@@ -191,12 +202,14 @@ export default {
       };
     },
     getSelectedSpec() {
-      let spec = this.info.specEnabled == 1 ? this.info.specList[this.selIndex] : null;
-      // 详情本身是规格 SKU:没有 specList 可选,直接把当前商品当作规格带上
-      if (!spec && this.isMasterSpecSku) {
+      let spec = null;
+      // 详情本身就是规格 SKU:以当前商品为准,避免秒杀规格仍带回 specList 时误用首规格
+      if (this.isMasterSpecSku) {
         spec = this.buildMasterSpecPayload();
+      } else if (this.info.specEnabled == 1) {
+        spec = this.info.specList[this.selIndex];
       }
-      if (this.info.specEnabled == 1 && !spec) {
+      if (this.showSwitchableSpec && !spec) {
         this.$msg('请选择规格');
         return null;
       }
@@ -349,7 +362,7 @@ export default {
       padding: 0;
 
       &.enhanced-btn {
-        background: linear-gradient(135deg, #ff4757 0%, #e41f19 100%);
+        background: #FF4D6D;
         border: none;
         color: #fff;
       }

+ 36 - 3
mallApp/src/pages/goods/detail.vue

@@ -3,7 +3,8 @@
   谁用:mallApp 顾客查看花束并加购/购买
   解决问题:多规格 specList 首项即主记录可售规格,选中后 specGoodsId 可等于主商品 id;
   顶部信息卡按标题/分享、价格已售、规格选择、服务保障排布,秒杀条保留在价格上方;
-  标题:多规格展示当前规格名,单规格展示商品名称;多规格卡片嵌在信息卡内;
+  标题:多规格展示当前规格名,单规格展示商品名称;主商品多规格卡片嵌在信息卡内;
+  秒杀挂在某个规格 SKU 上时不展示可切换规格,只保留该规格只读名;
   无价格商品作为秒杀时展示秒杀价,底部走加购/购买而不是询价;
   未登录可浏览详情:仅点购物车/咨询/加入购物车/购买(含询价)时弹 wangCg
 -->
@@ -48,9 +49,9 @@
           </view>
           <text class="sold-text">已售 {{ displaySold }}</text>
         </view>
-        <!-- 多规格嵌在信息卡内:切规格同步价格/库存与顶部轮播图 -->
+        <!-- 主商品多规格:切规格同步价格/库存与顶部轮播图;规格 SKU(含秒杀挂在该规格上)不展示,避免和弹层只读规格叠两套 -->
         <view
-          v-if="data.specEnabled == 1 && data.specList && data.specList.length"
+          v-if="showSwitchableSpec"
           class="spec-module"
         >
           <view class="spec-module-tit">花束规格</view>
@@ -158,6 +159,7 @@
       :cart-count="cartBadgeCount"
       :is-seckill="isSeckillActive"
       :activity-stock="activityStock"
+      :goods-ready="hasGoodsDetail"
       @goChat="goChat"
     />
     <!-- 按需弹出:仅购物车/咨询/加购/购买触发,浏览详情默认不弹 -->
@@ -294,6 +296,13 @@ export default {
     showNumericPrice() {
       return Number(this.data.priceType) === 1 || this.isSeckillActive;
     },
+    /**
+     * 商品详情是否已返回(有商品 id)。
+     * 给底部栏用:未返回前先展示加购/购买,避免 priceType 为空时闪「询价」。
+     */
+    hasGoodsDetail() {
+      return !!(this.data && this.data.id);
+    },
     /** 底部购物车角标:扎数 + 支数(与分类页/首页 tab 统计口径一致) */
     cartBadgeCount() {
       if (!this.selectList || !this.selectList.length) {
@@ -318,12 +327,36 @@ export default {
       });
       return count;
     },
+    /**
+     * 是否为挂在主商品下的规格 SKU(masterId/master 不为 0)
+     * 秒杀常挂在某个规格上,接口仍可能带回主商品 specEnabled/specList
+     */
+    isMasterSpecSku() {
+      const goods = this.data || {};
+      const master = goods.masterId != null ? goods.masterId : goods.master;
+      return master != null && String(master) !== '0' && Number(master) > 0;
+    },
+    /**
+     * 是否展示可切换规格卡片
+     * 仅主商品多规格展示;当前页已是某个规格 SKU 时不展示
+     */
+    showSwitchableSpec() {
+      const goods = this.data || {};
+      return !this.isMasterSpecSku
+        && Number(goods.specEnabled) === 1
+        && Array.isArray(goods.specList)
+        && goods.specList.length > 0;
+    },
     /**
      * 顶部标题文案:多规格展示当前选中规格名(默认首规格),否则展示商品名称
      */
     displayTitle() {
       const goods = this.data || {};
       const specList = goods.specList || [];
+      // 当前页就是某个规格 SKU:用该规格名作标题,不用主商品 specList 首项
+      if (this.isMasterSpecSku && goods.specName) {
+        return goods.specName;
+      }
       // 多规格:切规格后用 selectedSpecName,初始用当前下标对应规格
       if (Number(goods.specEnabled) === 1 && specList.length) {
         const selected = goods.selectedSpecName