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

fix(goods): 统一多规格首项与主商品语义

- 管理端回写首规格名称及价库字段,兼容 goodsStyle 多规格标识
- 商城按当前规格展示名称并校验库存,列表多规格统一进入详情选择
- 团购与秒杀配置明确首规格使用主商品 ID
shizhongqi 3 дней назад
Родитель
Сommit
9630923f47

+ 11 - 1
hdApp/src/admin/goods/add.vue

@@ -1,3 +1,8 @@
+<!--
+  花店后台新增商品
+  谁用:hdApp 管理端创建花束
+  解决问题:单规格写主记录;多规格主记录承载首规格(specName/price/stock),其余规格提交 specList 由后端拆子行
+-->
 <template>
 <template>
   <view class="app-content">
   <view class="app-content">
     
     
@@ -426,7 +431,7 @@ export default {
       });
       });
     },
     },
     toggleSpecs(e) {
     toggleSpecs(e) {
-      this.form.specEnabled=e.detail.value?1:0;
+      this.form.specEnabled = e.detail.value ? 1 : 0;
       if(this.form.specEnabled && !this.form.specList.length) {
       if(this.form.specEnabled && !this.form.specList.length) {
         this.form.specList.push(this.createSpecData(this.form))
         this.form.specList.push(this.createSpecData(this.form))
       }
       }
@@ -528,9 +533,13 @@ export default {
       }
       }
       return '';
       return '';
     },
     },
+    /**
+     * 提交前规范化规格:多规格把首规格价库和名称回写主表单字段,specList 仍整表提交由后端拆主/子
+     */
     normalizeSpecSubmitData(formData) {
     normalizeSpecSubmitData(formData) {
       if (formData.specEnabled != 1) {
       if (formData.specEnabled != 1) {
         formData.specList = [];
         formData.specList = [];
+        formData.specName = '';
         return formData;
         return formData;
       }
       }
       formData.specList = formData.specList.map(item => {
       formData.specList = formData.specList.map(item => {
@@ -549,6 +558,7 @@ export default {
         return spec;
         return spec;
       });
       });
       const firstSpec = formData.specList[0];
       const firstSpec = formData.specList[0];
+      formData.specName = firstSpec.specName || '';
       formData.priceType = String(firstSpec.priceType);
       formData.priceType = String(firstSpec.priceType);
       formData.price = firstSpec.price;
       formData.price = firstSpec.price;
       formData.stockSet = String(firstSpec.stockSet);
       formData.stockSet = String(firstSpec.stockSet);

+ 12 - 1
hdApp/src/admin/goods/detail.vue

@@ -1,3 +1,8 @@
+<!--
+  花店后台编辑商品
+  谁用:hdApp 管理端修改花束
+  解决问题:详情 specList 首项即主记录自身;提交时把首规格名称回写主表单 specName
+-->
 <template>
 <template>
   <view class="app-content">
   <view class="app-content">
 
 
@@ -441,7 +446,8 @@ export default {
         this.form.useCaseIdList = Array.isArray(useCaseIdList)
         this.form.useCaseIdList = Array.isArray(useCaseIdList)
           ? useCaseIdList.map(id => Number(id)).filter(id => id > 0)
           ? useCaseIdList.map(id => Number(id)).filter(id => id > 0)
           : [];
           : [];
-        this.form.specEnabled = Number(res.data.specEnabled || 0);
+        this.form.specEnabled = Number(res.data.specEnabled || res.data.goodsStyle || 0);
+        // 详情 specList 首项即主记录自身(id=主商品id),createSpecData 直接兼容
         this.form.specList = (res.data.specList || []).map(item => this.createSpecData(item));
         this.form.specList = (res.data.specList || []).map(item => this.createSpecData(item));
 
 
         // 设置价格类型
         // 设置价格类型
@@ -683,9 +689,13 @@ export default {
       }
       }
       return '';
       return '';
     },
     },
+    /**
+     * 提交前规范化规格:多规格把首规格价库和名称回写主表单字段,specList 仍整表提交由后端拆主/子
+     */
     normalizeSpecSubmitData(formData) {
     normalizeSpecSubmitData(formData) {
       if (formData.specEnabled != 1) {
       if (formData.specEnabled != 1) {
         formData.specList = [];
         formData.specList = [];
+        formData.specName = '';
         return formData;
         return formData;
       }
       }
       formData.specList = formData.specList.map(item => {
       formData.specList = formData.specList.map(item => {
@@ -704,6 +714,7 @@ export default {
         return spec;
         return spec;
       });
       });
       const firstSpec = formData.specList[0];
       const firstSpec = formData.specList[0];
+      formData.specName = firstSpec.specName || '';
       formData.priceType = String(firstSpec.priceType);
       formData.priceType = String(firstSpec.priceType);
       formData.price = firstSpec.price;
       formData.price = firstSpec.price;
       formData.stockSet = String(firstSpec.stockSet);
       formData.stockSet = String(firstSpec.stockSet);

+ 3 - 2
hdApp/src/admin/homePageConfig/groupBuyGoodsEdit.vue

@@ -1,7 +1,8 @@
 <!--
 <!--
   团购商品编辑页
   团购商品编辑页
   从团购配置页跳转,单选活动商品并填写团购价/成团人数/库存等;通过 groupBuyDraft 与列表页同步。
   从团购配置页跳转,单选活动商品并填写团购价/成团人数/库存等;通过 groupBuyDraft 与列表页同步。
-  单规格:一套表单;多规格:按规格数动态生成多套表单,保存时每规格各写一条团购商品(goodsId 用子规格 id)。
+  单规格:一套表单;多规格:按 specList 动态生成多套表单,保存时每规格各写一条团购商品。
+  多规格 goodsId:首规格用主商品 id,其余用子规格 id。
   自动退款不再可配,统一默认开启(活动结束未拼成自动退款、拼成自动退差价)。
   自动退款不再可配,统一默认开启(活动结束未拼成自动退款、拼成自动退差价)。
 -->
 -->
 <template>
 <template>
@@ -313,7 +314,7 @@ export default {
         this.goodsBase.name = data.name || this.goodsBase.name
         this.goodsBase.name = data.name || this.goodsBase.name
         this.goodsBase.cover = cover
         this.goodsBase.cover = cover
         const specList = Array.isArray(data.specList) ? data.specList : []
         const specList = Array.isArray(data.specList) ? data.specList : []
-        // 启用多规格且存在子规格:一套规格一张填写表单
+        // 启用多规格:specList 首项是主记录自身(id=主商品id),其后是子规格
         if (Number(data.specEnabled) === 1 && specList.length > 0) {
         if (Number(data.specEnabled) === 1 && specList.length > 0) {
           this.isMultiSpec = true
           this.isMultiSpec = true
           this.specForms = specList.map((spec) => ({
           this.specForms = specList.map((spec) => ({

+ 1 - 1
hdApp/src/admin/homePageConfig/seckill.vue

@@ -222,7 +222,7 @@ export default {
     },
     },
     /** 将接口数据规范为表单结构 */
     /** 将接口数据规范为表单结构 */
     normalizeForm(data) {
     normalizeForm(data) {
-      // specName 仅草稿/编辑页使用;多规格保存时每规格一条,goodsId 为子规格 id
+      // specName 仅草稿/编辑页使用;多规格保存时每规格一条,首规格 goodsId 为主商品 id
       const goods = Array.isArray(data.goods) ? data.goods.map((g) => ({
       const goods = Array.isArray(data.goods) ? data.goods.map((g) => ({
         goodsId: g.goodsId || 0,
         goodsId: g.goodsId || 0,
         price: g.price || '',
         price: g.price || '',

+ 4 - 3
hdApp/src/admin/homePageConfig/seckillGoodsEdit.vue

@@ -1,7 +1,8 @@
 <!--
 <!--
   秒杀商品编辑页
   秒杀商品编辑页
   从秒杀配置页跳转,单选活动商品并填写秒杀价/库存/限购;通过 seckillDraft 与列表页同步。
   从秒杀配置页跳转,单选活动商品并填写秒杀价/库存/限购;通过 seckillDraft 与列表页同步。
-  单规格:一套表单;多规格:按规格数动态生成多套表单,保存时每规格各写一条秒杀商品(goodsId 用子规格 id)。
+  单规格:一套表单;多规格:按 specList 动态生成多套表单,保存时每规格各写一条秒杀商品。
+  多规格 goodsId:首规格用主商品 id,其余用子规格 id。
 -->
 -->
 <template>
 <template>
   <view class="page-container">
   <view class="page-container">
@@ -239,7 +240,7 @@ export default {
         this.goodsBase.name = data.name || this.goodsBase.name
         this.goodsBase.name = data.name || this.goodsBase.name
         this.goodsBase.cover = cover
         this.goodsBase.cover = cover
         const specList = Array.isArray(data.specList) ? data.specList : []
         const specList = Array.isArray(data.specList) ? data.specList : []
-        // 启用多规格且存在子规格:一套规格一张填写表单
+        // 启用多规格:specList 首项是主记录自身(id=主商品id),其后是子规格
         if (Number(data.specEnabled) === 1 && specList.length > 0) {
         if (Number(data.specEnabled) === 1 && specList.length > 0) {
           this.isMultiSpec = true
           this.isMultiSpec = true
           this.specForms = specList.map((spec) => ({
           this.specForms = specList.map((spec) => ({
@@ -272,7 +273,7 @@ export default {
     },
     },
     /**
     /**
      * 校验全部规格表单后写回 seckillDraft
      * 校验全部规格表单后写回 seckillDraft
-     * 多规格保存为多条(每规格 goodsId 为子商品 id,后端按该 id 校验实际库存)
+     * 多规格保存为多条:首规格 goodsId 为主商品 id,其余为子规格 id
      */
      */
     saveFn() {
     saveFn() {
       if (!this.goodsBase.masterGoodsId && !(this.specForms[0] && this.specForms[0].goodsId)) {
       if (!this.goodsBase.masterGoodsId && !(this.specForms[0] && this.specForms[0].goodsId)) {

+ 2 - 2
mallApp/src/components/home/goodsSection.vue

@@ -126,10 +126,10 @@ export default {
     goDetail(g) {
     goDetail(g) {
       this.pageTo({ url: `/pages/goods/detail?id=${g.id}&account=${this.account}&hdId=${this.hdId}` })
       this.pageTo({ url: `/pages/goods/detail?id=${g.id}&account=${this.account}&hdId=${this.hdId}` })
     },
     },
-    /** 单规格直接加购,多规格跳转详情选规格;无价格商品拦截并提示询价 */
+    /** 单规格直接加购,多规格(goodsStyle=1)跳转详情选规格;无价格商品拦截并提示询价 */
     handleCartClick(g) {
     handleCartClick(g) {
       if (!g || !g.id) return
       if (!g || !g.id) return
-      if (Number(g.specEnabled) === 1) {
+      if (Number(g.goodsStyle) === 1 || Number(g.specEnabled) === 1) {
         this.goDetail(g)
         this.goDetail(g)
         return
         return
       }
       }

+ 10 - 13
mallApp/src/pages/goods/components/sel-popup.vue

@@ -132,10 +132,12 @@ export default {
     maxStock() {
     maxStock() {
       const spec = this.info.specEnabled == 1 && this.info.specList ? this.info.specList[this.selIndex] : this.info;
       const spec = this.info.specEnabled == 1 && this.info.specList ? this.info.specList[this.selIndex] : this.info;
       let max = Number(spec && spec.stockSet == 1 ? spec.stock : 9999) || 9999;
       let max = Number(spec && spec.stockSet == 1 ? spec.stock : 9999) || 9999;
-      const goodsStock = Number(this.info && this.info.stock);
-      // 商品本身有库存时,数量上限不超过 data.stock(未开启库存限制时后端会写成 9999)
-      if (goodsStock > 0) {
-        max = Math.min(max, goodsStock);
+      // 多规格按当前规格库存限制;主记录承载首规格后,不能再用主记录库存截断其他规格
+      if (this.info.specEnabled != 1) {
+        const goodsStock = Number(this.info && this.info.stock);
+        if (goodsStock > 0) {
+          max = Math.min(max, goodsStock);
+        }
       }
       }
       if (this.isSeckill) {
       if (this.isSeckill) {
         const actStock = Number(this.activityStock) || 0;
         const actStock = Number(this.activityStock) || 0;
@@ -212,19 +214,14 @@ export default {
         return false;
         return false;
       }
       }
       const sale = spec || this.info || {};
       const sale = spec || this.info || {};
-      const goodsStock = Number(this.info && this.info.stock);
-      const specStock = Number(sale.stock);
+      const saleStock = Number(sale.stock);
       const stockSet = Number(sale.stockSet != null ? sale.stockSet : (this.info && this.info.stockSet));
       const stockSet = Number(sale.stockSet != null ? sale.stockSet : (this.info && this.info.stockSet));
-      // 商品库存不足,或本次数量超过库存
-      if (!(goodsStock > 0) || (stockSet === 1 && !(specStock > 0))) {
-        this.$msg('库存不足哦');
-        return false;
-      }
-      if (stockSet === 1 && buyNum > specStock) {
+      // 按当前售卖 SKU 校验库存:多规格时不能拿主记录(首规格)库存去卡其他规格
+      if (!(saleStock > 0) || (stockSet === 1 && buyNum > saleStock)) {
         this.$msg('库存不足哦');
         this.$msg('库存不足哦');
         return false;
         return false;
       }
       }
-      if (goodsStock > 0 && buyNum > goodsStock) {
+      if (stockSet !== 1 && buyNum > saleStock) {
         this.$msg('库存不足哦');
         this.$msg('库存不足哦');
         return false;
         return false;
       }
       }

+ 22 - 2
mallApp/src/pages/goods/detail.vue

@@ -1,3 +1,8 @@
+<!--
+  商城商品详情
+  谁用:mallApp 顾客查看花束并加购/购买
+  解决问题:多规格 specList 首项即主记录可售规格,选中后 specGoodsId 可等于主商品 id
+-->
 <template>
 <template>
   <view class="page-container">
   <view class="page-container">
     <scroll-view class="app-content" scroll-y="true" @scroll="scrollFn">
     <scroll-view class="app-content" scroll-y="true" @scroll="scrollFn">
@@ -27,9 +32,9 @@
           <view class="tui-pro-title-wrap">
           <view class="tui-pro-title-wrap">
             <view class="tui-pro-title app-size-32">{{ data.name }}</view>
             <view class="tui-pro-title app-size-32">{{ data.name }}</view>
             <view
             <view
-              v-if="data.masterId && data.masterId !== '0'"
+              v-if="displaySpecName"
               class="shop-spec app-color-3"
               class="shop-spec app-color-3"
-            >{{ data.specName }}</view>
+            >{{ displaySpecName }}</view>
           </view>
           </view>
           <button open-type="share" class="share-button" @click="shareFn">
           <button open-type="share" class="share-button" @click="shareFn">
             <i class="iconfont iconfenxiang"></i>
             <i class="iconfont iconfenxiang"></i>
@@ -258,6 +263,21 @@ export default {
       });
       });
       return count;
       return count;
     },
     },
+    /**
+     * 标题下规格名:多规格展示当前选中规格(默认首规格),规格 SKU 详情展示自身 specName
+     */
+    displaySpecName() {
+      if (this.data.selectedSpecName) {
+        return this.data.selectedSpecName;
+      }
+      if (Number(this.data.specEnabled) === 1 && this.data.specList && this.data.specList[0]) {
+        return this.data.specList[0].specName || '';
+      }
+      if (this.data.masterId && String(this.data.masterId) !== '0') {
+        return this.data.specName || '';
+      }
+      return '';
+    },
   },
   },
   methods: {
   methods: {
     /** 按登录态驱动 wangCg:0 未登录弹层 / 1 已登录关闭 */
     /** 按登录态驱动 wangCg:0 未登录弹层 / 1 已登录关闭 */

+ 3 - 3
mallApp/src/pages/goods/list.vue

@@ -568,13 +568,13 @@ export default {
       })
       })
     },
     },
     /**
     /**
-     * 列表加购:单规格(普通)直接加入购物车;多规格跳转详情选规格后再加购
-     * @param {Object} item 列表商品行(含 specEnabled / price)
+     * 列表加购:单规格直接加入购物车;多规格(goodsStyle=1)跳转详情选规格后再加购
+     * @param {Object} item 列表商品行(含 goodsStyle / specEnabled / price)
      */
      */
     handleCartClick(item) {
     handleCartClick(item) {
       if (!item || !item.id) return
       if (!item || !item.id) return
       // 多规格:进入详情页选择规格
       // 多规格:进入详情页选择规格
-      if (Number(item.specEnabled) === 1) {
+      if (Number(item.goodsStyle) === 1 || Number(item.specEnabled) === 1) {
         this.goDetail(item)
         this.goDetail(item)
         return
         return
       }
       }

+ 3 - 3
mallApp/src/pages/goods/section-list.vue

@@ -224,13 +224,13 @@ export default {
       this.pageTo({ url })
       this.pageTo({ url })
     },
     },
     /**
     /**
-     * 列表加购:单规格(普通)直接加入购物车;多规格跳转详情选规格后再加购
-     * @param {Object} item 列表商品行(含 specEnabled / price)
+     * 列表加购:单规格直接加入购物车;多规格(goodsStyle=1)跳转详情选规格后再加购
+     * @param {Object} item 列表商品行(含 goodsStyle / specEnabled / price)
      */
      */
     handleCartClick(item) {
     handleCartClick(item) {
       if (!item || !(item.id || item.goodsId)) return
       if (!item || !(item.id || item.goodsId)) return
       // 多规格:进入详情页选择规格
       // 多规格:进入详情页选择规格
-      if (Number(item.specEnabled) === 1) {
+      if (Number(item.goodsStyle) === 1 || Number(item.specEnabled) === 1) {
         this.goDetail(item)
         this.goDetail(item)
         return
         return
       }
       }

+ 2 - 2
mallApp/src/pages/home/shop-category.vue

@@ -870,12 +870,12 @@ export default {
         }
         }
       })
       })
     },
     },
-    /** 花束加购:单规格直接加入购物车,多规格跳转详情选规格 */
+    /** 花束加购:单规格直接加入购物车,多规格(goodsStyle=1)跳转详情选规格 */
     handleBouquetAddToCart(item) {
     handleBouquetAddToCart(item) {
       if (!item || !item.id) {
       if (!item || !item.id) {
         return
         return
       }
       }
-      if (Number(item.specEnabled) === 1) {
+      if (Number(item.goodsStyle) === 1 || Number(item.specEnabled) === 1) {
         this.toBouquetDetail(item)
         this.toBouquetDetail(item)
         return
         return
       }
       }