Przeglądaj źródła

商品分类代码生成

JeromeSoar 3 lat temu
rodzic
commit
4e67b6bbcf
26 zmienionych plików z 1319 dodań i 4 usunięć
  1. 55 0
      sql/mall.sql
  2. 2 0
      yudao-module-mall/pom.xml
  3. 27 0
      yudao-module-mall/yudao-module-product-api/pom.xml
  4. 4 0
      yudao-module-mall/yudao-module-product-api/src/main/cn/iocoder/yudao/module/product/api/package-info.java
  5. 14 0
      yudao-module-mall/yudao-module-product-api/src/main/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
  6. 67 0
      yudao-module-mall/yudao-module-product-biz/pom.xml
  7. 100 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/CategoryController.java
  8. 42 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryBaseVO.java
  9. 14 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryCreateReqVO.java
  10. 48 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryExcelVO.java
  11. 30 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryExportReqVO.java
  12. 35 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryPageReqVO.java
  13. 19 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryRespVO.java
  14. 18 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryUpdateReqVO.java
  15. 34 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/CategoryConvert.java
  16. 58 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/category/CategoryDO.java
  17. 37 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/category/CategoryMapper.java
  18. 7 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/package-info.java
  19. 70 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryService.java
  20. 82 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryServiceImpl.java
  21. 194 0
      yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/CategoryServiceImplTest.java
  22. 1 0
      yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/clean.sql
  23. 17 0
      yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/create_tables.sql
  24. 9 4
      yudao-server/pom.xml
  25. 54 0
      yudao-ui-admin/src/api/mall/product/category.js
  26. 281 0
      yudao-ui-admin/src/views/mall/product/category/index.vue

+ 55 - 0
sql/mall.sql

@@ -0,0 +1,55 @@
+/*
+ Navicat Premium Data Transfer
+
+ Source Server         : 127.0.0.1
+ Source Server Type    : MySQL
+ Source Server Version : 80026
+ Source Host           : localhost:3306
+ Source Schema         : ruoyi-vue-pro
+
+ Target Server Type    : MySQL
+ Target Server Version : 80026
+ File Encoding         : 65001
+
+ Date: 05/02/2022 00:50:30
+*/
+SET
+FOREIGN_KEY_CHECKS = 0;
+SET NAMES utf8mb4;
+
+-- ----------------------------
+-- Table structure for product_category
+-- ----------------------------
+DROP TABLE IF EXISTS `product_category`;
+CREATE TABLE `product_category`
+(
+    `id`          bigint        NOT NULL AUTO_INCREMENT COMMENT '分类编号',
+    `pid`         bigint        NOT NULL COMMENT '父分类编号',
+    `name`        varchar(255)  NOT NULL COMMENT '分类名称',
+    `icon`        varchar(100)           DEFAULT '#' COMMENT '分类图标',
+    `banner_url`  varchar(255)  NOT NULL COMMENT '分类图片',
+    `sort`        int           NOT NULL DEFAULT '0' COMMENT '分类排序',
+    `description` varchar(1024) NOT NULL COMMENT '分类描述',
+    `status`      tinyint       NOT NULL COMMENT '开启状态',
+    `creator`     varchar(64)            DEFAULT '' COMMENT '创建者',
+    `create_time` datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`     varchar(64)            DEFAULT '' COMMENT '更新者',
+    `update_time` datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`     bit(1)        NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    `tenant_id`   bigint        NOT NULL DEFAULT '0' COMMENT '租户编号',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB COMMENT='商品分类';
+
+-- TODO 父级菜单的 id 处理
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `menu_type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`) VALUES (2000, '商城', '', 1, 1, 0, '/mall', 'merchant', NULL, 0);
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `menu_type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`) VALUES (2001, '商品管理', '', 1, 1, 2000, 'product', 'dict', NULL, 0);
+-- 菜单 SQL
+INSERT INTO `system_menu`(`name`, `permission`, `menu_type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`) VALUES ('商品分类管理', '', 2, 0, 2001, 'category', '', 'mall/product/category/index', 0);
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO `system_menu`(`name`, `permission`, `menu_type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`) VALUES ('商品分类查询', 'product:category:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `menu_type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`) VALUES ('商品分类创建', 'product:category:create', 3, 2, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `menu_type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`) VALUES ('商品分类更新', 'product:category:update', 3, 3, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `menu_type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`) VALUES ('商品分类删除', 'product:category:delete', 3, 4, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `menu_type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`) VALUES ('商品分类导出', 'product:category:export', 3, 5, @parentId, '', '', '', 0);

+ 2 - 0
yudao-module-mall/pom.xml

@@ -21,6 +21,8 @@
     <modules>
         <module>yudao-module-market-api</module>
         <module>yudao-module-market-biz</module>
+        <module>yudao-module-product-api</module>
+        <module>yudao-module-product-biz</module>
     </modules>
 
 </project>

+ 27 - 0
yudao-module-mall/yudao-module-product-api/pom.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>yudao-module-mall</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>yudao-module-product-api</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        product 模块 API,暴露给其它模块调用
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-common</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 4 - 0
yudao-module-mall/yudao-module-product-api/src/main/cn/iocoder/yudao/module/product/api/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.product.api;

+ 14 - 0
yudao-module-mall/yudao-module-product-api/src/main/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.product.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * product 错误码枚举类
+ * <p>
+ * product 系统,使用 1-008-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+    // ========== 商品分类相关  1008001000============
+    ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1008001000, "商品分类不存在");
+}

+ 67 - 0
yudao-module-mall/yudao-module-product-biz/pom.xml

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>yudao-module-mall</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>yudao-module-product-biz</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        product 模块,主要实现商品相关功能
+        例如:品牌、商品分类、spu、sku等功能。
+    </description>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-product-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- 业务组件 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
+        </dependency>
+
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-excel</artifactId>
+        </dependency>
+
+        <!-- DB 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+        </dependency>
+
+        <!-- Test 测试相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-test</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 100 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/CategoryController.java

@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.product.controller.admin.category;
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.annotations.*;
+
+import javax.validation.constraints.*;
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
+
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO;
+import cn.iocoder.yudao.module.product.convert.category.CategoryConvert;
+import cn.iocoder.yudao.module.product.service.category.CategoryService;
+
+@Api(tags = "管理后台 - 商品分类")
+@RestController
+@RequestMapping("/product/category")
+@Validated
+public class CategoryController {
+
+    @Resource
+    private CategoryService categoryService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建商品分类")
+    @PreAuthorize("@ss.hasPermission('product:category:create')")
+    public CommonResult<Long> createCategory(@Valid @RequestBody CategoryCreateReqVO createReqVO) {
+        return success(categoryService.createCategory(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新商品分类")
+    @PreAuthorize("@ss.hasPermission('product:category:update')")
+    public CommonResult<Boolean> updateCategory(@Valid @RequestBody CategoryUpdateReqVO updateReqVO) {
+        categoryService.updateCategory(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除商品分类")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('product:category:delete')")
+    public CommonResult<Boolean> deleteCategory(@RequestParam("id") Long id) {
+        categoryService.deleteCategory(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得商品分类")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('product:category:query')")
+    public CommonResult<CategoryRespVO> getCategory(@RequestParam("id") Long id) {
+        CategoryDO category = categoryService.getCategory(id);
+        return success(CategoryConvert.INSTANCE.convert(category));
+    }
+
+    @GetMapping("/list")
+    @ApiOperation("获得商品分类列表")
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
+    @PreAuthorize("@ss.hasPermission('product:category:query')")
+    public CommonResult<List<CategoryRespVO>> getCategoryList(@RequestParam("ids") Collection<Long> ids) {
+        List<CategoryDO> list = categoryService.getCategoryList(ids);
+        return success(CategoryConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得商品分类分页")
+    @PreAuthorize("@ss.hasPermission('product:category:query')")
+    public CommonResult<PageResult<CategoryRespVO>> getCategoryPage(@Valid CategoryPageReqVO pageVO) {
+        PageResult<CategoryDO> pageResult = categoryService.getCategoryPage(pageVO);
+        return success(CategoryConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出商品分类 Excel")
+    @PreAuthorize("@ss.hasPermission('product:category:export')")
+    @OperateLog(type = EXPORT)
+    public void exportCategoryExcel(@Valid CategoryExportReqVO exportReqVO,
+              HttpServletResponse response) throws IOException {
+        List<CategoryDO> list = categoryService.getCategoryList(exportReqVO);
+        // 导出 Excel
+        List<CategoryExcelVO> datas = CategoryConvert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "商品分类.xls", "数据", CategoryExcelVO.class, datas);
+    }
+
+}

+ 42 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryBaseVO.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+/**
+* 商品分类 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class CategoryBaseVO {
+
+    @ApiModelProperty(value = "父分类编号", required = true, example = "1")
+    @NotNull(message = "父分类编号不能为空")
+    private Long pid;
+
+    @ApiModelProperty(value = "分类名称", required = true, example = "办公文具")
+    @NotNull(message = "分类名称不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "分类图标")
+    private String icon;
+
+    @ApiModelProperty(value = "分类图片", required = true)
+    @NotNull(message = "分类图片不能为空")
+    private String bannerUrl;
+
+    @ApiModelProperty(value = "分类排序", required = true, example = "1")
+    @NotNull(message = "分类排序不能为空")
+    private Integer sort;
+
+    @ApiModelProperty(value = "分类描述", required = true, example = "描述")
+    @NotNull(message = "分类描述不能为空")
+    private String description;
+
+    @ApiModelProperty(value = "开启状态", required = true, example = "0")
+    @NotNull(message = "开启状态不能为空")
+    private Integer status;
+
+}

+ 14 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryCreateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 商品分类创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CategoryCreateReqVO extends CategoryBaseVO {
+
+}

+ 48 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryExcelVO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+
+
+/**
+ * 商品分类 Excel VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class CategoryExcelVO {
+
+    @ExcelProperty("分类编号")
+    private Long id;
+
+    @ExcelProperty("父分类编号")
+    private Long pid;
+
+    @ExcelProperty("分类名称")
+    private String name;
+
+    @ExcelProperty("分类图标")
+    private String icon;
+
+    @ExcelProperty("分类图片")
+    private String bannerUrl;
+
+    @ExcelProperty("分类排序")
+    private Integer sort;
+
+    @ExcelProperty("分类描述")
+    private String description;
+
+    @ExcelProperty(value = "开启状态", converter = DictConvert.class)
+    @DictFormat("common_status") // TODO 代码优化:建议设置到对应的 XXXDictTypeConstants 枚举类中
+    private Integer status;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryExportReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel(value = "管理后台 - 商品分类 Excel 导出 Request VO", description = "参数和 CategoryPageReqVO 是一致的")
+@Data
+public class CategoryExportReqVO {
+
+    @ApiModelProperty(value = "分类名称", example = "办公文具")
+    private String name;
+
+    @ApiModelProperty(value = "开启状态", example = "0")
+    private Integer status;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 35 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryPageReqVO.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 商品分类分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CategoryPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "分类名称", example = "办公文具")
+    private String name;
+
+    @ApiModelProperty(value = "开启状态", example = "0")
+    private Integer status;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 19 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - 商品分类 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CategoryRespVO extends CategoryBaseVO {
+
+    @ApiModelProperty(value = "分类编号", required = true, example = "2")
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 18 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryUpdateReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 商品分类更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CategoryUpdateReqVO extends CategoryBaseVO {
+
+    @ApiModelProperty(value = "分类编号", required = true, example = "2")
+    @NotNull(message = "分类编号不能为空")
+    private Long id;
+
+}

+ 34 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/CategoryConvert.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.product.convert.category;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO;
+
+/**
+ * 商品分类 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface CategoryConvert {
+
+    CategoryConvert INSTANCE = Mappers.getMapper(CategoryConvert.class);
+
+    CategoryDO convert(CategoryCreateReqVO bean);
+
+    CategoryDO convert(CategoryUpdateReqVO bean);
+
+    CategoryRespVO convert(CategoryDO bean);
+
+    List<CategoryRespVO> convertList(List<CategoryDO> list);
+
+    PageResult<CategoryRespVO> convertPage(PageResult<CategoryDO> page);
+
+    List<CategoryExcelVO> convertList02(List<CategoryDO> list);
+
+}

+ 58 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/category/CategoryDO.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.category;
+
+import lombok.*;
+import java.util.*;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 商品分类 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_category")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CategoryDO extends BaseDO {
+
+    /**
+     * 分类编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 父分类编号
+     */
+    private Long pid;
+    /**
+     * 分类名称
+     */
+    private String name;
+    /**
+     * 分类图标
+     */
+    private String icon;
+    /**
+     * 分类图片
+     */
+    private String bannerUrl;
+    /**
+     * 分类排序
+     */
+    private Integer sort;
+    /**
+     * 分类描述
+     */
+    private String description;
+    /**
+     * 开启状态
+     *
+     * 枚举 {@link TODO common_status 对应的类}
+     */
+    private Integer status;
+
+}

+ 37 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/category/CategoryMapper.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.product.dal.mysql.category;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.CategoryExportReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.CategoryPageReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 商品分类 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface CategoryMapper extends BaseMapperX<CategoryDO> {
+
+    default PageResult<CategoryDO> selectPage(CategoryPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<CategoryDO>()
+                .likeIfPresent(CategoryDO::getName, reqVO.getName())
+                .eqIfPresent(CategoryDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(CategoryDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc(CategoryDO::getId));
+    }
+
+    default List<CategoryDO> selectList(CategoryExportReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<CategoryDO>()
+                .likeIfPresent(CategoryDO::getName, reqVO.getName())
+                .eqIfPresent(CategoryDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(CategoryDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc(CategoryDO::getId));
+    }
+
+}

+ 7 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/package-info.java

@@ -0,0 +1,7 @@
+/**
+ * TODO
+ *
+ * @author JeromeSoar
+ * @since 2022-04-24
+ */
+package cn.iocoder.yudao.module.product;

+ 70 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryService.java

@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.product.service.category;
+
+import java.util.*;
+import javax.validation.*;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+/**
+ * 商品分类 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface CategoryService {
+
+    /**
+     * 创建商品分类
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createCategory(@Valid CategoryCreateReqVO createReqVO);
+
+    /**
+     * 更新商品分类
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateCategory(@Valid CategoryUpdateReqVO updateReqVO);
+
+    /**
+     * 删除商品分类
+     *
+     * @param id 编号
+     */
+    void deleteCategory(Long id);
+
+    /**
+     * 获得商品分类
+     *
+     * @param id 编号
+     * @return 商品分类
+     */
+    CategoryDO getCategory(Long id);
+
+    /**
+     * 获得商品分类列表
+     *
+     * @param ids 编号
+     * @return 商品分类列表
+     */
+    List<CategoryDO> getCategoryList(Collection<Long> ids);
+
+    /**
+     * 获得商品分类分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 商品分类分页
+     */
+    PageResult<CategoryDO> getCategoryPage(CategoryPageReqVO pageReqVO);
+
+    /**
+     * 获得商品分类列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 商品分类列表
+     */
+    List<CategoryDO> getCategoryList(CategoryExportReqVO exportReqVO);
+
+}

+ 82 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryServiceImpl.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.product.service.category;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import cn.iocoder.yudao.module.product.convert.category.CategoryConvert;
+import cn.iocoder.yudao.module.product.dal.mysql.category.CategoryMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
+
+/**
+ * 商品分类 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class CategoryServiceImpl implements CategoryService {
+
+    @Resource
+    private CategoryMapper categoryMapper;
+
+    @Override
+    public Long createCategory(CategoryCreateReqVO createReqVO) {
+        // 插入
+        CategoryDO category = CategoryConvert.INSTANCE.convert(createReqVO);
+        categoryMapper.insert(category);
+        // 返回
+        return category.getId();
+    }
+
+    @Override
+    public void updateCategory(CategoryUpdateReqVO updateReqVO) {
+        // 校验存在
+        this.validateCategoryExists(updateReqVO.getId());
+        // 更新
+        CategoryDO updateObj = CategoryConvert.INSTANCE.convert(updateReqVO);
+        categoryMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteCategory(Long id) {
+        // 校验存在
+        this.validateCategoryExists(id);
+        // 删除
+        categoryMapper.deleteById(id);
+    }
+
+    private void validateCategoryExists(Long id) {
+        if (categoryMapper.selectById(id) == null) {
+            throw exception(CATEGORY_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public CategoryDO getCategory(Long id) {
+        return categoryMapper.selectById(id);
+    }
+
+    @Override
+    public List<CategoryDO> getCategoryList(Collection<Long> ids) {
+        return categoryMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<CategoryDO> getCategoryPage(CategoryPageReqVO pageReqVO) {
+        return categoryMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<CategoryDO> getCategoryList(CategoryExportReqVO exportReqVO) {
+        return categoryMapper.selectList(exportReqVO);
+    }
+
+}

+ 194 - 0
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/CategoryServiceImplTest.java

@@ -0,0 +1,194 @@
+package cn.iocoder.yudao.module.product.service.category;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.CategoryCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.CategoryExportReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.CategoryPageReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.CategoryUpdateReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO;
+import cn.iocoder.yudao.module.product.dal.mysql.category.CategoryMapper;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.CATEGORY_NOT_EXISTS;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link CategoryServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(CategoryServiceImpl.class)
+public class CategoryServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private CategoryServiceImpl categoryService;
+
+    @Resource
+    private CategoryMapper categoryMapper;
+
+    @Test
+    public void testCreateCategory_success() {
+        // 准备参数
+        CategoryCreateReqVO reqVO = randomPojo(CategoryCreateReqVO.class);
+
+        // 调用
+        Long categoryId = categoryService.createCategory(reqVO);
+        // 断言
+        assertNotNull(categoryId);
+        // 校验记录的属性是否正确
+        CategoryDO category = categoryMapper.selectById(categoryId);
+        assertPojoEquals(reqVO, category);
+    }
+
+    @Test
+    public void testUpdateCategory_success() {
+        // mock 数据
+        CategoryDO dbCategory = randomPojo(CategoryDO.class);
+        categoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        CategoryUpdateReqVO reqVO = randomPojo(CategoryUpdateReqVO.class, o -> {
+            o.setId(dbCategory.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        categoryService.updateCategory(reqVO);
+        // 校验是否更新正确
+        CategoryDO category = categoryMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, category);
+    }
+
+    @Test
+    public void testUpdateCategory_notExists() {
+        // 准备参数
+        CategoryUpdateReqVO reqVO = randomPojo(CategoryUpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> categoryService.updateCategory(reqVO), CATEGORY_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteCategory_success() {
+        // mock 数据
+        CategoryDO dbCategory = randomPojo(CategoryDO.class);
+        categoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbCategory.getId();
+
+        // 调用
+        categoryService.deleteCategory(id);
+        // 校验数据不存在了
+        assertNull(categoryMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteCategory_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> categoryService.deleteCategory(id), CATEGORY_NOT_EXISTS);
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetCategoryPage() {
+        // mock 数据
+        CategoryDO dbCategory = randomPojo(CategoryDO.class, o -> { // 等会查询到
+            o.setPid(null);
+            o.setName(null);
+            o.setIcon(null);
+            o.setBannerUrl(null);
+            o.setSort(null);
+            o.setDescription(null);
+            o.setStatus(null);
+            o.setCreateTime(null);
+        });
+        categoryMapper.insert(dbCategory);
+        // 测试 pid 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setPid(null)));
+        // 测试 name 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setName(null)));
+        // 测试 icon 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setIcon(null)));
+        // 测试 bannerUrl 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setBannerUrl(null)));
+        // 测试 sort 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setSort(null)));
+        // 测试 description 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setDescription(null)));
+        // 测试 status 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setStatus(null)));
+        // 测试 createTime 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setCreateTime(null)));
+        // 准备参数
+        CategoryPageReqVO reqVO = new CategoryPageReqVO();
+        reqVO.setName(null);
+        reqVO.setStatus(null);
+        reqVO.setBeginCreateTime(null);
+        reqVO.setEndCreateTime(null);
+
+        // 调用
+        PageResult<CategoryDO> pageResult = categoryService.getCategoryPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbCategory, pageResult.getList().get(0));
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetCategoryList() {
+        // mock 数据
+        CategoryDO dbCategory = randomPojo(CategoryDO.class, o -> { // 等会查询到
+            o.setPid(null);
+            o.setName(null);
+            o.setIcon(null);
+            o.setBannerUrl(null);
+            o.setSort(null);
+            o.setDescription(null);
+            o.setStatus(null);
+            o.setCreateTime(null);
+        });
+        categoryMapper.insert(dbCategory);
+        // 测试 pid 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setPid(null)));
+        // 测试 name 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setName(null)));
+        // 测试 icon 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setIcon(null)));
+        // 测试 bannerUrl 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setBannerUrl(null)));
+        // 测试 sort 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setSort(null)));
+        // 测试 description 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setDescription(null)));
+        // 测试 status 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setStatus(null)));
+        // 测试 createTime 不匹配
+        categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setCreateTime(null)));
+        // 准备参数
+        CategoryExportReqVO reqVO = new CategoryExportReqVO();
+        reqVO.setName(null);
+        reqVO.setStatus(null);
+        reqVO.setBeginCreateTime(null);
+        reqVO.setEndCreateTime(null);
+
+        // 调用
+        List<CategoryDO> list = categoryService.getCategoryList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbCategory, list.get(0));
+    }
+
+}

+ 1 - 0
yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/clean.sql

@@ -0,0 +1 @@
+DELETE FROM "product_category";

+ 17 - 0
yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/create_tables.sql

@@ -0,0 +1,17 @@
+CREATE TABLE IF NOT EXISTS "product_category" (
+    "id" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "pid" bigint(20) NOT NULL,
+    "name" varchar(255) NOT NULL,
+    "icon" varchar(100),
+    "banner_url" varchar(255) NOT NULL,
+    "sort" int(11) NOT NULL,
+    "description" varchar(1024) NOT NULL,
+    "status" tinyint(4) NOT NULL,
+    "creator" varchar(64) DEFAULT '',
+    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater" varchar(64) DEFAULT '',
+    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted" bit NOT NULL DEFAULT FALSE,
+    "tenant_id" bigint(20) NOT NULL,
+    PRIMARY KEY ("id")
+) COMMENT '商品分类';

+ 9 - 4
yudao-server/pom.xml

@@ -47,16 +47,21 @@
             <artifactId>yudao-module-market-biz</artifactId>
             <version>${revision}</version>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-product-biz</artifactId>
+            <version>${revision}</version>
+        </dependency>
         <!-- 默认引入 yudao-module-bpm-biz-flowable 实现,可以替换为 yudao-module-bpm-biz-activiti 实现-->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-module-bpm-biz-flowable</artifactId>
             <version>${revision}</version>
         </dependency>
-<!--        <dependency>-->
-<!--            <groupId>cn.iocoder.boot</groupId>-->
-<!--            <artifactId>yudao-module-bpm-biz-activiti</artifactId>-->
-<!--            <version>${revision}</version>-->
+        <!--        <dependency>-->
+        <!--            <groupId>cn.iocoder.boot</groupId>-->
+        <!--            <artifactId>yudao-module-bpm-biz-activiti</artifactId>-->
+        <!--            <version>${revision}</version>-->
 <!--        </dependency>-->
 
         <!-- spring boot 配置所需依赖 -->

+ 54 - 0
yudao-ui-admin/src/api/mall/product/category.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 创建商品分类
+export function createCategory(data) {
+  return request({
+    url: '/product/category/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新商品分类
+export function updateCategory(data) {
+  return request({
+    url: '/product/category/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除商品分类
+export function deleteCategory(id) {
+  return request({
+    url: '/product/category/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得商品分类
+export function getCategory(id) {
+  return request({
+    url: '/product/category/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得商品分类分页
+export function getCategoryPage(query) {
+  return request({
+    url: '/product/category/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出商品分类 Excel
+export function exportCategoryExcel(query) {
+  return request({
+    url: '/product/category/export-excel',
+    method: 'get',
+    params: query,
+    responseType: 'blob'
+  })
+}

+ 281 - 0
yudao-ui-admin/src/views/mall/product/category/index.vue

@@ -0,0 +1,281 @@
+<template>
+  <div class="app-container">
+
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="分类名称" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入分类名称" clearable @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="开启状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable size="small">
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
+                     :key="dict.value" :label="dict.label" :value="dict.value"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间">
+        <el-date-picker v-model="dateRangeCreateTime" style="width: 240px" value-format="yyyy-MM-dd"
+                        type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"/>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+                   v-hasPermi="['product:category:create']">新增
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
+                   :loading="exportLoading"
+                   v-hasPermi="['product:category:export']">导出
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="分类编号" align="center" prop="id"/>
+      <el-table-column label="父分类编号" align="center" prop="pid"/>
+      <el-table-column label="分类名称" align="center" prop="name"/>
+      <el-table-column label="分类图标" align="center" prop="icon"/>
+      <el-table-column label="分类图片" align="center" prop="bannerUrl"/>
+      <el-table-column label="分类排序" align="center" prop="sort"/>
+      <el-table-column label="分类描述" align="center" prop="description"/>
+      <el-table-column label="开启状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+                     v-hasPermi="['product:category:update']">修改
+          </el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                     v-hasPermi="['product:category:delete']">删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+
+    <!-- 对话框(添加 / 修改) -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="父分类编号" prop="pid">
+          <el-input v-model="form.pid" placeholder="请输入父分类编号"/>
+        </el-form-item>
+        <el-form-item label="分类名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入分类名称"/>
+        </el-form-item>
+        <el-form-item label="分类图标" prop="icon">
+          <el-input v-model="form.icon" placeholder="请输入分类图标"/>
+        </el-form-item>
+        <el-form-item label="分类图片" prop="bannerUrl">
+          <el-input v-model="form.bannerUrl" placeholder="请输入分类图片"/>
+        </el-form-item>
+        <el-form-item label="分类排序" prop="sort">
+          <el-input v-model="form.sort" placeholder="请输入分类排序"/>
+        </el-form-item>
+        <el-form-item label="分类描述">
+          <editor v-model="form.description" :min-height="192"/>
+        </el-form-item>
+        <el-form-item label="开启状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
+                      :key="dict.value" :label="parseInt(dict.value)">{{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  createCategory,
+  deleteCategory,
+  exportCategoryExcel,
+  getCategory,
+  getCategoryPage,
+  updateCategory
+} from "@/api/mall/product/category";
+import Editor from '@/components/Editor';
+
+export default {
+  name: "Category",
+  components: {
+    Editor,
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 商品分类列表
+      list: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      dateRangeCreateTime: [],
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        name: null,
+        status: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        pid: [{required: true, message: "父分类编号不能为空", trigger: "blur"}],
+        name: [{required: true, message: "分类名称不能为空", trigger: "blur"}],
+        bannerUrl: [{required: true, message: "分类图片不能为空", trigger: "blur"}],
+        sort: [{required: true, message: "分类排序不能为空", trigger: "blur"}],
+        description: [{required: true, message: "分类描述不能为空", trigger: "blur"}],
+        status: [{required: true, message: "开启状态不能为空", trigger: "blur"}],
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      this.loading = true;
+      // 处理查询参数
+      let params = {...this.queryParams};
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行查询
+      getCategoryPage(params).then(response => {
+        this.list = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 取消按钮 */
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    /** 表单重置 */
+    reset() {
+      this.form = {
+        id: undefined,
+        pid: undefined,
+        name: undefined,
+        icon: undefined,
+        bannerUrl: undefined,
+        sort: undefined,
+        description: undefined,
+        status: undefined,
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRangeCreateTime = [];
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加商品分类";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id;
+      getCategory(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改商品分类";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (!valid) {
+          return;
+        }
+        // 修改的提交
+        if (this.form.id != null) {
+          updateCategory(this.form).then(response => {
+            this.$modal.msgSuccess("修改成功");
+            this.open = false;
+            this.getList();
+          });
+          return;
+        }
+        // 添加的提交
+        createCategory(this.form).then(response => {
+          this.$modal.msgSuccess("新增成功");
+          this.open = false;
+          this.getList();
+        });
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const id = row.id;
+      this.$modal.confirm('是否确认删除商品分类编号为"' + id + '"的数据项?').then(function () {
+        return deleteCategory(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      // 处理查询参数
+      let params = {...this.queryParams};
+      params.pageNo = undefined;
+      params.pageSize = undefined;
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行导出
+      this.$modal.confirm('是否确认导出所有商品分类数据项?').then(() => {
+        this.exportLoading = true;
+        return exportCategoryExcel(params);
+      }).then(response => {
+        this.$download.excel(response, '${table.classComment}.xls');
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+    }
+  }
+};
+</script>