Răsfoiți Sursa

fix(goods): 修复分类商品去重分页条数不足

- 按唯一商品统计总数并分页,避免多分类关联占用分页名额
- 稳定商品排序并按分页 ID 顺序组装列表

API: category/goods-list 的 total、moreData 和分页列表改按唯一商品计算
shizhongqi 14 ore în urmă
părinte
comite
f6d0b39103

+ 1 - 0
app-mall/controllers/CategoryController.php

@@ -55,6 +55,7 @@ class CategoryController extends BaseController
     }
 
     //取分类下商品 ssh 2019.12.2(游客可浏览,custom 为空时按散客价组装)
+    // 列表按唯一商品分页:同一商品多分类去重后仍保证每页条数,避免商城列表无法触底加载
     public function actionGoodsList()
     {
         $get = Yii::$app->request->get();

+ 76 - 17
biz-hd/goods/classes/GoodsCategoryClass.php

@@ -1,10 +1,16 @@
 <?php
 
+/**
+ * 商品-分类关联业务
+ * 谁用:hd 分类商品列表、mallApp /category/goods-list
+ * 解决问题:按分类/搜索取商品;同一商品挂多个分类时按唯一商品分页,避免本页去重后条数不足导致前端无法触底加载
+ */
 namespace bizHd\goods\classes;
 
 use bizHd\goods\models\GoodsCategory;
 use common\components\util;
 use bizHd\base\classes\BaseClass;
+use Yii;
 
 class GoodsCategoryClass extends BaseClass
 {
@@ -12,11 +18,22 @@ class GoodsCategoryClass extends BaseClass
     public static $baseFile = '\bizHd\goods\models\GoodsCategory';
 
     //$params 必须预留字段,为后面做准备
+    /**
+     * 分类/搜索商品列表
+     * 分页口径是唯一商品,不是分类关联行:搜索或多分类时同一商品会对应多行 xhGoodsCategory,
+     * 若先 LIMIT 再 PHP 去重,本页可能只剩几条,列表页撑不满屏、onReachBottom 不会触发。
+     *
+     * @param array $askInfo 请求参数(page/pageSize/requestType/筛选条件)
+     * @param mixed $shop 店铺
+     * @param mixed $custom 客户(散客价/会员价)
+     * @param array $params 预留扩展
+     * @return array{list:array,total:int,page:int,pageSize:int,moreData:int}
+     */
     public static function getGoodsList($askInfo, $shop, $custom, $params = [])
     {
         $requestType = $askInfo['requestType'] ?? '';
-        $page = $askInfo['page'] ?? 1;
-        $pageSize = $askInfo['pageSize'] ?? 20;
+        $page = max(1, intval($askInfo['page'] ?? 1));
+        $pageSize = max(1, intval($askInfo['pageSize'] ?? 20));
         if (empty($requestType)) {
             util::fail('没有请求类型');
         }
@@ -56,7 +73,8 @@ class GoodsCategoryClass extends BaseClass
         if (!empty($askInfo['search'])) {
             $query->andWhere(['like', 'xhGoods.name', $askInfo['search']]);
         }
-        $query->joinWith(['goods' => function ($query) use ($askInfo, $requestType) {
+        // INNER JOIN:不符合商城/开单条件的商品不占分页名额,避免 LIMIT 后再被 PHP 丢掉
+        $query->innerJoinWith(['goods' => function ($query) use ($askInfo, $requestType) {
             $query->andWhere(['xhGoods.masterId' => 0]);
 
             if ($requestType == 'kd') {
@@ -65,7 +83,6 @@ class GoodsCategoryClass extends BaseClass
                 $query->andWhere(['>', 'xhGoods.stock', 0]);
             } elseif ($requestType == 'goodsList') {
                 //花束管理列表
-
                 $query->andWhere(['xhGoods.delStatus' => 0]);
 
                 $priceType = isset($askInfo['priceType']) && is_numeric($askInfo['priceType']) ? $askInfo['priceType'] : -1;
@@ -136,20 +153,21 @@ class GoodsCategoryClass extends BaseClass
         } else {
             $query->orderBy(['inTurn' => SORT_DESC]);
         }
-        // 获取总数
-        $total = $query->count();
-        // 分页查询
-        $goodsCategoryInfoList = $query->offset(($page - 1) * $pageSize)
-            ->limit($pageSize)
-            ->all();
-        // 整理结果,只要有库存的商品,并去重
+        // 按唯一商品分页:total / moreData / 本页 list 都以去重后的商品为准
+        $pageInfo = self::getUniqueGoodsPage($query, $page, $pageSize, $goodsIdsForOrder);
+        $total = $pageInfo['total'];
+        $pagedGoodsIds = $pageInfo['ids'];
+
         $list = [];
-        $addedGoodsIds = []; // 用于记录已添加的商品ID,避免重复,一个商品在多个分类下就会出现重复,搜索的情况下
-        foreach ($goodsCategoryInfoList as $info) {
-            if ($info->goods && !in_array($info->goods->id, $addedGoodsIds)) {
-                // 只要有关联且有库存的商品,且未添加过
-                $list[] = $info->goods->attributes;
-                $addedGoodsIds[] = $info->goods->id; // 记录已添加的商品ID
+        $addedGoodsIds = []; // 用于记录已添加的商品ID,避免重复
+        if (!empty($pagedGoodsIds)) {
+            $goodsMap = GoodsClass::getByIds($pagedGoodsIds, null, 'id');
+            foreach ($pagedGoodsIds as $gid) {
+                if (empty($goodsMap[$gid]) || in_array($gid, $addedGoodsIds, true)) {
+                    continue;
+                }
+                $list[] = $goodsMap[$gid];
+                $addedGoodsIds[] = $gid;
             }
         }
 
@@ -167,6 +185,47 @@ class GoodsCategoryClass extends BaseClass
         ];
     }
 
+    /**
+     * 取出去重后的商品总数与本页 goodsId
+     * 先按 xhGoods.id 分组再 LIMIT,保证每页都是 pageSize 条唯一商品(末页除外)
+     *
+     * @param \yii\db\ActiveQuery $query 已带商品 INNER JOIN 与筛选、排序条件
+     * @param int $page 页码,从 1 起
+     * @param int $pageSize 每页唯一商品数
+     * @param int[] $goodsIdsForOrder 运营指定顺序时按 FIELD 排,否则按分类 inTurn 最大值
+     * @return array{total:int,ids:int[]}
+     */
+    public static function getUniqueGoodsPage($query, $page, $pageSize, $goodsIdsForOrder = [])
+    {
+        $countQuery = clone $query;
+        $total = (int)$countQuery->count('DISTINCT xhGoods.id');
+        if ($total <= 0) {
+            return ['total' => 0, 'ids' => []];
+        }
+
+        $idQuery = clone $query;
+        if (!empty($goodsIdsForOrder)) {
+            // FIELD 已按商品唯一,再加 id 保证顺序稳定,避免 OFFSET 翻页交叉重复
+            $idQuery->select(['xhGoods.id'])
+                ->groupBy(['xhGoods.id'])
+                ->orderBy(new \yii\db\Expression('FIELD(xhGoods.id, ' . implode(',', $goodsIdsForOrder) . '), xhGoods.id DESC'));
+        } else {
+            // 同一商品多分类取最大 inTurn;inTurn 相同再按商品 id,避免无第二排序时翻页重复/遗漏
+            $idQuery->select(['xhGoods.id'])
+                ->groupBy(['xhGoods.id'])
+                ->orderBy(new \yii\db\Expression('MAX(xhGoodsCategory.inTurn) DESC, xhGoods.id DESC'));
+        }
+
+        $ids = $idQuery->offset(($page - 1) * $pageSize)
+            ->limit($pageSize)
+            ->column();
+
+        return [
+            'total' => $total,
+            'ids' => array_values(array_unique(array_filter(array_map('intval', $ids)))),
+        ];
+    }
+
     //删除商品的分类
     public static function delGoodsCategory($mainId, $categoryId, $goodsId)
     {