diff --git a/pom.xml b/pom.xml index 0ba239d5d..e3c5072ab 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ <module>yudao-module-system</module> <module>yudao-module-infra</module> <module>yudao-module-pay</module> - <module>yudao-module-market</module> + <module>yudao-module-mall</module> </modules> <name>${project.artifactId}</name> diff --git a/sql/mall.sql b/sql/mall.sql new file mode 100644 index 000000000..825d43e36 --- /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) NOT NULL DEFAULT '#' COMMENT '分类图标', + `banner_url` varchar(255) NOT NULL COMMENT '分类图片', + `sort` int DEFAULT '0' COMMENT '分类排序', + `description` varchar(1024) DEFAULT 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-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index e3d810dec..f53b45ac8 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.infra.controller.admin.file; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.IoUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -23,6 +24,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; +import java.util.Date; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -47,6 +49,17 @@ public class FileController { return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream()))); } + @PostMapping("/simple-upload") + @ApiOperation("简单上传文件") + @ApiImplicitParams({ + @ApiImplicitParam(name = "file", value = "文件附件", required = true, dataTypeClass = MultipartFile.class), + }) + public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file) throws Exception { + String path = DateUtil.format(new Date(), "yyyy/MM/dd/") + file.getOriginalFilename(); + String file1 = fileService.createFile(path, IoUtil.readBytes(file.getInputStream())); + return success(file1); + } + @DeleteMapping("/delete") @ApiOperation("删除文件") @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) diff --git a/yudao-module-mall/pom.xml b/yudao-module-mall/pom.xml index 448986c6e..866874852 100644 --- a/yudao-module-mall/pom.xml +++ b/yudao-module-mall/pom.xml @@ -9,7 +9,7 @@ </parent> <modelVersion>4.0.0</modelVersion> - <artifactId>yudao-module-market</artifactId> + <artifactId>yudao-module-mall</artifactId> <packaging>pom</packaging> <name>${project.artifactId}</name> @@ -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> diff --git a/yudao-module-mall/yudao-module-market-api/pom.xml b/yudao-module-mall/yudao-module-market-api/pom.xml index c83179632..fef3428fc 100644 --- a/yudao-module-mall/yudao-module-market-api/pom.xml +++ b/yudao-module-mall/yudao-module-market-api/pom.xml @@ -3,8 +3,8 @@ 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"> <parent> - <artifactId>yudao-module-market</artifactId> <groupId>cn.iocoder.boot</groupId> + <artifactId>yudao-module-mall</artifactId> <version>${revision}</version> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/yudao-module-mall/yudao-module-market-biz/pom.xml b/yudao-module-mall/yudao-module-market-biz/pom.xml index 0e3a0490e..e7fa33e9d 100644 --- a/yudao-module-mall/yudao-module-market-biz/pom.xml +++ b/yudao-module-mall/yudao-module-market-biz/pom.xml @@ -3,8 +3,8 @@ 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"> <parent> - <artifactId>yudao-module-market</artifactId> <groupId>cn.iocoder.boot</groupId> + <artifactId>yudao-module-mall</artifactId> <version>${revision}</version> </parent> <modelVersion>4.0.0</modelVersion> 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 @@ +<?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> \ 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 错误码枚举类 + * <p> + * 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 @@ +<?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> \ 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..9e5614a87 --- /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,108 @@ +package cn.iocoder.yudao.module.product.controller.admin.category; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.product.controller.admin.category.vo.*; +import cn.iocoder.yudao.module.product.convert.category.CategoryConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO; +import cn.iocoder.yudao.module.product.service.category.CategoryService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@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("/listByIds") + @ApiOperation("获得商品分类列表") + @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("/listByQuery") + @ApiOperation("获得商品分类列表") + @PreAuthorize("@ss.hasPermission('product:category:query')") + public CommonResult<List<CategoryRespVO>> listByQuery(@Valid CategoryTreeListReqVO treeListReqVO) { + List<CategoryDO> list = categoryService.getCategoryTreeList(treeListReqVO); + list.sort(Comparator.comparing(CategoryDO::getSort)); + 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); + } + +} 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..d4c2ebb8b --- /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 io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** +* 商品分类 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 = "办公文具") + @NotBlank(message = "分类名称不能为空") + private String name; + + @ApiModelProperty(value = "分类图标") + @NotBlank(message = "分类图标不能为空") + private String icon; + + @ApiModelProperty(value = "分类图片", required = true) + @NotBlank(message = "分类图片不能为空") + private String bannerUrl; + + @ApiModelProperty(value = "分类排序", required = true, example = "1") + private Integer sort; + + @ApiModelProperty(value = "分类描述", required = true, example = "描述") + 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/CategoryTreeListReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryTreeListReqVO.java new file mode 100644 index 000000000..12256efd3 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/CategoryTreeListReqVO.java @@ -0,0 +1,29 @@ +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; + +@Data +@ApiModel(value = "管理后台 - 商品分类列表查询 Request VO", description = "参数和 CategoryPageReqVO 是一致的") +public class CategoryTreeListReqVO extends 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/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<CategoryRespVO> convertList(List<CategoryDO> list); + + PageResult<CategoryRespVO> convertPage(PageResult<CategoryDO> page); + + List<CategoryExcelVO> convertList02(List<CategoryDO> 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<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)); + } + +} 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..b29b3418f --- /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,79 @@ +package cn.iocoder.yudao.module.product.service.category; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.category.vo.*; +import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; + +/** + * 商品分类 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); + + /** + * 获得商品分类列表 + * + * @param treeListReqVO 查询条件 + * @return 商品分类列表 + */ + List<CategoryDO> getCategoryTreeList(CategoryTreeListReqVO treeListReqVO); +} 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..b84ac5b9a --- /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,87 @@ +package cn.iocoder.yudao.module.product.service.category; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.category.vo.*; +import cn.iocoder.yudao.module.product.convert.category.CategoryConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO; +import cn.iocoder.yudao.module.product.dal.mysql.category.CategoryMapper; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.CATEGORY_NOT_EXISTS; + +/** + * 商品分类 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); + } + + @Override + public List<CategoryDO> getCategoryTreeList(CategoryTreeListReqVO treeListReqVO) { + return categoryMapper.selectList(treeListReqVO); + } + +} 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<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)); + } + +} 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 @@ <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 配置所需依赖 --> 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..bd825034d --- /dev/null +++ b/yudao-ui-admin/src/api/mall/product/category.js @@ -0,0 +1,63 @@ +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 listCategory(query) { + return request({ + url: '/product/category/listByQuery', + method: 'get', + params: query + }) +} + +// 获得商品分类分页 +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/components/ImageUpload/index.vue b/yudao-ui-admin/src/components/ImageUpload/index.vue index 170c1bd08..b736b7240 100644 --- a/yudao-ui-admin/src/components/ImageUpload/index.vue +++ b/yudao-ui-admin/src/components/ImageUpload/index.vue @@ -77,7 +77,7 @@ export default { dialogVisible: false, hideUpload: false, baseUrl: process.env.VUE_APP_BASE_API, - uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址 + uploadImgUrl: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/simple-upload", // 上传的图片服务器地址 headers: { Authorization: "Bearer " + getToken(), }, @@ -127,7 +127,7 @@ export default { }, // 上传成功回调 handleUploadSuccess(res) { - this.uploadList.push({ name: res.fileName, url: res.fileName }); + this.uploadList.push({ name: res.data, url: res.data }); if (this.uploadList.length === this.number) { this.fileList = this.fileList.concat(this.uploadList); this.uploadList = []; 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..751ad9c52 --- /dev/null +++ b/yudao-ui-admin/src/views/mall/product/category/index.vue @@ -0,0 +1,330 @@ +<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> + <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="info" plain icon="el-icon-sort" size="mini" @click="toggleExpandAll">展开/折叠</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-if="refreshTable" v-loading="loading" :data="list" row-key="id" :default-expand-all="isExpandAll" + :tree-props="{children: 'children', hasChildren: 'hasChildren'}"> + <el-table-column label="分类名称" align="center" prop="name"/> + <el-table-column label="分类图标" align="center" prop="icon"> + <template slot-scope="scope"> + <svg-icon :icon-class="scope.row.icon" /> + </template> + </el-table-column> + <el-table-column label="分类图片" align="center" prop="bannerUrl"> + <template slot-scope="scope"> + <img v-if="scope.row.bannerUrl" :src="scope.row.bannerUrl" alt="分类图片"/> + </template> + </el-table-column> + <el-table-column label="分类排序" align="center" prop="sort"/> + <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> + + <!-- 对话框(添加 / 修改) --> + <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"> + <Treeselect v-model="form.pid" :options="parentCategoryOptions" :normalizer="normalizer" :show-count="true" + 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-popover placement="bottom-start" width="460" trigger="click" @show="$refs['iconSelect'].reset()"> + <IconSelect ref="iconSelect" @selected="iconSelected"/> + <el-input slot="reference" v-model="form.icon" placeholder="点击选择分类图标" readonly> + <svg-icon v-if="form.icon" slot="prefix" :icon-class="form.icon" class="el-input__icon" + style="height: 32px;width: 16px;"/> + <i v-else slot="prefix" class="el-icon-search el-input__icon"/> + </el-input> + </el-popover> + </el-form-item> + <el-form-item label="分类图片" prop="bannerUrl"> + <ImageUpload v-model="form.bannerUrl" /> + </el-form-item> + <el-form-item label="分类排序" prop="sort"> + <el-input-number v-model="form.sort" controls-position="right" :min="0" /> + </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, + listCategory, + updateCategory +} from "@/api/mall/product/category"; +import Editor from '@/components/Editor'; +import Treeselect from "@riophae/vue-treeselect"; +import "@riophae/vue-treeselect/dist/vue-treeselect.css"; +import IconSelect from "@/components/IconSelect"; +import ImageUpload from '@/components/ImageUpload'; + +export default { + name: "Category", + components: { + Editor, Treeselect, IconSelect, ImageUpload + }, + data() { + return { + // 遮罩层 + loading: true, + // 导出遮罩层 + exportLoading: false, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 商品分类列表 + list: [], + // 商品分类树选项 + parentCategoryOptions: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 是否展开,默认全部折叠 + isExpandAll: false, + // 重新渲染表格状态 + refreshTable: true, + // 查询参数 + queryParams: { + pageNo: 1, + pageSize: 10, + name: null, + status: null, + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { + pid: [{required: true, message: "请选择上级分类", trigger: "blur"}], + name: [{required: true, message: "分类名称不能为空", trigger: "blur"}], + icon: [{required: true, message: "分类图标不能为空", trigger: "blur"}], + bannerUrl: [{required: true, message: "分类图片不能为空", trigger: "blur"}], + status: [{required: true, message: "开启状态不能为空", trigger: "blur"}], + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询列表 */ + getList() { + this.loading = true; + // 处理查询参数 + let params = {...this.queryParams}; + // 执行查询 + listCategory(params).then(response => { + this.list = this.handleTree(response.data, "id", "pid"); + this.loading = false; + }); + }, + // 选择图标 + iconSelected(name) { + this.form.icon = name; + }, + /** 转换菜单数据结构 */ + normalizer(node) { + if (node.children && !node.children.length) { + delete node.children; + } + return { + id: node.id, + label: node.name, + children: node.children + }; + }, + /** 查询分类下拉树结构 */ + getTreeselect() { + listCategory().then(response => { + this.parentCategoryOptions = []; + const menu = {id: 0, name: '主分类', children: []}; + menu.children = this.handleTree(response.data, "id", "pid"); + this.parentCategoryOptions.push(menu); + }); + }, + /** 取消按钮 */ + 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.resetForm("queryForm"); + this.handleQuery(); + }, + /** 展开/折叠操作 */ + toggleExpandAll() { + this.refreshTable = false; + this.isExpandAll = !this.isExpandAll; + this.$nextTick(() => { + this.refreshTable = true; + }); + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset(); + this.getTreeselect(); + this.open = true; + this.title = "添加商品分类"; + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + this.getTreeselect(); + 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.$modal.confirm('是否确认导出所有商品分类数据项?').then(() => { + this.exportLoading = true; + return exportCategoryExcel(params); + }).then(response => { + this.$download.excel(response, '${table.classComment}.xls'); + this.exportLoading = false; + }).catch(() => { + }); + } + } +}; +</script>