Browse Source

卓美 商品标签 金刚区 瀑布区

yuhongqi 1 tuần trước cách đây
mục cha
commit
5ffc839291
38 tập tin đã thay đổi với 1328 bổ sung20 xóa
  1. 3 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductScrmController.java
  2. 73 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductTagScrmController.java
  3. 83 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreUserEndCategoryScrmController.java
  4. 33 7
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  5. 20 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductTagRelationScrm.java
  6. 35 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductTagScrm.java
  7. 20 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductUserEndCategory.java
  8. 43 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreUserEndCategoryScrm.java
  9. 11 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java
  10. 28 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductTagRelationScrmMapper.java
  11. 29 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductTagScrmMapper.java
  12. 27 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductUserEndCategoryMapper.java
  13. 30 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreUserEndCategoryScrmMapper.java
  14. 5 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductAddEditParam.java
  15. 12 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductScrmService.java
  16. 31 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductTagScrmService.java
  17. 39 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreUserEndCategoryScrmService.java
  18. 60 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  19. 79 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductTagScrmServiceImpl.java
  20. 163 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreUserEndCategoryScrmServiceImpl.java
  21. 23 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductTagListVO.java
  22. 17 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductTagNameVO.java
  23. 22 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreTagProductVO.java
  24. 29 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreUserEndCategoryProductVO.java
  25. 8 0
      fs-service/src/main/java/com/fs/live/mapper/LiveDataMapper.java
  26. 5 0
      fs-service/src/main/java/com/fs/live/service/ILiveDataService.java
  27. 24 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java
  28. 18 0
      fs-service/src/main/java/com/fs/live/vo/LiveAppSimpleVO.java
  29. 1 1
      fs-service/src/main/resources/application-config-druid-bjzm-test.yml
  30. 3 3
      fs-service/src/main/resources/application-config-druid-bjzm.yml
  31. 41 2
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml
  32. 34 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductTagRelationScrmMapper.xml
  33. 65 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductTagScrmMapper.xml
  34. 34 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductUserEndCategoryMapper.xml
  35. 75 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreUserEndCategoryScrmMapper.xml
  36. 8 0
      fs-service/src/main/resources/mapper/live/LiveDataMapper.xml
  37. 14 4
      fs-user-app/src/main/java/com/fs/app/controller/IndexController.java
  38. 83 1
      fs-user-app/src/main/java/com/fs/app/controller/store/IndexScrmController.java

+ 3 - 1
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductScrmController.java

@@ -153,7 +153,9 @@ public class FsStoreProductScrmController extends BaseController
     {
         FsStoreProductScrm product=fsStoreProductService.selectFsStoreProductById(productId);
         List<FsStoreProductAttrScrm> attrs=attrService.selectFsStoreProductAttrByProductId(productId);
-        return R.ok().put("data",product).put("attrs", attrs);
+        List<Long> userEndCategoryIds = fsStoreProductService.selectUserEndCategoryIdsByProductId(productId);
+        List<Long> tagIds = fsStoreProductService.selectTagIdsByProductId(productId);
+        return R.ok().put("data",product).put("attrs", attrs).put("userEndCategoryIds", userEndCategoryIds).put("tagIds", tagIds);
     }
 
     @PreAuthorize("@ss.hasPermi('store:storeProduct:add')")

+ 73 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductTagScrmController.java

@@ -0,0 +1,73 @@
+package com.fs.hisStore.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.hisStore.domain.FsStoreProductTagScrm;
+import com.fs.hisStore.service.IFsStoreProductTagScrmService;
+import com.fs.hisStore.vo.FsStoreProductTagListVO;
+import com.fs.hisStore.vo.FsStoreTagProductVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 商品标签管理
+ * 支持:标签名称模糊搜索、新增/编辑/删除、调整排序、一页10条分页、查看关联商品(弹窗:商品ID、名称、主图、售价、状态)
+ */
+@RestController
+@RequestMapping("/store/store/storeProductTag")
+public class FsStoreProductTagScrmController extends BaseController {
+
+    @Autowired
+    private IFsStoreProductTagScrmService productTagService;
+
+    /** 分页列表,一页10条 */
+    @PreAuthorize("@ss.hasPermi('store:storeProductTag:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductTagScrm query) {
+        startPage();
+        List<FsStoreProductTagListVO> list = productTagService.selectListVOPage(query);
+        return getDataTable(list);
+    }
+
+    /** 关联商品:点击商品数量弹窗展示(商品ID、名称、主图、售价、状态),分页 */
+    @PreAuthorize("@ss.hasPermi('store:storeProductTag:query')")
+    @GetMapping("/products/{tagId}")
+    public TableDataInfo listProductsByTagId(@PathVariable Long tagId) {
+        startPage();
+        List<FsStoreTagProductVO> list = productTagService.selectProductsByTagId(tagId);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductTag:query')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        return AjaxResult.success(productTagService.selectById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductTag:add')")
+    @Log(title = "商品标签", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductTagScrm entity) {
+        return toAjax(productTagService.insert(entity));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductTag:edit')")
+    @Log(title = "商品标签", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductTagScrm entity) {
+        return toAjax(productTagService.update(entity));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:storeProductTag:remove')")
+    @Log(title = "商品标签", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(productTagService.deleteByIds(ids));
+    }
+}

+ 83 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreUserEndCategoryScrmController.java

@@ -0,0 +1,83 @@
+package com.fs.hisStore.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.hisStore.domain.FsStoreUserEndCategoryScrm;
+import com.fs.hisStore.service.IFsStoreUserEndCategoryScrmService;
+import com.fs.hisStore.vo.FsStoreUserEndCategoryProductVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 商品用户分端类管理(金刚区/瀑布流)
+ * 支持:分类名称模糊搜索、新增/编辑/删除、排序(用户端最多展示8个,超出不展示)、金刚区需icon/瀑布流不需、状态显示隐藏、一页10条分页、排序相同按创建时间倒序
+ */
+@RestController
+@RequestMapping("/store/store/userEndCategory")
+public class FsStoreUserEndCategoryScrmController extends BaseController {
+
+    @Autowired
+    private IFsStoreUserEndCategoryScrmService userEndCategoryService;
+
+    /** 分页列表,一页10条,排序值相同按创建时间倒序 */
+    @PreAuthorize("@ss.hasPermi('store:userEndCategory:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreUserEndCategoryScrm query) {
+        startPage();
+        List<FsStoreUserEndCategoryScrm> list = userEndCategoryService.selectListPage(query);
+        return getDataTable(list);
+    }
+
+    /** 添加/编辑商品时拉取用户端分类(金刚区、瀑布流各最多8条由前端截取) */
+    @GetMapping("/listForProduct")
+    public R listForProduct(@RequestParam(required = false) Long storeId) {
+        List<FsStoreUserEndCategoryScrm> list = userEndCategoryService.selectListForProduct(storeId);
+        return R.ok().put("data", list);
+    }
+
+    /** 按用户端分类ID分页查询关联商品(去重商品ID分页;返回商品ID、名称、售价、原价、销量及产品标签列表) */
+    @PreAuthorize("@ss.hasPermi('store:userEndCategory:query')")
+    @GetMapping("/products")
+    public TableDataInfo listProductsByCategoryId(
+            @RequestParam Long id,
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize) {
+        startPage();
+        List<FsStoreUserEndCategoryProductVO> list = userEndCategoryService.listProductsByCategoryId(id);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:userEndCategory:query')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        return AjaxResult.success(userEndCategoryService.selectById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:userEndCategory:add')")
+    @Log(title = "用户分端类", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreUserEndCategoryScrm entity) {
+        return toAjax(userEndCategoryService.insert(entity));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:userEndCategory:edit')")
+    @Log(title = "用户分端类", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreUserEndCategoryScrm entity) {
+        return toAjax(userEndCategoryService.update(entity));
+    }
+
+    @PreAuthorize("@ss.hasPermi('store:userEndCategory:remove')")
+    @Log(title = "用户分端类", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(userEndCategoryService.deleteByIds(ids));
+    }
+}

+ 33 - 7
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -65,6 +65,7 @@ import com.fs.his.service.IFsUserWxService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.OptionsVO;
 import com.fs.hisStore.domain.FsStoreProductScrm;
+import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
 import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.*;
 import com.fs.qw.domain.QwCompany;
@@ -170,6 +171,8 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     private ISopUserLogsInfoService iSopUserLogsInfoService;
     @Autowired
     private FsCourseLinkMapper fsCourseLinkMapper;
+    @Autowired
+    private FsStoreProductScrmMapper fsStoreProductScrmMapper;
 
 
     @Autowired
@@ -4560,15 +4563,38 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                 throw new RuntimeException(e);
             }
             if (jsonNode.isArray()) {
-                List<FsStoreProductScrm> fsPackageListVOS = new ArrayList<>();
+                // 第一步:收集所有 productId
+                List<Long> productIds = new ArrayList<>();
+                Map<Long, JsonNode> productIdToJsonNodeMap = new HashMap<>();
                 for (JsonNode node : jsonNode) {
+                    Long productId = node.path("productId").asLong();
+                    if (productId != null && productId > 0) {
+                        productIds.add(productId);
+                        productIdToJsonNodeMap.put(productId, node);
+                    }
+                }
+
+                // 第二步:批量从数据库查询满足条件的产品(已上架、未删除、审核通过)
+                List<FsStoreProductScrm> validProducts = new ArrayList<>();
+                if (!productIds.isEmpty()) {
+                    // 使用 getStoreProductInProductIdsForApp 查询,该方法已过滤 is_del=0 和 is_show=1
+                    // 但还需要在内存中过滤 is_audit='1'
+                    validProducts = fsStoreProductScrmMapper.getStoreProductInProductIdsForApp(productIds);
+                }
+                // 第四步:根据查询结果构建 VO 对象
+                List<FsStoreProductScrm> fsPackageListVOS = new ArrayList<>();
+                for (FsStoreProductScrm validProduct : validProducts) {
                     FsStoreProductScrm fsStoreProductScrm = new FsStoreProductScrm();
-                    fsStoreProductScrm.setProductId(node.path("productId").asLong());
-                    fsStoreProductScrm.setImages(node.path("image").asText());
-                    fsStoreProductScrm.setImgUrl(node.path("imgUrl").asText());
-                    fsStoreProductScrm.setBarCode(node.path("barCode").asText());
-                    fsStoreProductScrm.setPrice(new BigDecimal(node.path("price").asText()));
-                    fsStoreProductScrm.setProductName(node.path("productName").asText());
+                    JsonNode originalNode = productIdToJsonNodeMap.get(validProduct.getProductId());
+
+                    fsStoreProductScrm.setProductId(validProduct.getProductId());
+                    fsStoreProductScrm.setImages(validProduct.getImages() != null ? validProduct.getImages() :
+                            (originalNode != null ? originalNode.path("image").asText() : ""));
+                    fsStoreProductScrm.setImgUrl(validProduct.getImgUrl() != null ? validProduct.getImgUrl() :
+                            (originalNode != null ? originalNode.path("imgUrl").asText() : ""));
+                    fsStoreProductScrm.setBarCode(validProduct.getBarCode());
+                    fsStoreProductScrm.setPrice(validProduct.getPrice());
+                    fsStoreProductScrm.setProductName(validProduct.getProductName());
                     fsPackageListVOS.add(fsStoreProductScrm);
                 }
                 vo.setFsStoreProductScrms(fsPackageListVOS);

+ 20 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductTagRelationScrm.java

@@ -0,0 +1,20 @@
+package com.fs.hisStore.domain;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 商品与标签关联表 fs_store_product_tag_relation(一个商品最多3个标签)
+ */
+@Data
+public class FsStoreProductTagRelationScrm implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 商品ID */
+    private Long productId;
+
+    /** 标签ID */
+    private Long tagId;
+}

+ 35 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductTagScrm.java

@@ -0,0 +1,35 @@
+package com.fs.hisStore.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 商品标签对象 fs_store_product_tag
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class FsStoreProductTagScrm extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 店铺ID */
+    @Excel(name = "店铺ID")
+    private Long storeId;
+
+    /** 标签名称,最多4个字 */
+    @Excel(name = "标签名称")
+    private String tagName;
+
+    /** 状态:1-显示 0-隐藏 */
+    @Excel(name = "状态", readConverterExp = "1=显示,0=隐藏")
+    private Integer status;
+
+    /** 排序值 */
+    @Excel(name = "排序")
+    private Long sort;
+}

+ 20 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductUserEndCategory.java

@@ -0,0 +1,20 @@
+package com.fs.hisStore.domain;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 商品与用户分端类关联表 fs_store_product_user_end_category
+ */
+@Data
+public class FsStoreProductUserEndCategory implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 商品ID */
+    private Long productId;
+
+    /** 用户分端类ID */
+    private Long userEndCategoryId;
+}

+ 43 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreUserEndCategoryScrm.java

@@ -0,0 +1,43 @@
+package com.fs.hisStore.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 商品用户分端类(金刚区/瀑布流)对象 fs_store_user_end_category
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class FsStoreUserEndCategoryScrm extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 店铺ID */
+    @Excel(name = "店铺ID")
+    private Long storeId;
+
+    /** 分类名称 */
+    @Excel(name = "分类名称")
+    private String categoryName;
+
+    /** 分类位置:1-金刚区 2-瀑布流 */
+    @Excel(name = "分类位置", readConverterExp = "1=金刚区,2=瀑布流")
+    private Integer position;
+
+    /** 分类icon(金刚区必填,瀑布流不填) */
+    @Excel(name = "分类icon")
+    private String icon;
+
+    /** 状态:1-显示 0-隐藏 */
+    @Excel(name = "状态", readConverterExp = "1=显示,0=隐藏")
+    private Integer status;
+
+    /** 排序值,越小越靠前;用户端各位置最多展示前8个 */
+    @Excel(name = "排序")
+    private Long sort;
+}

+ 11 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java

@@ -281,7 +281,11 @@ public interface FsStoreProductScrmMapper
 
     List<FsStoreProductListQueryVO> selectFsStoreProductNewQuery(Map<String, Object> params);
 
+    List<FsStoreProductListQueryVO> selectFsStoreProductNewQueryPage(Map<String, Object> params);
+
     List<FsStoreProductListQueryVO> selectFsStoreProductHotQuery(Map<String, Object> params);
+
+    List<FsStoreProductListQueryVO> selectFsStoreProductHotQueryPage(Map<String, Object> params);
     @Select("select p.* from fs_store_product_scrm p " +
             //新增审核状态及所属店铺审核状态
             "<if test='config.isAudit == \"1\" '>" +
@@ -409,7 +413,13 @@ public interface FsStoreProductScrmMapper
 
     List<FsStoreProductScrm> bulkCopyFsStoreProductByIds(Long[] productIds);
 
-    List<FsStoreProductScrm> getStoreProductInProductIds(List<Long> productIds);
+    List<FsStoreProductScrm> getStoreProductInProductIds(@Param("productIds") List<Long> productIds);
+
+    /** 按商品ID列表查询,仅返回上线且未删除的商品(is_del=0, is_show=1),用于 App 列表 */
+    List<FsStoreProductScrm> getStoreProductInProductIdsForApp(@Param("productIds") List<Long> productIds);
+
+    /** 用于首页商品列表「全部」:分页查上架商品ID(is_del=0, is_show=1),配合 PageHelper */
+    List<Long> selectProductIdsForApp();
 
     @Select({"<script> " +
             "SELECT distinct fsp.* FROM fs_store_product_scrm fsp " +

+ 28 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductTagRelationScrmMapper.java

@@ -0,0 +1,28 @@
+package com.fs.hisStore.mapper;
+
+import com.fs.hisStore.domain.FsStoreProductTagRelationScrm;
+import com.fs.hisStore.vo.FsStoreProductTagNameVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 商品-标签关联 Mapper(一个商品最多3个标签)
+ */
+public interface FsStoreProductTagRelationScrmMapper {
+
+    int insertBatch(@Param("productId") Long productId, @Param("tagIds") List<Long> tagIds);
+
+    int deleteByProductId(Long productId);
+
+    List<Long> selectTagIdsByProductId(Long productId);
+
+    /** 统计某标签关联的商品数量 */
+    int countByTagId(Long tagId);
+
+    /** 按标签ID删除关联(删除标签时调用) */
+    int deleteByTagIds(@Param("tagIds") Long[] tagIds);
+
+    /** 按商品ID列表批量查询商品对应的标签名称(关联 fs_store_product_tag_scrm) */
+    List<FsStoreProductTagNameVO> selectProductTagNamesByProductIds(@Param("productIds") List<Long> productIds);
+}

+ 29 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductTagScrmMapper.java

@@ -0,0 +1,29 @@
+package com.fs.hisStore.mapper;
+
+import com.fs.hisStore.domain.FsStoreProductTagScrm;
+import com.fs.hisStore.vo.FsStoreProductTagListVO;
+import com.fs.hisStore.vo.FsStoreTagProductVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 商品标签 Mapper
+ */
+public interface FsStoreProductTagScrmMapper {
+
+    FsStoreProductTagScrm selectById(Long id);
+
+    List<FsStoreProductTagListVO> selectListVO(FsStoreProductTagScrm query);
+
+    int insert(FsStoreProductTagScrm entity);
+
+    int update(FsStoreProductTagScrm entity);
+
+    int deleteById(Long id);
+
+    int deleteByIds(Long[] ids);
+
+    /** 查询标签下关联商品(分页:商品ID、名称、主图、售价、状态) */
+    List<FsStoreTagProductVO> selectProductsByTagId(@Param("tagId") Long tagId);
+}

+ 27 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductUserEndCategoryMapper.java

@@ -0,0 +1,27 @@
+package com.fs.hisStore.mapper;
+
+import com.fs.hisStore.domain.FsStoreProductUserEndCategory;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 商品-用户分端类关联 Mapper
+ */
+public interface FsStoreProductUserEndCategoryMapper {
+
+    int insertBatch(@Param("productId") Long productId, @Param("categoryIds") List<Long> categoryIds);
+
+    int deleteByProductId(Long productId);
+
+    List<Long> selectCategoryIdsByProductId(Long productId);
+
+    /** 按用户端分类ID删除关联(删除分类时调用) */
+    int deleteByCategoryIds(@Param("categoryIds") Long[] categoryIds);
+
+    /** 按用户端分类ID查询去重后的商品ID列表(用于分页,配合 PageHelper) */
+    List<Long> selectDistinctProductIdsByCategoryId(@Param("categoryId") Long categoryId);
+
+    /** 关联表内全部去重商品ID(不按分类,配合 PageHelper 用于「全部」) */
+    List<Long> selectDistinctProductIds();
+}

+ 30 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreUserEndCategoryScrmMapper.java

@@ -0,0 +1,30 @@
+package com.fs.hisStore.mapper;
+
+import com.fs.hisStore.domain.FsStoreUserEndCategoryScrm;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 商品用户分端类 Mapper
+ */
+public interface FsStoreUserEndCategoryScrmMapper {
+
+    FsStoreUserEndCategoryScrm selectById(Long id);
+
+    List<FsStoreUserEndCategoryScrm> selectList(FsStoreUserEndCategoryScrm query);
+
+    int insert(FsStoreUserEndCategoryScrm entity);
+
+    int update(FsStoreUserEndCategoryScrm entity);
+
+    int deleteById(Long id);
+
+    int deleteByIds(Long[] ids);
+
+    /** 用于添加商品时选择:按 position 分组,各取前8条(status=1),排序:sort asc, create_time desc */
+    List<FsStoreUserEndCategoryScrm> selectListForProduct(@Param("storeId") Long storeId);
+
+    /** 用户端展示:按位置取前8条,status=1,排序 sort asc, create_time desc */
+    List<FsStoreUserEndCategoryScrm> selectTop8ByPosition(@Param("storeId") Long storeId, @Param("position") Integer position);
+}

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductAddEditParam.java

@@ -302,4 +302,9 @@ public class FsStoreProductAddEditParam implements Serializable
     /** 所属小程序app_id,多个用逗号隔开 */
     private String appIds;
 
+    /** 用户端分类ID列表(金刚区/瀑布流多选) */
+    private List<Long> userEndCategoryIds;
+
+    /** 商品标签ID列表(最多3个) */
+    private List<Long> tagIds;
 }

+ 12 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductScrmService.java

@@ -101,6 +101,12 @@ public interface IFsStoreProductScrmService
 
     List<FsStoreProductListQueryVO> selectFsStoreProductHotQuery(int count, String appId);
 
+    /** 绿色有机分页(与 selectFsStoreProductNewQuery 条件一致,支持分页) */
+    List<FsStoreProductListQueryVO> selectFsStoreProductNewQueryPage(int pageNum, int pageSize, String appId);
+
+    /** 上新推荐分页(与 selectFsStoreProductHotQuery 条件一致,支持分页) */
+    List<FsStoreProductListQueryVO> selectFsStoreProductHotQueryPage(int pageNum, int pageSize, String appId);
+
     List<FsStoreProductListQueryVO> selectFsStoreProductGoodQuery(int count);
 
     List<FsStoreProductListQueryVO> selectFsStoreProductTuiListQuery(BaseQueryParam param);
@@ -148,4 +154,10 @@ public interface IFsStoreProductScrmService
     R copyStoreProduct(Long productId);
 
     R updateCache(Long productId);
+
+    /** 查询商品关联的用户端分类ID列表 */
+    List<Long> selectUserEndCategoryIdsByProductId(Long productId);
+
+    /** 查询商品关联的标签ID列表 */
+    List<Long> selectTagIdsByProductId(Long productId);
 }

+ 31 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductTagScrmService.java

@@ -0,0 +1,31 @@
+package com.fs.hisStore.service;
+
+import com.fs.hisStore.domain.FsStoreProductTagScrm;
+import com.fs.hisStore.vo.FsStoreProductTagListVO;
+import com.fs.hisStore.vo.FsStoreTagProductVO;
+
+import java.util.List;
+
+/**
+ * 商品标签 Service 接口
+ */
+public interface IFsStoreProductTagScrmService {
+
+    FsStoreProductTagScrm selectById(Long id);
+
+    List<FsStoreProductTagListVO> selectListVO(FsStoreProductTagScrm query);
+
+    /** 分页列表 */
+    List<FsStoreProductTagListVO> selectListVOPage(FsStoreProductTagScrm query);
+
+    /** 标签下关联商品(分页:商品ID、名称、主图、售价、状态) */
+    List<FsStoreTagProductVO> selectProductsByTagId(Long tagId);
+
+    int insert(FsStoreProductTagScrm entity);
+
+    int update(FsStoreProductTagScrm entity);
+
+    int deleteById(Long id);
+
+    int deleteByIds(Long[] ids);
+}

+ 39 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreUserEndCategoryScrmService.java

@@ -0,0 +1,39 @@
+package com.fs.hisStore.service;
+
+import com.fs.hisStore.domain.FsStoreUserEndCategoryScrm;
+import com.fs.hisStore.vo.FsStoreUserEndCategoryProductVO;
+
+import java.util.List;
+
+/**
+ * 商品用户分端类 Service 接口
+ */
+public interface IFsStoreUserEndCategoryScrmService {
+
+    FsStoreUserEndCategoryScrm selectById(Long id);
+
+    List<FsStoreUserEndCategoryScrm> selectList(FsStoreUserEndCategoryScrm query);
+
+    /** 分页列表:一页10条,排序值相同按创建时间倒序 */
+    List<FsStoreUserEndCategoryScrm> selectListPage(FsStoreUserEndCategoryScrm query);
+
+    /** 添加商品时选择:按金刚区/瀑布流分组,各最多8条(仅展示用由前端截取) */
+    List<FsStoreUserEndCategoryScrm> selectListForProduct(Long storeId);
+
+    /** 用户端首页:按位置(1金刚区 2瀑布流)取前8条,status=1,排序 sort asc, create_time desc */
+    List<FsStoreUserEndCategoryScrm> selectTop8ByPosition(Long storeId, Integer position);
+
+    /** 按用户端分类ID分页查询关联商品(去重商品ID分页,再查商品简表+标签并组装) */
+    List<FsStoreUserEndCategoryProductVO> listProductsByCategoryId(Long categoryId);
+
+    /** 首页商品列表:id 为空查全部(分页商品ID后查简表+标签),id 不为空按用户端分类查;返回 list+total */
+    java.util.Map<String, Object> listProductsForApp(Long id, Integer pageNum, Integer pageSize);
+
+    int insert(FsStoreUserEndCategoryScrm entity);
+
+    int update(FsStoreUserEndCategoryScrm entity);
+
+    int deleteById(Long id);
+
+    int deleteByIds(Long[] ids);
+}

+ 60 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -127,6 +127,11 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     @Autowired
     private FsStoreProductCategoryScrmMapper fsStoreProductCategoryScrmMapper;
 
+    @Autowired
+    private FsStoreProductUserEndCategoryMapper fsStoreProductUserEndCategoryMapper;
+    @Autowired
+    private FsStoreProductTagRelationScrmMapper fsStoreProductTagRelationScrmMapper;
+
     @Autowired
     @Lazy
     private ILiveService liveService;
@@ -252,6 +257,14 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     {
         storeAuditLogUtil.addBatchAuditArray(productIds, "", "");
         log.info("批量删除商品:{}", productIds);
+        if (productIds != null) {
+            for (Long pid : productIds) {
+                if (pid != null) {
+                    fsStoreProductUserEndCategoryMapper.deleteByProductId(pid);
+                    fsStoreProductTagRelationScrmMapper.deleteByProductId(pid);
+                }
+            }
+        }
         int result = fsStoreProductMapper.deleteFsStoreProductByIds(productIds);
 
         // 清除缓存
@@ -512,6 +525,10 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     @Override
     public int deleteFsStoreProductById(Long productId)
     {
+        if (productId != null) {
+            fsStoreProductUserEndCategoryMapper.deleteByProductId(productId);
+            fsStoreProductTagRelationScrmMapper.deleteByProductId(productId);
+        }
         int result = fsStoreProductMapper.deleteFsStoreProductById(productId);
         // 清除缓存
         clearProductDetailCache(productId);
@@ -759,6 +776,19 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         } else {
             addProductAttr(product.getProductId(),param.getItems(),param.getValues());
         }
+        // 保存用户端分类关联
+        fsStoreProductUserEndCategoryMapper.deleteByProductId(product.getProductId());
+        if (param.getUserEndCategoryIds() != null && !param.getUserEndCategoryIds().isEmpty()) {
+            fsStoreProductUserEndCategoryMapper.insertBatch(product.getProductId(), param.getUserEndCategoryIds());
+        }
+        // 保存商品标签关联(最多3个)
+        fsStoreProductTagRelationScrmMapper.deleteByProductId(product.getProductId());
+        if (param.getTagIds() != null && !param.getTagIds().isEmpty()) {
+            if (param.getTagIds().size() > 3) {
+                throw new ServiceException("商品标签最多选择3个");
+            }
+            fsStoreProductTagRelationScrmMapper.insertBatch(product.getProductId(), param.getTagIds());
+        }
         // 数据修改缓存
         if (product.getProductId() != null) {
             FsStoreProductScrm cacheProduct = fsStoreProductMapper.selectFsStoreProductById(product.getProductId());
@@ -1141,6 +1171,24 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         return fsStoreProductMapper.selectFsStoreProductHotQuery(map);
     }
 
+    @Override
+    public List<FsStoreProductListQueryVO> selectFsStoreProductNewQueryPage(int pageNum, int pageSize, String appId) {
+        HashMap<String, Object> map = new HashMap<>();
+        map.put("config", medicalMallConfig);
+        map.put("appId", appId);
+        com.github.pagehelper.PageHelper.startPage(pageNum, pageSize);
+        return fsStoreProductMapper.selectFsStoreProductNewQueryPage(map);
+    }
+
+    @Override
+    public List<FsStoreProductListQueryVO> selectFsStoreProductHotQueryPage(int pageNum, int pageSize, String appId) {
+        HashMap<String, Object> map = new HashMap<>();
+        map.put("config", medicalMallConfig);
+        map.put("appId", appId);
+        com.github.pagehelper.PageHelper.startPage(pageNum, pageSize);
+        return fsStoreProductMapper.selectFsStoreProductHotQueryPage(map);
+    }
+
     @Override
     public List<FsStoreProductListQueryVO> selectFsStoreProductGoodQuery(int count) {
         return fsStoreProductMapper.selectFsStoreProductGoodQuery(count,medicalMallConfig);
@@ -1636,4 +1684,16 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         }
         return R.ok();
     }
+
+    @Override
+    public List<Long> selectUserEndCategoryIdsByProductId(Long productId) {
+        if (productId == null) return Collections.emptyList();
+        return fsStoreProductUserEndCategoryMapper.selectCategoryIdsByProductId(productId);
+    }
+
+    @Override
+    public List<Long> selectTagIdsByProductId(Long productId) {
+        if (productId == null) return Collections.emptyList();
+        return fsStoreProductTagRelationScrmMapper.selectTagIdsByProductId(productId);
+    }
 }

+ 79 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductTagScrmServiceImpl.java

@@ -0,0 +1,79 @@
+package com.fs.hisStore.service.impl;
+
+import com.fs.common.utils.DateUtils;
+import com.fs.hisStore.domain.FsStoreProductTagScrm;
+import com.fs.hisStore.mapper.FsStoreProductTagRelationScrmMapper;
+import com.fs.hisStore.mapper.FsStoreProductTagScrmMapper;
+import com.fs.hisStore.service.IFsStoreProductTagScrmService;
+import com.fs.hisStore.vo.FsStoreProductTagListVO;
+import com.fs.hisStore.vo.FsStoreTagProductVO;
+import com.fs.common.exception.ServiceException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 商品标签 Service 实现
+ */
+@Service
+public class FsStoreProductTagScrmServiceImpl implements IFsStoreProductTagScrmService {
+
+    @Autowired
+    private FsStoreProductTagScrmMapper mapper;
+
+    @Autowired
+    private FsStoreProductTagRelationScrmMapper productTagRelationMapper;
+
+    @Override
+    public FsStoreProductTagScrm selectById(Long id) {
+        return mapper.selectById(id);
+    }
+
+    @Override
+    public List<FsStoreProductTagListVO> selectListVO(FsStoreProductTagScrm query) {
+        return mapper.selectListVO(query);
+    }
+
+    @Override
+    public List<FsStoreProductTagListVO> selectListVOPage(FsStoreProductTagScrm query) {
+        return mapper.selectListVO(query);
+    }
+
+    @Override
+    public List<FsStoreTagProductVO> selectProductsByTagId(Long tagId) {
+        return mapper.selectProductsByTagId(tagId);
+    }
+
+    @Override
+    public int insert(FsStoreProductTagScrm entity) {
+        if (entity.getTagName() != null && entity.getTagName().length() > 4) {
+            throw new ServiceException("标签名称最多4个字");
+        }
+        entity.setCreateTime(DateUtils.getNowDate());
+        entity.setUpdateTime(DateUtils.getNowDate());
+        return mapper.insert(entity);
+    }
+
+    @Override
+    public int update(FsStoreProductTagScrm entity) {
+        if (entity.getTagName() != null && entity.getTagName().length() > 4) {
+            throw new ServiceException("标签名称最多4个字");
+        }
+        entity.setUpdateTime(DateUtils.getNowDate());
+        return mapper.update(entity);
+    }
+
+    @Override
+    public int deleteById(Long id) {
+        productTagRelationMapper.deleteByTagIds(new Long[]{id});
+        return mapper.deleteById(id);
+    }
+
+    @Override
+    public int deleteByIds(Long[] ids) {
+        if (ids == null || ids.length == 0) return 0;
+        productTagRelationMapper.deleteByTagIds(ids);
+        return mapper.deleteByIds(ids);
+    }
+}

+ 163 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreUserEndCategoryScrmServiceImpl.java

@@ -0,0 +1,163 @@
+package com.fs.hisStore.service.impl;
+
+import com.fs.common.utils.DateUtils;
+import com.fs.hisStore.domain.FsStoreProductScrm;
+import com.fs.hisStore.domain.FsStoreUserEndCategoryScrm;
+import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
+import com.fs.hisStore.mapper.FsStoreProductTagRelationScrmMapper;
+import com.fs.hisStore.mapper.FsStoreProductUserEndCategoryMapper;
+import com.fs.hisStore.mapper.FsStoreUserEndCategoryScrmMapper;
+import com.fs.hisStore.service.IFsStoreUserEndCategoryScrmService;
+import com.fs.hisStore.vo.FsStoreProductTagNameVO;
+import com.fs.hisStore.vo.FsStoreUserEndCategoryProductVO;
+import com.github.pagehelper.Page;
+import com.github.pagehelper.PageHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 商品用户分端类 Service 实现
+ */
+@Service
+public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCategoryScrmService {
+
+    @Autowired
+    private FsStoreUserEndCategoryScrmMapper mapper;
+
+    @Autowired
+    private FsStoreProductUserEndCategoryMapper productUserEndCategoryMapper;
+
+    @Autowired
+    private FsStoreProductScrmMapper fsStoreProductScrmMapper;
+
+    @Autowired
+    private FsStoreProductTagRelationScrmMapper productTagRelationMapper;
+
+    @Override
+    public List<FsStoreUserEndCategoryProductVO> listProductsByCategoryId(Long categoryId) {
+        if (categoryId == null) return new ArrayList<>();
+        List<Long> productIds = productUserEndCategoryMapper.selectDistinctProductIdsByCategoryId(categoryId);
+        if (productIds == null || productIds.isEmpty()) return new ArrayList<>();
+        List<FsStoreProductScrm> products = fsStoreProductScrmMapper.getStoreProductInProductIds(productIds);
+        Map<Long, FsStoreProductScrm> productMap = products.stream().collect(Collectors.toMap(FsStoreProductScrm::getProductId, p -> p, (a, b) -> a));
+        List<FsStoreProductTagNameVO> tagNames = productTagRelationMapper.selectProductTagNamesByProductIds(productIds);
+        Map<Long, List<String>> tagMap = new LinkedHashMap<>();
+        for (FsStoreProductTagNameVO tn : tagNames) {
+            tagMap.computeIfAbsent(tn.getProductId(), k -> new ArrayList<>()).add(tn.getTagName());
+        }
+        List<FsStoreUserEndCategoryProductVO> result = new ArrayList<>();
+        for (Long pid : productIds) {
+            FsStoreProductScrm p = productMap.get(pid);
+            if (p == null) continue;
+            FsStoreUserEndCategoryProductVO vo = new FsStoreUserEndCategoryProductVO();
+            vo.setProductId(p.getProductId());
+            vo.setProductName(p.getProductName());
+            vo.setImage(p.getImage());
+            vo.setPrice(p.getPrice());
+            vo.setOtPrice(p.getOtPrice());
+            vo.setSales(p.getSales());
+            vo.setTagList(tagMap.getOrDefault(pid, new ArrayList<>()));
+            result.add(vo);
+        }
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> listProductsForApp(Long id, Integer pageNum, Integer pageSize) {
+        Map<String, Object> out = new HashMap<>();
+        out.put("list", new ArrayList<FsStoreUserEndCategoryProductVO>());
+        out.put("total", 0L);
+        if (pageNum == null || pageSize == null || pageSize <= 0) return out;
+        PageHelper.startPage(pageNum, pageSize);
+        List<Long> productIds = (id != null && id != 0L)
+                ? productUserEndCategoryMapper.selectDistinctProductIdsByCategoryId(id)
+                : productUserEndCategoryMapper.selectDistinctProductIds();
+        long total = productIds instanceof Page ? ((Page<?>) productIds).getTotal() : (productIds != null ? productIds.size() : 0);
+        if (productIds == null || productIds.isEmpty()) {
+            out.put("total", total);
+            return out;
+        }
+        List<FsStoreProductScrm> products = fsStoreProductScrmMapper.getStoreProductInProductIdsForApp(productIds);
+        Map<Long, FsStoreProductScrm> productMap = products.stream().collect(Collectors.toMap(FsStoreProductScrm::getProductId, p -> p, (a, b) -> a));
+        List<FsStoreProductTagNameVO> tagNames = productTagRelationMapper.selectProductTagNamesByProductIds(productIds);
+        Map<Long, List<String>> tagMap = new LinkedHashMap<>();
+        for (FsStoreProductTagNameVO tn : tagNames) {
+            tagMap.computeIfAbsent(tn.getProductId(), k -> new ArrayList<>()).add(tn.getTagName());
+        }
+        List<FsStoreUserEndCategoryProductVO> result = new ArrayList<>();
+        for (Long pid : productIds) {
+            FsStoreProductScrm p = productMap.get(pid);
+            if (p == null) continue;
+            FsStoreUserEndCategoryProductVO vo = new FsStoreUserEndCategoryProductVO();
+            vo.setProductId(p.getProductId());
+            vo.setProductName(p.getProductName());
+            vo.setImage(p.getImage());
+            vo.setPrice(p.getPrice());
+            vo.setOtPrice(p.getOtPrice());
+            vo.setSales(p.getSales());
+            vo.setTagList(tagMap.getOrDefault(pid, new ArrayList<>()));
+            result.add(vo);
+        }
+        out.put("list", result);
+        out.put("total", total);
+        return out;
+    }
+
+    @Override
+    public FsStoreUserEndCategoryScrm selectById(Long id) {
+        return mapper.selectById(id);
+    }
+
+    @Override
+    public List<FsStoreUserEndCategoryScrm> selectList(FsStoreUserEndCategoryScrm query) {
+        return mapper.selectList(query);
+    }
+
+    @Override
+    public List<FsStoreUserEndCategoryScrm> selectListPage(FsStoreUserEndCategoryScrm query) {
+        return mapper.selectList(query);
+    }
+
+    @Override
+    public List<FsStoreUserEndCategoryScrm> selectListForProduct(Long storeId) {
+        return mapper.selectListForProduct(storeId);
+    }
+
+    @Override
+    public List<FsStoreUserEndCategoryScrm> selectTop8ByPosition(Long storeId, Integer position) {
+        return mapper.selectTop8ByPosition(storeId, position);
+    }
+
+    @Override
+    public int insert(FsStoreUserEndCategoryScrm entity) {
+        entity.setCreateTime(DateUtils.getNowDate());
+        entity.setUpdateTime(DateUtils.getNowDate());
+        return mapper.insert(entity);
+    }
+
+    @Override
+    public int update(FsStoreUserEndCategoryScrm entity) {
+        entity.setUpdateTime(DateUtils.getNowDate());
+        return mapper.update(entity);
+    }
+
+    @Override
+    public int deleteById(Long id) {
+        productUserEndCategoryMapper.deleteByCategoryIds(new Long[]{id});
+        return mapper.deleteById(id);
+    }
+
+    @Override
+    public int deleteByIds(Long[] ids) {
+        if (ids == null || ids.length == 0) return 0;
+        productUserEndCategoryMapper.deleteByCategoryIds(ids);
+        return mapper.deleteByIds(ids);
+    }
+}

+ 23 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductTagListVO.java

@@ -0,0 +1,23 @@
+package com.fs.hisStore.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 商品标签列表VO(含关联商品数量)
+ */
+@Data
+public class FsStoreProductTagListVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+    private Long storeId;
+    private String tagName;
+    private Integer status;
+    private Long sort;
+    private java.util.Date createTime;
+    /** 关联商品数量 */
+    private Integer productCount;
+}

+ 17 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductTagNameVO.java

@@ -0,0 +1,17 @@
+package com.fs.hisStore.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 商品-标签名称(用于按商品ID批量查标签)
+ */
+@Data
+public class FsStoreProductTagNameVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long productId;
+    private String tagName;
+}

+ 22 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreTagProductVO.java

@@ -0,0 +1,22 @@
+package com.fs.hisStore.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 标签关联商品项(弹窗展示:商品ID、名称、主图、售价、状态)
+ */
+@Data
+public class FsStoreTagProductVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long productId;
+    private String productName;
+    private String image;
+    private BigDecimal price;
+    /** 状态:0-未上架 1-上架 */
+    private Integer isShow;
+}

+ 29 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreUserEndCategoryProductVO.java

@@ -0,0 +1,29 @@
+package com.fs.hisStore.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 用户端分类下的商品项:商品ID、名称、售价、原价、销量、产品标签列表
+ */
+@Data
+public class FsStoreUserEndCategoryProductVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long productId;
+    private String productName;
+    /** 主图(App 列表用) */
+    private String image;
+    /** 实际销售价格 */
+    private BigDecimal price;
+    /** 原价 */
+    private BigDecimal otPrice;
+    /** 销量 */
+    private Long sales;
+    /** 产品标签名称列表 */
+    private List<String> tagList;
+}

+ 8 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveDataMapper.java

@@ -10,6 +10,7 @@ import com.fs.live.vo.LiveDataDetailVo;
 import com.fs.live.vo.LiveDataListVo;
 import com.fs.live.vo.LiveDataStatisticsVo;
 import com.fs.live.vo.LiveUserDetailVo;
+import com.fs.live.vo.LiveAppSimpleVO;
 import com.fs.live.vo.RecentLiveDataVo;
 import com.fs.live.vo.TrendDataVO;
 import org.apache.ibatis.annotations.Param;
@@ -238,4 +239,11 @@ public interface LiveDataMapper {
     @DataSource(DataSourceType.SLAVE)
     List<LiveDataCompanyVO> selectCompanyEmployeeCountByLiveIds(@Param("liveIds") List<Long> liveIds,
                                                                 @Param("companyIds") List<Long> companyIds);
+
+    /**
+     * 查询正在直播的直播间(仅 liveId、liveType、封面、标题),用于首页推荐
+     * status=2 表示直播中
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<LiveAppSimpleVO> selectLivingLivesForApp();
 }

+ 5 - 0
fs-service/src/main/java/com/fs/live/service/ILiveDataService.java

@@ -171,6 +171,11 @@ public interface ILiveDataService {
 
     List<LiveDataListVo> exportLiveData(LiveDataParam param);
 
+    /**
+     * 首页推荐:正在直播的直播间列表,仅返回 liveId/liveType/封面/标题;当 liveType=2 时 liveFlag 从 Redis 查询
+     */
+    List<LiveAppSimpleVO> listLivingLivesForApp();
+
     /**
      * 查询分公司直播数据统计列表
      * @param param 查询参数

+ 24 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java

@@ -16,6 +16,7 @@ import com.fs.live.service.ILiveDataService;
 import com.fs.live.service.ILiveUserFavoriteService;
 import com.fs.live.service.ILiveUserFollowService;
 import com.fs.live.service.ILiveUserLikeService;
+import com.fs.live.service.ILiveWatchUserService;
 import com.fs.live.vo.*;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
@@ -78,6 +79,8 @@ public class LiveDataServiceImpl implements ILiveDataService {
     @Autowired
     private ILiveUserFavoriteService liveUserFavoriteService;
     @Autowired
+    private ILiveWatchUserService liveWatchUserService;
+    @Autowired
     private LiveDataMapper baseMapper;
     @Autowired
     private LiveUserFirstEntryMapper liveUserFirstEntryMapper;
@@ -571,6 +574,27 @@ public class LiveDataServiceImpl implements ILiveDataService {
         return baseMapper.getAllLiveDatas();
     }
 
+    @Override
+    public List<LiveAppSimpleVO> listLivingLivesForApp() {
+        List<LiveAppSimpleVO> list = liveDataMapper.selectLivingLivesForApp();
+        if (list == null || list.isEmpty()) {
+            return list;
+        }
+        for (LiveAppSimpleVO vo : list) {
+            if (vo.getLiveType() != null && vo.getLiveType() == 2 && vo.getLiveId() != null) {
+                try {
+                    Map<String, Integer> flagMap = liveWatchUserService.getLiveFlagWithCache(vo.getLiveId());
+                    if (flagMap != null && flagMap.containsKey("liveFlag")) {
+                        vo.setLiveFlag(flagMap.get("liveFlag"));
+                    }
+                } catch (Exception e) {
+                    log.warn("getLiveFlagWithCache fail liveId={}", vo.getLiveId(), e);
+                }
+            }
+        }
+        return list;
+    }
+
     @Override
     public void updateBatchById(List<LiveData> liveDatas) {
         baseMapper.updateBatchLiveData(liveDatas);

+ 18 - 0
fs-service/src/main/java/com/fs/live/vo/LiveAppSimpleVO.java

@@ -0,0 +1,18 @@
+package com.fs.live.vo;
+
+import lombok.Data;
+
+/**
+ * 首页推荐-正在直播的直播间简要信息(liveId、liveType、封面、标题、liveFlag当liveType=2时从Redis取)
+ */
+@Data
+public class LiveAppSimpleVO {
+    private Long liveId;
+    private Integer liveType;
+    /** 直播间封面图片 */
+    private String liveImgUrl;
+    /** 直播间标题 */
+    private String liveName;
+    /** 当 liveType=2 时由 Redis 查询填充 */
+    private Integer liveFlag;
+}

+ 1 - 1
fs-service/src/main/resources/application-config-druid-bjzm-test.yml

@@ -91,7 +91,7 @@ cloud_host:
   company_name: 北京卓美
   projectCode: BJZM
   spaceName:
-  volcengineUrl: https://myhkvolcengine.ylrztop.com
+  volcengineUrl: https://bjzmvolcengine.ylrztop.com
 headerImg:
   imgUrl:
 

+ 3 - 3
fs-service/src/main/resources/application-config-druid-bjzm.yml

@@ -37,8 +37,8 @@ wx:
       port: 6379
       timeout: 2000
     configs:
-      - appId: wxc8534f3a7c4f306c # 第一个公众号的appid  //公众号名称:德瑞康
-        secret: 7a4bac8d7628c2adf70575628826e2b8 # 公众号的appsecret--德瑞康
+      - appId: wx346276e5e6abd1a5 # 第一个公众号的appid  //公众号名称:德瑞康
+        secret: 4591d841f1df62bd97827ec0de1f7a93 # 公众号的appsecret--德瑞康
         token: PPKOdAlCoMO # 接口配置里的Token值
         aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
 aifabu:  #爱链接
@@ -86,7 +86,7 @@ cloud_host:
   company_name: 北京卓美
   projectCode: BJZM
   spaceName:
-  volcengineUrl: https://myhkvolcengine.ylrztop.com
+  volcengineUrl: https://bjzmvolcengine.ylrztop.com
 headerImg:
   imgUrl:
 

+ 41 - 2
fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml

@@ -517,11 +517,25 @@
         <if test='config.isAudit == "1"'>
             and p.is_audit = '1'
         </if>
-        <if test='appId != null and appId = "" '>
+        <if test='appId != null and appId != "" '>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
         and p.is_new=1 and p.is_display=1 order by p.sort desc limit #{count}
     </select>
+    <select id="selectFsStoreProductNewQueryPage" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
+        select p.* from fs_store_product_scrm p
+        <if test='config.isAudit == "1"'>
+            inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
+        </if>
+        where p.is_del=0 and p.is_show=1
+        <if test='config.isAudit == "1"'>
+            and p.is_audit = '1'
+        </if>
+        <if test='appId != null and appId != ""'>
+            and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
+        </if>
+        and p.is_new=1 and p.is_display=1 order by p.sort desc
+    </select>
     <select id="selectFsStoreProductHotQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
         <if test='config.isAudit == "1" '>
@@ -531,11 +545,25 @@
         <if test='config.isAudit == "1" '>
             and p.is_audit = '1'
         </if>
-        <if test='appId != null and appId = "" '>
+        <if test='appId != null and appId != "" '>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
         and  p.is_hot=1 and p.is_display=1 order by p.sort desc limit #{count}
     </select>
+    <select id="selectFsStoreProductHotQueryPage" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
+        select p.* from fs_store_product_scrm p
+        <if test='config.isAudit == "1" '>
+            inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
+        </if>
+        where p.is_del=0 and p.is_show=1
+        <if test='config.isAudit == "1" '>
+            and p.is_audit = '1'
+        </if>
+        <if test='appId != null and appId != ""'>
+            and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
+        </if>
+        and  p.is_hot=1 and p.is_display=1 order by p.sort desc
+    </select>
     <select id="selectFsStoreProductGoodListQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
         <if test='config.isAudit == "1" '>
@@ -557,4 +585,15 @@
         #{item}
     </foreach>
     </select>
+
+    <select id="getStoreProductInProductIdsForApp" resultType="com.fs.hisStore.domain.FsStoreProductScrm">
+        <include refid="selectFsStoreProductVo"/>
+        where is_del = 0 and is_show = 1 and is_audit = 1
+        and product_id IN
+        <foreach collection="productIds" index="index" item="item" open="(" separator="," close=")">#{item}</foreach>
+    </select>
+
+    <select id="selectProductIdsForApp" resultType="java.lang.Long">
+        select product_id from fs_store_product_scrm where is_del = 0 and is_show = 1 and is_audit = 1  order by sort desc, product_id desc
+    </select>
 </mapper>

+ 34 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreProductTagRelationScrmMapper.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.hisStore.mapper.FsStoreProductTagRelationScrmMapper">
+
+    <insert id="insertBatch">
+        insert into fs_store_product_tag_relation_scrm (product_id, tag_id) values
+        <foreach collection="tagIds" item="tid" separator=",">(#{productId}, #{tid})</foreach>
+    </insert>
+
+    <delete id="deleteByProductId">
+        delete from fs_store_product_tag_relation_scrm where product_id = #{productId}
+    </delete>
+
+    <select id="selectTagIdsByProductId" resultType="java.lang.Long">
+        select tag_id from fs_store_product_tag_relation_scrm where product_id = #{productId}
+    </select>
+
+    <select id="countByTagId" resultType="int">
+        select count(*) from fs_store_product_tag_relation_scrm where tag_id = #{tagId}
+    </select>
+
+    <delete id="deleteByTagIds">
+        delete from fs_store_product_tag_relation_scrm where tag_id in
+        <foreach collection="tagIds" item="id" open="(" separator="," close=")">#{id}</foreach>
+    </delete>
+
+    <select id="selectProductTagNamesByProductIds" resultType="com.fs.hisStore.vo.FsStoreProductTagNameVO">
+        select r.product_id as productId, t.tag_name as tagName
+        from fs_store_product_tag_relation_scrm r
+        inner join fs_store_product_tag_scrm t on r.tag_id = t.id and t.status = 1
+        where r.product_id in
+        <foreach collection="productIds" item="pid" open="(" separator="," close=")">#{pid}</foreach>
+    </select>
+</mapper>

+ 65 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreProductTagScrmMapper.xml

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.hisStore.mapper.FsStoreProductTagScrmMapper">
+
+    <resultMap id="BaseResultMap" type="com.fs.hisStore.domain.FsStoreProductTagScrm">
+        <id property="id" column="id"/>
+        <result property="storeId" column="store_id"/>
+        <result property="tagName" column="tag_name"/>
+        <result property="status" column="status"/>
+        <result property="sort" column="sort"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">id, store_id, tag_name, status, sort, create_time, update_time</sql>
+
+    <select id="selectById" resultMap="BaseResultMap">
+        select <include refid="Base_Column_List"/> from fs_store_product_tag_scrm where id = #{id}
+    </select>
+
+    <select id="selectListVO" resultType="com.fs.hisStore.vo.FsStoreProductTagListVO">
+        select t.id, t.store_id, t.tag_name, t.status, t.sort, t.create_time,
+               (select count(*) from fs_store_product_tag_relation_scrm r where r.tag_id = t.id) as product_count
+        from fs_store_product_tag_scrm t
+        <where>
+            <if test="storeId != null">and t.store_id = #{storeId}</if>
+            <if test="tagName != null and tagName != ''">and t.tag_name like concat('%', #{tagName}, '%')</if>
+            <if test="status != null">and t.status = #{status}</if>
+        </where>
+        order by t.sort asc, t.create_time desc
+    </select>
+
+    <insert id="insert" parameterType="com.fs.hisStore.domain.FsStoreProductTagScrm" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_store_product_tag_scrm (store_id, tag_name, status, sort, create_time, update_time)
+        values (#{storeId}, #{tagName}, #{status}, #{sort}, #{createTime}, #{updateTime})
+    </insert>
+
+    <update id="update" parameterType="com.fs.hisStore.domain.FsStoreProductTagScrm">
+        update fs_store_product_tag_scrm
+        <set>
+            <if test="tagName != null">tag_name = #{tagName},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="sort != null">sort = #{sort},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </set>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteById">
+        delete from fs_store_product_tag_scrm where id = #{id}
+    </delete>
+
+    <delete id="deleteByIds">
+        delete from fs_store_product_tag_scrm where id in
+        <foreach collection="array" item="id" open="(" separator="," close=")">#{id}</foreach>
+    </delete>
+
+    <select id="selectProductsByTagId" resultType="com.fs.hisStore.vo.FsStoreTagProductVO">
+        select p.product_id as productId, p.product_name as productName, p.image as image, p.price as price, p.is_show as isShow
+        from fs_store_product_scrm p
+        inner join fs_store_product_tag_relation_scrm r on p.product_id = r.product_id
+        where r.tag_id = #{tagId} and (p.is_del is null or p.is_del = 0)
+        order by p.create_time desc
+    </select>
+</mapper>

+ 34 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreProductUserEndCategoryMapper.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.hisStore.mapper.FsStoreProductUserEndCategoryMapper">
+
+    <insert id="insertBatch">
+        insert into fs_store_product_user_end_category (product_id, user_end_category_id) values
+        <foreach collection="categoryIds" item="cid" separator=",">(#{productId}, #{cid})</foreach>
+    </insert>
+
+    <delete id="deleteByProductId">
+        delete from fs_store_product_user_end_category where product_id = #{productId}
+    </delete>
+
+    <select id="selectCategoryIdsByProductId" resultType="java.lang.Long">
+        select user_end_category_id from fs_store_product_user_end_category where product_id = #{productId}
+    </select>
+
+    <delete id="deleteByCategoryIds">
+        delete from fs_store_product_user_end_category where user_end_category_id in
+        <foreach collection="categoryIds" item="id" open="(" separator="," close=")">#{id}</foreach>
+    </delete>
+
+    <select id="selectDistinctProductIdsByCategoryId" resultType="java.lang.Long">
+        select distinct a.product_id from fs_store_product_user_end_category a left join fs_store_product_scrm c on a.product_id = c.product_id
+        where a.user_end_category_id = #{categoryId} and c.is_del = 0 and c.is_show = 1
+        order by c.sort desc, c.create_time desc, a.product_id
+    </select>
+
+    <select id="selectDistinctProductIds" resultType="java.lang.Long">
+        select distinct a.product_id from fs_store_product_user_end_category  a left join fs_store_product_scrm c on a.product_id = c.product_id
+        where c.is_del = 0 and c.is_show = 1
+        order by c.sort desc, c.create_time desc, a.product_id
+    </select>
+</mapper>

+ 75 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreUserEndCategoryScrmMapper.xml

@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.hisStore.mapper.FsStoreUserEndCategoryScrmMapper">
+
+    <resultMap id="BaseResultMap" type="com.fs.hisStore.domain.FsStoreUserEndCategoryScrm">
+        <id property="id" column="id"/>
+        <result property="storeId" column="store_id"/>
+        <result property="categoryName" column="category_name"/>
+        <result property="position" column="position"/>
+        <result property="icon" column="icon"/>
+        <result property="status" column="status"/>
+        <result property="sort" column="sort"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">id, store_id, category_name, position, icon, status, sort, create_time, update_time</sql>
+
+    <select id="selectById" resultMap="BaseResultMap">
+        select <include refid="Base_Column_List"/> from fs_store_user_end_category_scrm where id = #{id}
+    </select>
+
+    <select id="selectList" resultMap="BaseResultMap">
+        select <include refid="Base_Column_List"/> from fs_store_user_end_category_scrm
+        <where>
+            <if test="storeId != null">and store_id = #{storeId}</if>
+            <if test="categoryName != null and categoryName != ''">and category_name like concat('%', #{categoryName}, '%')</if>
+            <if test="position != null">and position = #{position}</if>
+            <if test="status != null">and status = #{status}</if>
+        </where>
+        order by sort asc, create_time desc
+    </select>
+
+    <select id="selectListForProduct" resultMap="BaseResultMap">
+        select <include refid="Base_Column_List"/> from fs_store_user_end_category_scrm
+        where status = 1
+        <if test="storeId != null">and store_id = #{storeId}</if>
+        order by position asc, sort asc, create_time desc
+    </select>
+
+    <select id="selectTop8ByPosition" resultMap="BaseResultMap">
+        select <include refid="Base_Column_List"/> from fs_store_user_end_category_scrm
+        where status = 1 and position = #{position}
+        <if test="storeId != null">and store_id = #{storeId}</if>
+        order by sort asc, create_time desc
+        limit 8
+    </select>
+
+    <insert id="insert" parameterType="com.fs.hisStore.domain.FsStoreUserEndCategoryScrm" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_store_user_end_category_scrm (store_id, category_name, position, icon, status, sort, create_time, update_time)
+        values (#{storeId}, #{categoryName}, #{position}, #{icon}, #{status}, #{sort}, #{createTime}, #{updateTime})
+    </insert>
+
+    <update id="update" parameterType="com.fs.hisStore.domain.FsStoreUserEndCategoryScrm">
+        update fs_store_user_end_category_scrm
+        <set>
+            <if test="categoryName != null">category_name = #{categoryName},</if>
+            <if test="position != null">position = #{position},</if>
+            <if test="icon != null">icon = #{icon},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="sort != null">sort = #{sort},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </set>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteById">
+        delete from fs_store_user_end_category_scrm where id = #{id}
+    </delete>
+
+    <delete id="deleteByIds">
+        delete from fs_store_user_end_category_scrm where id in
+        <foreach collection="array" item="id" open="(" separator="," close=")">#{id}</foreach>
+    </delete>
+</mapper>

+ 8 - 0
fs-service/src/main/resources/mapper/live/LiveDataMapper.xml

@@ -52,6 +52,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                  inner join live B on A.live_id = B.live_id
         where B.status in (1,2,4) and b.is_audit = 1 and b.is_show = 1 and is_del=0
     </select>
+
+    <!-- 首页推荐:正在直播的直播间,仅查 liveId、liveType、封面、标题 -->
+    <select id="selectLivingLivesForApp" resultType="com.fs.live.vo.LiveAppSimpleVO">
+        SELECT live_id AS liveId, live_type AS liveType, live_img_url AS liveImgUrl, live_name AS liveName
+        FROM live
+        WHERE status = 2 AND is_show = 1 AND (is_del = 0 OR is_del IS NULL) AND is_audit = 1
+        ORDER BY start_time DESC
+    </select>
     <select id="getRecentLive" resultType="com.fs.live.vo.RecentLiveDataVo">
         SELECT A.page_views AS pageViews,A.unique_visitors AS uniqueVisitors, B.live_id AS liveId,B.live_name AS liveName,B.`status` AS status,B.live_img_url AS liveImgUrl,B.start_time AS startTime
         FROM live_data A LEFT JOIN live B ON A.live_id = B.live_id ORDER BY B.start_time LIMIT 4

+ 14 - 4
fs-user-app/src/main/java/com/fs/app/controller/IndexController.java

@@ -10,6 +10,8 @@ import com.fs.his.domain.*;
 import com.fs.his.param.*;
 import com.fs.his.service.*;
 import com.fs.his.vo.*;
+import com.fs.hisStore.domain.FsStoreUserEndCategoryScrm;
+import com.fs.hisStore.service.IFsStoreUserEndCategoryScrmService;
 import com.fs.store.config.ConceptConfig;
 import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
@@ -53,6 +55,9 @@ public class IndexController extends AppBaseController {
 	@Autowired
 	private IFsChineseMedicineService chineseMedicineService;
 
+	@Autowired
+	private IFsStoreUserEndCategoryScrmService userEndCategoryScrmService;
+
 	@ApiOperation("获取名方列表")
 	@Cacheable(value = "getFamousPrescribeList", key = "#param")
 	@GetMapping("/getFamousPrescribeList")
@@ -146,14 +151,19 @@ public class IndexController extends AppBaseController {
 
 	/**
 	 * 首页初始化核心数据(频道入口、分类标签、商品分类导航)
-	 * 合并请求,提升首屏加载速度
+	 * channelList:金刚区用户端分类,按排序和创建时间取前8个
+	 * categoryTags:瀑布流用户端分类,按排序和创建时间取前8个
 	 */
 	@ApiOperation("首页初始化")
 	@GetMapping("/home/init")
-	public R homeInit() {
+	public R homeInit(@RequestParam(value = "storeId", required = false) Long storeId) {
 		Map<String, Object> data = new HashMap<>();
-		data.put("channelList", Collections.emptyList());
-		data.put("categoryTags", Collections.emptyList());
+		// 金刚区:前8条,排序 asc、创建时间 desc
+		List<FsStoreUserEndCategoryScrm> channelList = userEndCategoryScrmService.selectTop8ByPosition(storeId, 1);
+		// 瀑布流:前8条,排序 asc、创建时间 desc
+		List<FsStoreUserEndCategoryScrm> categoryTags = userEndCategoryScrmService.selectTop8ByPosition(storeId, 2);
+		data.put("channelList", channelList != null ? channelList : Collections.emptyList());
+		data.put("categoryTags", categoryTags != null ? categoryTags : Collections.emptyList());
 		data.put("goodsNav", Collections.emptyList());
 		return R.ok().put("data", data);
 	}

+ 83 - 1
fs-user-app/src/main/java/com/fs/app/controller/store/IndexScrmController.java

@@ -8,6 +8,7 @@ import com.fs.app.annotation.Login;
 import com.fs.app.controller.AppBaseController;
 import com.fs.app.vo.IndexVO;
 import com.fs.common.core.domain.R;
+import com.fs.common.utils.StringUtils;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.service.IFsCoursePlaySourceConfigService;
 import com.fs.his.config.AgreementConfig;
@@ -15,6 +16,8 @@ import com.fs.hisStore.domain.*;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
 import com.fs.hisStore.vo.*;
+import com.fs.live.service.ILiveDataService;
+import com.fs.live.vo.LiveAppSimpleVO;
 import com.fs.store.config.ConceptConfig;
 import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
@@ -27,11 +30,13 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.servlet.ModelAndView;
 import springfox.documentation.annotations.Cacheable;
 
 import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 
 @Api("首页接口")
@@ -64,6 +69,83 @@ public class IndexScrmController extends AppBaseController {
 	@Autowired
 	private IFsCoursePlaySourceConfigService coursePlaySourceConfigService;
 
+	@Autowired
+	private IFsStoreUserEndCategoryScrmService userEndCategoryScrmService;
+
+	@Autowired
+	private ILiveDataService liveDataService;
+
+	/**
+	 * 首页初始化核心数据(频道入口、分类标签、商品分类导航)
+	 * channelList:金刚区用户端分类,按排序和创建时间取前8个
+	 * categoryTags:瀑布流用户端分类,按排序和创建时间取前8个
+	 */
+	@ApiOperation("首页初始化")
+	@GetMapping("/home/init")
+	public R homeInit(@RequestParam(value = "storeId", required = false) Long storeId) {
+		Map<String, Object> data = new HashMap<>();
+		// 金刚区:前8条,排序 asc、创建时间 desc
+		List<FsStoreUserEndCategoryScrm> categoryTags = userEndCategoryScrmService.selectTop8ByPosition(storeId, 1);
+		// 瀑布流:前8条,排序 asc、创建时间 desc
+		List<FsStoreUserEndCategoryScrm> goodsNav = userEndCategoryScrmService.selectTop8ByPosition(storeId, 2);
+		data.put("channelList", Collections.emptyList());
+		data.put("categoryTags", categoryTags != null ? categoryTags : Collections.emptyList());
+		data.put("goodsNav", goodsNav != null ? goodsNav : Collections.emptyList());
+		return R.ok().put("data", data);
+	}
+
+	/**
+	 * 首页推荐区块(直播中、上新推荐等,可延迟加载)
+	 * live:正在直播的直播间(liveId、liveType、封面、标题;liveType=2 时 liveFlag 从 Redis 查)
+	 * green:商品“新品首发”开启的 2 条
+	 * hot:商品“是否热卖”开启的 2 条
+	 */
+	@ApiOperation("首页推荐区块")
+	@GetMapping("/home/recommend")
+	public R homeRecommend(HttpServletRequest request) {
+		String appId = request.getParameter("appId");
+		List<LiveAppSimpleVO> liveList = liveDataService.listLivingLivesForApp();
+		List<FsStoreProductListQueryVO> greenProduct = productService.selectFsStoreProductNewQuery(2, appId);
+		List<FsStoreProductListQueryVO> hotProduct = productService.selectFsStoreProductHotQuery(2, appId);
+		return R.ok()
+				.put("live", liveList != null ? liveList : Collections.emptyList())
+				.put("green", greenProduct != null ? greenProduct : Collections.emptyList())
+				.put("hot", hotProduct != null ? hotProduct : Collections.emptyList());
+	}
+
+	/**
+	 * 首页商品列表(支持用户端分类 id,id 为空查全部)
+	 * 去重分页查商品ID → 查商品简表+标签 → 返回 productId、productName、image、price、otPrice、sales、tagList
+	 */
+	@ApiOperation("首页商品列表")
+	@GetMapping("/home/goods")
+	public R homeGoods(@RequestParam(value = "id", required = false) Long id,
+					   @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+					   @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
+		java.util.Map<String, Object> data = userEndCategoryScrmService.listProductsForApp(id, pageNum, pageSize);
+		return R.ok().put("data", data);
+	}
+
+	/**
+	 * 首页推荐「更多」分页:绿色有机(green) / 上新推荐(hot),逻辑同 homeRecommend 的 green、hot 查询,支持分页,默认第1页10条
+	 */
+	@ApiOperation("首页推荐更多分页")
+	@GetMapping("/home/goods/recommend")
+	public R homeGoodsRecommend(HttpServletRequest request,
+								@RequestParam(value = "type") String type,
+								@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+								@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
+		String appId = request.getParameter("appId");
+		if (!"green".equalsIgnoreCase(type) && !"hot".equalsIgnoreCase(type)) {
+			return R.ok().put("data", new PageInfo<>(Collections.emptyList()));
+		}
+		List<FsStoreProductListQueryVO> list = "green".equalsIgnoreCase(type)
+				? productService.selectFsStoreProductNewQueryPage(pageNum, pageSize, appId)
+				: productService.selectFsStoreProductHotQueryPage(pageNum, pageSize, appId);
+		PageInfo<FsStoreProductListQueryVO> pageInfo = new PageInfo<>(list);
+		return R.ok().put("data", pageInfo);
+	}
+
 	@ApiOperation("获取首页数据")
 	@GetMapping("/getIndexData")
 	public R getIndexData(HttpServletRequest request){