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

feat: add ProductCategory module (entity+mapper+service+controllers)

Add product category CRUD with shop-facing and admin-facing controllers.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
liaoxg 2 месяцев назад
Родитель
Сommit
62f24c84c4

+ 48 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminCategoryController.java

@@ -0,0 +1,48 @@
+package com.etotem.cfc.controller.admin;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.ProductCategory;
+import com.etotem.cfc.service.ProductCategoryService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/admin/shop/category")
+public class AdminCategoryController {
+
+    @Resource
+    private ProductCategoryService productCategoryService;
+
+    @PostMapping("/create")
+    public Result<String> create(@RequestBody ProductCategory category) {
+        return productCategoryService.create(category);
+    }
+
+    @PostMapping("/update")
+    public Result<String> update(@RequestBody ProductCategory category) {
+        return productCategoryService.update(category);
+    }
+
+    @PostMapping("/delete")
+    public Result<String> delete(@RequestBody Map<String, Object> body) {
+        Long id = Long.valueOf(body.get("id").toString());
+        return productCategoryService.delete(id);
+    }
+
+    @PostMapping("/tree")
+    public Result<List<Map<String, Object>>> tree() {
+        return productCategoryService.tree(true);
+    }
+
+    @PostMapping("/toggle")
+    public Result<String> toggle(@RequestBody Map<String, Object> body) {
+        Long id = Long.valueOf(body.get("id").toString());
+        return productCategoryService.toggle(id);
+    }
+}

+ 31 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/shop/ProductCategoryController.java

@@ -0,0 +1,31 @@
+package com.etotem.cfc.controller.shop;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.service.ProductCategoryService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/shop/category")
+public class ProductCategoryController {
+
+    @Resource
+    private ProductCategoryService productCategoryService;
+
+    @PostMapping("/tree")
+    public Result<List<Map<String, Object>>> tree() {
+        return productCategoryService.tree(false);
+    }
+
+    @PostMapping("/products")
+    public Result<List<Map<String, Object>>> products(@RequestBody Map<String, Object> body) {
+        Long categoryId = Long.valueOf(body.get("categoryId").toString());
+        return productCategoryService.products(categoryId);
+    }
+}

+ 26 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/ProductCategory.java

@@ -0,0 +1,26 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("shop_categories")
+public class ProductCategory implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String name;
+    private Long parentId;
+    private Integer level;
+    private Integer sort;
+    private String image;
+    private Boolean enabled;
+    private String dimensionCodes;
+    private String productTypes;
+    private Date createdAt;
+    private Date updatedAt;
+}

+ 7 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/ProductCategoryMapper.java

@@ -0,0 +1,7 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.ProductCategory;
+
+public interface ProductCategoryMapper extends BaseMapper<ProductCategory> {
+}

+ 139 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ProductCategoryService.java

@@ -0,0 +1,139 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.Product;
+import com.etotem.cfc.entity.ProductCategory;
+import com.etotem.cfc.mapper.ProductCategoryMapper;
+import com.etotem.cfc.mapper.ProductMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class ProductCategoryService {
+
+    @Resource
+    private ProductCategoryMapper productCategoryMapper;
+
+    @Resource
+    private ProductMapper productMapper;
+
+    public Result<List<Map<String, Object>>> tree(Boolean includeDisabled) {
+        List<ProductCategory> all = productCategoryMapper.selectList(
+            new LambdaQueryWrapper<ProductCategory>()
+                .eq(!Boolean.TRUE.equals(includeDisabled), ProductCategory::getEnabled, true)
+                .orderByAsc(ProductCategory::getSort)
+                .orderByAsc(ProductCategory::getId)
+        );
+        return Result.success(buildTree(all, 0L));
+    }
+
+    public Result<List<Map<String, Object>>> treeByParent(Long parentId) {
+        List<ProductCategory> list = productCategoryMapper.selectList(
+            new LambdaQueryWrapper<ProductCategory>()
+                .eq(ProductCategory::getParentId, parentId)
+                .orderByAsc(ProductCategory::getSort)
+                .orderByAsc(ProductCategory::getId)
+        );
+        List<Map<String, Object>> result = list.stream().map(this::toMap).collect(Collectors.toList());
+        return Result.success(result);
+    }
+
+    public Result<String> create(ProductCategory category) {
+        category.setId(null);
+        if (category.getParentId() == null) category.setParentId(0L);
+        if (category.getLevel() == null) category.setLevel(0);
+        if (category.getSort() == null) category.setSort(0);
+        if (category.getEnabled() == null) category.setEnabled(true);
+        category.setCreatedAt(new Date());
+        category.setUpdatedAt(new Date());
+        productCategoryMapper.insert(category);
+        return Result.success("创建成功");
+    }
+
+    public Result<String> update(ProductCategory category) {
+        ProductCategory existing = productCategoryMapper.selectById(category.getId());
+        if (existing == null) return Result.error("分类不存在");
+        category.setUpdatedAt(new Date());
+        productCategoryMapper.updateById(category);
+        return Result.success("更新成功");
+    }
+
+    public Result<String> toggle(Long id) {
+        ProductCategory category = productCategoryMapper.selectById(id);
+        if (category == null) return Result.error("分类不存在");
+        category.setEnabled(!Boolean.TRUE.equals(category.getEnabled()));
+        category.setUpdatedAt(new Date());
+        productCategoryMapper.updateById(category);
+        return Result.success(Boolean.TRUE.equals(category.getEnabled()) ? "已启用" : "已禁用");
+    }
+
+    public Result<String> delete(Long id) {
+        ProductCategory category = productCategoryMapper.selectById(id);
+        if (category == null) return Result.error("分类不存在");
+
+        boolean hasChildren = productCategoryMapper.selectCount(
+            new LambdaQueryWrapper<ProductCategory>().eq(ProductCategory::getParentId, id)
+        ) > 0;
+        if (hasChildren) return Result.error("存在子分类,无法删除");
+
+        boolean hasProducts = productMapper.selectCount(
+            new LambdaQueryWrapper<Product>().eq(Product::getCategoryId, id)
+        ) > 0;
+        if (hasProducts) return Result.error("该分类下存在商品,无法删除");
+
+        productCategoryMapper.deleteById(id);
+        return Result.success("删除成功");
+    }
+
+    public Result<List<Map<String, Object>>> products(Long categoryId) {
+        List<Product> products = productMapper.selectList(
+            new LambdaQueryWrapper<Product>()
+                .eq(Product::getCategoryId, categoryId)
+                .eq(Product::getStatus, "on_shelf")
+        );
+        List<Map<String, Object>> result = products.stream().map(p -> {
+            Map<String, Object> m = new LinkedHashMap<>();
+            m.put("id", p.getId());
+            m.put("name", p.getName());
+            m.put("coverImage", p.getCoverImage());
+            m.put("price", p.getPrice());
+            m.put("salesCount", p.getSalesCount());
+            m.put("productType", p.getProductType());
+            return m;
+        }).collect(Collectors.toList());
+        return Result.success(result);
+    }
+
+    private List<Map<String, Object>> buildTree(List<ProductCategory> all, Long parentId) {
+        List<Map<String, Object>> nodes = new ArrayList<>();
+        for (ProductCategory cat : all) {
+            if (cat.getParentId() != null && cat.getParentId().equals(parentId)) {
+                Map<String, Object> node = toMap(cat);
+                List<Map<String, Object>> children = buildTree(all, cat.getId());
+                if (!children.isEmpty()) {
+                    node.put("children", children);
+                }
+                nodes.add(node);
+            }
+        }
+        return nodes;
+    }
+
+    private Map<String, Object> toMap(ProductCategory cat) {
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("id", cat.getId());
+        m.put("name", cat.getName());
+        m.put("parentId", cat.getParentId());
+        m.put("level", cat.getLevel());
+        m.put("sort", cat.getSort());
+        m.put("image", cat.getImage());
+        m.put("enabled", cat.getEnabled());
+        m.put("dimensionCodes", cat.getDimensionCodes());
+        m.put("productTypes", cat.getProductTypes());
+        return m;
+    }
+}