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

feat(home): 优化首页商品模块交互

- 支持活动商品选择页单选模式返回商品详情,便于秒杀和团购编辑回填

- 首页商品模块更多跳转模块商品列表,并在加购后刷新底部购物车角标
shizhongqi 2 недель назад
Родитель
Сommit
ef29c36bd0

+ 77 - 11
hdApp/src/admin/goods/ad-goods-select.vue

@@ -1,6 +1,6 @@
 <!--
-  分类广告 - 商品多选
-  用途:从花束商品中勾选关联商品,返回商品 id 列表给 category-ad
+  分类广告 / 活动商品选择
+  用途:从花束商品中勾选关联商品;默认多选返回 goodsIds,单选(mode=single)时同时返回 goodsList 详情
   基于 manage 花束面板精简:无打标签/库存/编辑/更多/新增,无花材标签筛选
 -->
 <template>
@@ -85,8 +85,12 @@ export default {
   data() {
     return {
       slotIndex: 0,
+      // multi | single;团购/秒杀等活动商品编辑走 single
+      mode: 'multi',
       // 用对象存选中态,小程序端 $set 比数组 splice 更易触发视图更新
       selectedMap: {},
+      // id -> 商品详情快照,供 confirm 时组装 goodsList
+      selectedGoodsMap: {},
       tabbar: [],
       currentTab: 0,
       catId: '',
@@ -118,19 +122,52 @@ export default {
       this.applyPageOptions(this.option || {});
       this.ensureLoadCategories();
     },
-    /** 解析 slotIndex、已选商品 id,可重复调用但 selectedIds 只回填一次 */
+    /** 解析 mode / slotIndex / 已选商品 id,可重复调用但 selectedIds 只回填一次 */
     applyPageOptions(options) {
+      if (options.mode === 'single') {
+        this.mode = 'single';
+      }
       if (options.slotIndex !== undefined && options.slotIndex !== '') {
         this.slotIndex = parseInt(options.slotIndex || 0, 10);
       }
       if (!this.selectedIdsParsed && options.selectedIds) {
         this.selectedIdsParsed = true;
-        String(options.selectedIds)
+        let ids = String(options.selectedIds)
           .split(',')
-          .filter(Boolean)
-          .forEach((id) => {
-            this.$set(this.selectedMap, String(id), true);
-          });
+          .filter(Boolean);
+        if (this.mode === 'single') {
+          ids = ids.slice(0, 1);
+        }
+        ids.forEach((id) => {
+          this.$set(this.selectedMap, String(id), true);
+        });
+      }
+    },
+    /** 组装回传给编辑页的商品结构(cover 兼容 smallCover) */
+    toGoodsPayload(goods) {
+      return {
+        id: goods.id,
+        name: goods.name || '',
+        cover: goods.cover || goods.smallCover || '',
+        price: goods.price || 0,
+        stock: goods.stock || 0
+      };
+    },
+    /** 清空当前勾选(单选切换时用) */
+    clearSelected() {
+      Object.keys(this.selectedMap).forEach((key) => {
+        this.$delete(this.selectedMap, key);
+      });
+      Object.keys(this.selectedGoodsMap).forEach((key) => {
+        this.$delete(this.selectedGoodsMap, key);
+      });
+    },
+    /** 按 id 从当前列表取商品并缓存详情 */
+    cacheSelectedGoods(id) {
+      const key = String(id);
+      const goods = (this.list.data || []).find((item) => String(item.id) === key);
+      if (goods) {
+        this.$set(this.selectedGoodsMap, key, this.toGoodsPayload(goods));
       }
     },
     /** 避免 init / onLoad 重复请求分类列表 */
@@ -163,6 +200,12 @@ export default {
         requestType: 'goodsList'
       }).then((res) => {
         this.completes(res);
+        // 列表刷新后,把已勾选但尚无详情的商品补齐(含路由预选)
+        Object.keys(this.selectedMap).forEach((id) => {
+          if (!this.selectedGoodsMap[id]) {
+            this.cacheSelectedGoods(id);
+          }
+        });
       });
     },
     refreshGoods() {
@@ -216,14 +259,37 @@ export default {
       const key = String(id);
       if (this.selectedMap[key]) {
         this.$delete(this.selectedMap, key);
-      } else {
-        this.$set(this.selectedMap, key, true);
+        this.$delete(this.selectedGoodsMap, key);
+        return;
+      }
+      if (this.mode === 'single') {
+        this.clearSelected();
       }
+      this.$set(this.selectedMap, key, true);
+      this.cacheSelectedGoods(key);
     },
     confirmSelect() {
+      const goodsIds = Object.keys(this.selectedMap);
+      const goodsList = goodsIds.map((id) => {
+        if (this.selectedGoodsMap[id]) {
+          return this.selectedGoodsMap[id];
+        }
+        const fromList = (this.list.data || []).find((item) => String(item.id) === id);
+        if (fromList) {
+          return this.toGoodsPayload(fromList);
+        }
+        return {
+          id: Number(id) || id,
+          name: '',
+          cover: '',
+          price: 0,
+          stock: 0
+        };
+      });
       uni.$emit('categoryAdGoodsSelected', {
         slotIndex: this.slotIndex,
-        goodsIds: Object.keys(this.selectedMap)
+        goodsIds,
+        goodsList
       });
       uni.navigateBack();
     }

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

@@ -56,6 +56,11 @@ export default {
       type: Object,
       default: () => ({})
     },
+    /** hot | new | pullGoods,用于「更多」列表页按模块拉取商品 */
+    moduleKey: {
+      type: String,
+      default: ''
+    },
     account: {
       type: [String, Number],
       default: ''
@@ -119,6 +124,8 @@ export default {
       }
       const ok = this.addBouquetToCart(goods, null, 1)
       if (ok) {
+        // 通知首页等父级刷新底部 tab 购物车角标(停留当前页时 onShow 不会触发)
+        this.$emit('cart-changed')
         uni.showToast({
           title: '已加入购物车',
           icon: 'none',
@@ -126,9 +133,15 @@ export default {
         })
       }
     },
-    /** 「更多」暂先跳店铺分类浏览页,后续可按关联类型精确筛选 */
+    /**
+     * 「更多」跳转模块商品列表页(与秒杀/团购一致)
+     * 带 moduleKey 供后端分页拉取对应模块全量商品
+     */
     goMore() {
-      this.pageTo({ url: `/pages/home/category?account=${this.account}&hdId=${this.hdId}` })
+      const title = (this.data && this.data.name) || '商品列表'
+      this.pageTo({
+        url: `/pages/goods/section-list?moduleKey=${this.moduleKey}&title=${encodeURIComponent(title)}&account=${this.account}&hdId=${this.hdId}`
+      })
     }
   }
 }

+ 35 - 3
mallApp/src/pages/home/index.vue

@@ -57,6 +57,7 @@
             module-key="hot"
             :account="account"
             :hd-id="hdId"
+            @cart-changed="refreshCartBadgeCount"
           />
           <home-goods-section
             v-else-if="mod.key === 'new'"
@@ -64,6 +65,7 @@
             module-key="new"
             :account="account"
             :hd-id="hdId"
+            @cart-changed="refreshCartBadgeCount"
           />
           <home-goods-section
             v-else-if="mod.key === 'pullGoods'"
@@ -71,13 +73,14 @@
             module-key="pullGoods"
             :account="account"
             :hd-id="hdId"
+            @cart-changed="refreshCartBadgeCount"
           />
         </template>
       </block>
       <view class="home-bottom-space"></view>
     </block>
-    <!-- 底部导航栏:固定页面底部,首页 Tab 高亮 -->
-    <shop-tab-bar current="home" :account="account" />
+    <!-- 底部导航栏:固定页面底部,首页 Tab 高亮;cart-count 与分类/订单页一致展示角标 -->
+    <shop-tab-bar current="home" :account="account" :cart-count="cartBadgeCount" />
   </view>
 </template>
 
@@ -110,7 +113,9 @@ export default {
       loading: true,
       pageData: null,
       /** 门店信息,用于补充 merchantName(get-home 未返回时兜底) */
-      shopInfo: null
+      shopInfo: null,
+      /** 底部 tab 购物车角标数量(本地缓存非响应式,需 onShow 主动刷新) */
+      cartBadgeCount: 0
     }
   },
   created() {
@@ -119,6 +124,10 @@ export default {
       this.shopInfo = cached
     }
   },
+  /** 从分类/购物车返回时刷新角标,避免仍显示旧数量 */
+  onShow() {
+    this.refreshCartBadgeCount()
+  },
   computed: {
     /** 商户名称:优先 get-home.topNav,其次 /shop/info 与本地缓存 */
     merchantName() {
@@ -157,9 +166,32 @@ export default {
     }
   },
   methods: {
+    /**
+     * 读取花材购物车本地缓存,计算底部 tab 角标
+     * 缓存 key 与分类页/订单页一致:selectListcg_hd_{account}
+     */
+    refreshCartBadgeCount() {
+      const account = this.account || uni.getStorageSync('account') || ''
+      if (!account) {
+        this.cartBadgeCount = 0
+        return
+      }
+      const cached = uni.getStorageSync('selectListcg_hd_' + account)
+      if (!Array.isArray(cached)) {
+        this.cartBadgeCount = 0
+        return
+      }
+      let count = 0
+      cached.forEach((item) => {
+        count += Number(item.bigCount) || 0
+        count += Number(item.smallCount) || 0
+      })
+      this.cartBadgeCount = count
+    },
     /** 页面初始化:并行拉取首页配置与门店信息(merchantName) */
     init() {
       this.loading = true
+      this.refreshCartBadgeCount()
       const tasks = [getHome()]
       if (this.hdId) {
         tasks.push(getInfo({ hdId: this.hdId }))