diff --git a/sql/mall.sql b/sql/mall.sql
new file mode 100644
index 000000000..390044e06
--- /dev/null
+++ b/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);
diff --git a/yudao-module-mall/pom.xml b/yudao-module-mall/pom.xml
index b13dc68f6..866874852 100644
--- a/yudao-module-mall/pom.xml
+++ b/yudao-module-mall/pom.xml
@@ -21,6 +21,8 @@
yudao-module-market-api
yudao-module-market-biz
+ yudao-module-product-api
+ yudao-module-product-biz
diff --git a/yudao-module-mall/yudao-module-product-api/pom.xml b/yudao-module-mall/yudao-module-product-api/pom.xml
new file mode 100644
index 000000000..7eb38008a
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/pom.xml
@@ -0,0 +1,27 @@
+
+
+ 4.0.0
+
+ cn.iocoder.boot
+ yudao-module-mall
+ ${revision}
+
+
+ yudao-module-product-api
+ jar
+
+ ${project.artifactId}
+
+ product 模块 API,暴露给其它模块调用
+
+
+
+
+ cn.iocoder.boot
+ yudao-common
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/cn/iocoder/yudao/module/product/api/package-info.java b/yudao-module-mall/yudao-module-product-api/src/main/cn/iocoder/yudao/module/product/api/package-info.java
new file mode 100644
index 000000000..b19092853
--- /dev/null
+++ b/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;
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
new file mode 100644
index 000000000..b8c671fc5
--- /dev/null
+++ b/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 错误码枚举类
+ *
+ * product 系统,使用 1-008-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 商品分类相关 1008001000============
+ ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1008001000, "商品分类不存在");
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/pom.xml b/yudao-module-mall/yudao-module-product-biz/pom.xml
new file mode 100644
index 000000000..a06f8937c
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/pom.xml
@@ -0,0 +1,67 @@
+
+
+ 4.0.0
+
+ cn.iocoder.boot
+ yudao-module-mall
+ ${revision}
+
+
+ yudao-module-product-biz
+ jar
+
+ ${project.artifactId}
+
+ product 模块,主要实现商品相关功能
+ 例如:品牌、商品分类、spu、sku等功能。
+
+
+
+
+
+ cn.iocoder.boot
+ yudao-module-product-api
+ ${revision}
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-biz-operatelog
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-biz-weixin
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-biz-tenant
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-web
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-excel
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-mybatis
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-test
+
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/CategoryController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/CategoryController.java
new file mode 100644
index 000000000..346b3177a
--- /dev/null
+++ b/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 createCategory(@Valid @RequestBody CategoryCreateReqVO createReqVO) {
+ return success(categoryService.createCategory(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @ApiOperation("更新商品分类")
+ @PreAuthorize("@ss.hasPermission('product:category:update')")
+ public CommonResult 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 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 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> getCategoryList(@RequestParam("ids") Collection ids) {
+ List list = categoryService.getCategoryList(ids);
+ return success(CategoryConvert.INSTANCE.convertList(list));
+ }
+
+ @GetMapping("/page")
+ @ApiOperation("获得商品分类分页")
+ @PreAuthorize("@ss.hasPermission('product:category:query')")
+ public CommonResult> getCategoryPage(@Valid CategoryPageReqVO pageVO) {
+ PageResult 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 list = categoryService.getCategoryList(exportReqVO);
+ // 导出 Excel
+ List datas = CategoryConvert.INSTANCE.convertList02(list);
+ ExcelUtils.write(response, "商品分类.xls", "数据", CategoryExcelVO.class, datas);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryBaseVO.java
new file mode 100644
index 000000000..82f2ba09d
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryCreateReqVO.java
new file mode 100644
index 000000000..ce583f08b
--- /dev/null
+++ b/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 {
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryExcelVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryExcelVO.java
new file mode 100644
index 000000000..ae754e42d
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryExportReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryExportReqVO.java
new file mode 100644
index 000000000..c1e23b3ed
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryPageReqVO.java
new file mode 100644
index 000000000..d824b8bd8
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryRespVO.java
new file mode 100644
index 000000000..e7d0b2238
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryUpdateReqVO.java
new file mode 100644
index 000000000..13ee83c1e
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/CategoryConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/CategoryConvert.java
new file mode 100644
index 000000000..2b2cfe99c
--- /dev/null
+++ b/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 convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+ List convertList02(List list);
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/category/CategoryDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/category/CategoryDO.java
new file mode 100644
index 000000000..bbebfc11c
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/category/CategoryMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/category/CategoryMapper.java
new file mode 100644
index 000000000..109f1ff54
--- /dev/null
+++ b/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 {
+
+ default PageResult selectPage(CategoryPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(CategoryDO::getName, reqVO.getName())
+ .eqIfPresent(CategoryDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(CategoryDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+ .orderByDesc(CategoryDO::getId));
+ }
+
+ default List selectList(CategoryExportReqVO reqVO) {
+ return selectList(new LambdaQueryWrapperX()
+ .likeIfPresent(CategoryDO::getName, reqVO.getName())
+ .eqIfPresent(CategoryDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(CategoryDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+ .orderByDesc(CategoryDO::getId));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/package-info.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/package-info.java
new file mode 100644
index 000000000..4e80cc2bc
--- /dev/null
+++ b/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;
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryService.java
new file mode 100644
index 000000000..ff5f520fa
--- /dev/null
+++ b/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 getCategoryList(Collection ids);
+
+ /**
+ * 获得商品分类分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 商品分类分页
+ */
+ PageResult getCategoryPage(CategoryPageReqVO pageReqVO);
+
+ /**
+ * 获得商品分类列表, 用于 Excel 导出
+ *
+ * @param exportReqVO 查询条件
+ * @return 商品分类列表
+ */
+ List getCategoryList(CategoryExportReqVO exportReqVO);
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryServiceImpl.java
new file mode 100644
index 000000000..873bad297
--- /dev/null
+++ b/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 getCategoryList(Collection ids) {
+ return categoryMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public PageResult getCategoryPage(CategoryPageReqVO pageReqVO) {
+ return categoryMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List getCategoryList(CategoryExportReqVO exportReqVO) {
+ return categoryMapper.selectList(exportReqVO);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/CategoryServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/CategoryServiceImplTest.java
new file mode 100644
index 000000000..c0ef7e572
--- /dev/null
+++ b/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 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 list = categoryService.getCategoryList(reqVO);
+ // 断言
+ assertEquals(1, list.size());
+ assertPojoEquals(dbCategory, list.get(0));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/clean.sql b/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/clean.sql
new file mode 100644
index 000000000..7c66b9074
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/clean.sql
@@ -0,0 +1 @@
+DELETE FROM "product_category";
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/create_tables.sql b/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/create_tables.sql
new file mode 100644
index 000000000..6400c8f0b
--- /dev/null
+++ b/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 '商品分类';
\ No newline at end of file
diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml
index 73eba6068..b7eed1e50 100644
--- a/yudao-server/pom.xml
+++ b/yudao-server/pom.xml
@@ -47,16 +47,21 @@
yudao-module-market-biz
${revision}
+
+ cn.iocoder.boot
+ yudao-module-product-biz
+ ${revision}
+
cn.iocoder.boot
yudao-module-bpm-biz-flowable
${revision}
-
-
-
-
+
+
+
+
diff --git a/yudao-ui-admin/src/api/mall/product/category.js b/yudao-ui-admin/src/api/mall/product/category.js
new file mode 100644
index 000000000..33fde73cd
--- /dev/null
+++ b/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'
+ })
+}
diff --git a/yudao-ui-admin/src/views/mall/product/category/index.vue b/yudao-ui-admin/src/views/mall/product/category/index.vue
new file mode 100644
index 000000000..57273c146
--- /dev/null
+++ b/yudao-ui-admin/src/views/mall/product/category/index.vue
@@ -0,0 +1,281 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+ 新增
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+