Переглянути джерело

feat(hd-home): 自动推导首页商品展示布局

- 根据商品数量和展开开关自动生成首页模块列数与展示数量

- 热门、上新和下拉商品按真实商品总数推导布局,保存时不再接收手动排列配置

- 补充花束场景相关建表脚本的表注释与索引格式

API: 首页配置读取结果继续返回 layoutCols/displayCount,并新增活动 expand 开关语义;保存接口不再持久化手动 layoutCols/displayCount

Database: 更新 sql/20260714_goods_use_case.sql 中花束场景表和关联表注释,无结构字段变更
shizhongqi 2 тижнів тому
батько
коміт
c169192cf3

+ 12 - 13
biz-hd/homePageConfig/classes/HomePageDisplayClass.php

@@ -153,7 +153,7 @@ class HomePageDisplayClass
         }
         $goods = self::filterActivityGoods($data['goods'] ?? []);
         $data['goodsTotal'] = count($goods);
-        $limit = HomePageModuleClass::resolveHomeDisplayLimit($data['displayCount'] ?? 0);
+        $limit = !empty($data['expand']) ? 0 : HomePageModuleClass::resolveHomeDisplayLimit($data['displayCount'] ?? 0);
         if ($limit > 0 && count($goods) > $limit) {
             $goods = array_slice($goods, 0, $limit);
         }
@@ -162,7 +162,7 @@ class HomePageDisplayClass
     }
 
     /**
-     * 格式化热门推荐/今日上新/下拉商品:关闭时清空商品;开启时按配置解析出真实商品列表
+     * 格式化热门推荐/今日上新/下拉商品:关闭时清空商品;开启时按真实商品数量自动推导列数与展示条数
      *
      * @param int $mainId
      * @param string $moduleKey hot|new|pullGoods
@@ -174,19 +174,18 @@ class HomePageDisplayClass
         if (empty($data['enabled'])) {
             $data['goods'] = [];
             $data['goodsTotal'] = 0;
+            $data['layoutCols'] = 1;
             return $data;
         }
-        $limit = HomePageModuleClass::resolveHomeDisplayLimit($data['displayCount'] ?? 0);
-        $paged = HomePageModuleClass::resolveDisplayGoodsPaged(
-            $mainId,
-            $data['type'],
-            $data['value'],
-            $data['sort'],
-            1,
-            $limit
-        );
-        $data['goods'] = $paged['list'];
-        $data['goodsTotal'] = $paged['total'];
+        $rows = HomePageModuleClass::matchGoodsRows($mainId, $data['type'], $data['value'], $data['sort']);
+        $total = count($rows);
+        $layout = HomePageModuleClass::resolveGoodsSectionLayout($total, $moduleKey);
+        $data['layoutCols'] = $layout['layoutCols'];
+        if ($layout['displayCount'] > 0 && $layout['displayCount'] < $total) {
+            $rows = array_slice($rows, 0, $layout['displayCount']);
+        }
+        $data['goods'] = HomePageModuleClass::formatGoodsRows($rows);
+        $data['goodsTotal'] = $total;
         return $data;
     }
 

+ 63 - 36
biz-hd/homePageConfig/classes/HomePageModuleClass.php

@@ -171,6 +171,10 @@ class HomePageModuleClass
         $data = self::normalizeActivityBase($saved);
         $data['enabled'] = HomePageConfigClass::getModuleEnabled($mainId, 'seckill');
         $data['goods'] = self::normalizeSeckillGoods($saved['goods'] ?? [], $mainId, $refreshStock);
+        // 按已添加商品数量 + 展开开关动态推导列数与展示数量(不依赖商家手动配置)
+        $layout = self::resolveActivityLayout(count($data['goods']), $data['expand']);
+        $data['layoutCols'] = $layout['layoutCols'];
+        $data['displayCount'] = $layout['displayCount'];
         $data['status'] = self::calcActivityStatus($data['startTime'], $data['endTime'], $data['enabled']);
         return $data;
     }
@@ -314,6 +318,10 @@ class HomePageModuleClass
         $data = self::normalizeActivityBase($saved);
         $data['enabled'] = HomePageConfigClass::getModuleEnabled($mainId, 'groupBuy');
         $data['goods'] = self::normalizeGroupBuyGoods($saved['goods'] ?? [], $mainId, $refreshStock);
+        // 按已添加商品数量 + 展开开关动态推导列数与展示数量(不依赖商家手动配置)
+        $layout = self::resolveActivityLayout(count($data['goods']), $data['expand']);
+        $data['layoutCols'] = $layout['layoutCols'];
+        $data['displayCount'] = $layout['displayCount'];
         $data['status'] = self::calcActivityStatus($data['startTime'], $data['endTime'], $data['enabled']);
         return $data;
     }
@@ -451,8 +459,6 @@ class HomePageModuleClass
             'type' => intval($saved['type'] ?? self::LINK_GOODS),
             'value' => strval($saved['value'] ?? ''),
             'sort' => intval($saved['sort'] ?? self::SORT_PRODUCT),
-            'layoutCols' => self::normalizeLayoutCols($saved['layoutCols'] ?? null),
-            'displayCount' => intval($saved['displayCount'] ?? 0),
         ];
     }
 
@@ -464,8 +470,7 @@ class HomePageModuleClass
         $type = intval($data['type'] ?? 0);
         $value = trim(strval($data['value'] ?? ''));
         $sort = intval($data['sort'] ?? self::SORT_PRODUCT);
-        $layoutCols = intval($data['layoutCols'] ?? self::DEFAULT_LAYOUT_COLS);
-        $displayCount = self::normalizeDisplayCount($data['displayCount'] ?? 0);
+        // layoutCols/displayCount 由读取时按真实商品数量自动推导,保存时不再接收商家手动配置
         if ($name === '') {
             util::fail('请输入首页展示名称');
         }
@@ -481,21 +486,41 @@ class HomePageModuleClass
         if (!in_array($sort, [self::SORT_PRODUCT, self::SORT_SALES, self::SORT_NEW], true)) {
             util::fail('排序规则无效');
         }
-        if (!in_array($layoutCols, self::LAYOUT_COLS_OPTIONS, true)) {
-            util::fail('排列规则无效');
-        }
         self::setJson($mainId, $suffix, [
             'name' => $name,
             'type' => $type,
             'value' => $value,
             'sort' => $sort,
-            'layoutCols' => $layoutCols,
-            'displayCount' => $displayCount,
         ]);
         HomePageConfigClass::updateModuleEnabled($mainId, $moduleKey, !empty($data['enabled']) ? 1 : 0);
         return true;
     }
 
+    /**
+     * 按真实商品总数与模块类型自动推导首页列数与展示数量
+     * - 1个商品:1排1列,展示全部
+     * - 2个商品:1排2列,展示全部
+     * - ≥3个:hot/new 按1排3列且只展示3个;pullGoods 按1排2列且展示全部
+     *
+     * @param int $goodsTotal 关联配置解析出的真实商品总数
+     * @param string $moduleKey hot|new|pullGoods
+     * @return array{layoutCols:int,displayCount:int}
+     */
+    public static function resolveGoodsSectionLayout($goodsTotal, $moduleKey)
+    {
+        $goodsTotal = intval($goodsTotal);
+        if ($goodsTotal <= 1) {
+            return ['layoutCols' => 1, 'displayCount' => 0];
+        }
+        if ($goodsTotal === 2) {
+            return ['layoutCols' => 2, 'displayCount' => 0];
+        }
+        if ($moduleKey === 'pullGoods') {
+            return ['layoutCols' => 2, 'displayCount' => 0];
+        }
+        return ['layoutCols' => 3, 'displayCount' => 3];
+    }
+
     /**
      * 规范化排列规则取值,非法/缺省时回退默认值
      */
@@ -667,26 +692,9 @@ class HomePageModuleClass
         ];
     }
 
-    /**
-     * 规范化首页商品展示数量:0=全部,1-10=具体数量
-     *
-     * @param mixed $value
-     * @return int
-     */
-    public static function normalizeDisplayCount($value)
-    {
-        if ($value === '' || $value === null || intval($value) === 0) {
-            return 0;
-        }
-        $count = intval($value);
-        if ($count < 1 || $count > 10) {
-            util::fail('商品展示数量范围为1-10,留空表示全部');
-        }
-        return $count;
-    }
-
     /**
      * 根据 displayCount 计算首页实际截断条数:1-10 用配置值,0(全部)回退默认上限
+     * 供秒杀/团购等活动模块使用
      *
      * @param mixed $displayCount
      * @return int
@@ -721,14 +729,38 @@ class HomePageModuleClass
             'title' => strval($saved['title'] ?? ''),
             'subtitle' => strval($saved['subtitle'] ?? ''),
             'showCountdown' => !empty($saved['showCountdown']) ? 1 : 0,
-            'displayCount' => intval($saved['displayCount'] ?? 0),
+            // 商品展开:开启后首页按1排1列展示全部商品,默认关闭
+            'expand' => !empty($saved['expand']) ? 1 : 0,
             'startTime' => intval($saved['startTime'] ?? 0),
             'endTime' => intval($saved['endTime'] ?? 0),
             'desc' => strval($saved['desc'] ?? ''),
-            'layoutCols' => self::normalizeLayoutCols($saved['layoutCols'] ?? null),
         ];
     }
 
+    /**
+     * 按已添加商品数量与「商品展开」开关自动推导首页列数与展示数量
+     * - expand 开启:1排1列,展示全部
+     * - 1个商品:1排1列;2个:1排2列;≥3个:1排3列且只展示3个
+     *
+     * @param int $goodsCount 已添加商品总数(含未上架)
+     * @param int $expand 商品展开开关 0/1
+     * @return array{layoutCols:int,displayCount:int}
+     */
+    public static function resolveActivityLayout($goodsCount, $expand)
+    {
+        if (!empty($expand)) {
+            return ['layoutCols' => 1, 'displayCount' => 0];
+        }
+        $goodsCount = intval($goodsCount);
+        if ($goodsCount <= 1) {
+            return ['layoutCols' => 1, 'displayCount' => 0];
+        }
+        if ($goodsCount === 2) {
+            return ['layoutCols' => 2, 'displayCount' => 0];
+        }
+        return ['layoutCols' => 3, 'displayCount' => 3];
+    }
+
     public static function validateActivityBase($data)
     {
         $title = trim(strval($data['title'] ?? ''));
@@ -736,8 +768,7 @@ class HomePageModuleClass
         $desc = trim(strval($data['desc'] ?? ''));
         $startTime = intval($data['startTime'] ?? 0);
         $endTime = intval($data['endTime'] ?? 0);
-        $layoutCols = intval($data['layoutCols'] ?? self::DEFAULT_LAYOUT_COLS);
-        $displayCount = self::normalizeDisplayCount($data['displayCount'] ?? 0);
+        // layoutCols/displayCount 由读取时按商品数量自动推导,保存时不再接收商家手动配置
         if ($title === '') {
             util::fail('请输入活动标题');
         }
@@ -762,18 +793,14 @@ class HomePageModuleClass
         if (mb_strlen($desc) > 500) {
             util::fail('活动说明不能超过500字');
         }
-        if (!in_array($layoutCols, self::LAYOUT_COLS_OPTIONS, true)) {
-            util::fail('排列规则无效');
-        }
         return [
             'title' => $title,
             'subtitle' => $subtitle,
             'showCountdown' => !empty($data['showCountdown']) ? 1 : 0,
-            'displayCount' => $displayCount,
+            'expand' => !empty($data['expand']) ? 1 : 0,
             'startTime' => $startTime,
             'endTime' => $endTime,
             'desc' => $desc,
-            'layoutCols' => $layoutCols,
         ];
     }
 

+ 7 - 4
sql/20260714_goods_use_case.sql

@@ -8,16 +8,19 @@ CREATE TABLE `xhGoodsUseCase` (
   `delStatus` tinyint(4) NOT NULL DEFAULT 0 COMMENT '删除状态',
   `addTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
   `updateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-  PRIMARY KEY (`id`), KEY `mainId` (`mainId`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+  PRIMARY KEY (`id`),
+  KEY `mainId` (`mainId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='花束场景';
 
 CREATE TABLE `xhGoodsUseCaseRelation` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `mainId` int(11) NOT NULL DEFAULT 0,
   `goodsId` int(11) NOT NULL DEFAULT 0,
   `useCaseId` int(11) NOT NULL DEFAULT 0,
-  PRIMARY KEY (`id`), UNIQUE KEY `goods_use_case` (`goodsId`,`useCaseId`), KEY `mainId` (`mainId`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `goods_use_case` (`goodsId`,`useCaseId`),
+  KEY `mainId` (`mainId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='花束与场景关联表';