lyshop学习笔记七-商品管理(添加商品)


title: 乐优商城学习笔记七-商品管理(添加商品)
date: 2019-04-17 16:18:10
tags:
- 乐优商城
- java
- springboot
categories:
- 乐优商城


0.学习目标

  • 独立实现商品新增后台
  • 独立实现商品编辑后台
  • 独立搭建前台系统页面

1.商品新增

1.1.页面预览

当我们点击新增商品按钮:

lyshop学习笔记七-商品管理(添加商品)_第1张图片
1528083727447

就会出现一个弹窗:

[图片上传失败...(image-55dc49-1555491859890)]

里面把商品的数据分为了4部分来填写:

  • 基本信息:主要是一些简单的文本数据,包含了SPU和SpuDetail的部分数据,如
    • 商品分类:是SPU中的cid1cid2cid3属性
    • 品牌:是spu中的brandId属性
    • 标题:是spu中的title属性
    • 子标题:是spu中的subTitle属性
    • 售后服务:是SpuDetail中的afterService属性
    • 包装列表:是SpuDetail中的packingList属性
  • 商品描述:是SpuDetail中的description属性,数据较多,所以单独放一个页面
  • 规格参数:商品规格信息,对应SpuDetail中的genericSpec属性
  • SKU属性:spu下的所有Sku信息

对应到页面中的四个stepper-content

[图片上传失败...(image-4fa092-1555491859890)]

1.2.弹窗事件

弹窗是一个独立组件:

[图片上传失败...(image-591a50-1555491859890)]

并且在Goods组件中已经引用它:

[图片上传失败...(image-465e8e-1555491859890)]

并且在页面中渲染:

[图片上传失败...(image-44bb64-1555491859890)]

新增商品按钮的点击事件中,改变这个dialogshow属性:

[图片上传失败...(image-4e78e8-1555491859890)]

[图片上传失败...(image-d90975-1555491859890)]

1.3.基本数据

我们先来看下基本数据:

[图片上传失败...(image-1e3aeb-1555491859890)]

1.3.1.商品分类

商品分类信息查询我们之前已经做过,所以这里的级联选框已经实现完成:

[图片上传失败...(image-211bab-1555491859890)]

刷新页面,可以看到请求已经发出:

[图片上传失败...(image-47d990-1555491859890)]

[图片上传失败...(image-4d6430-1555491859890)]

[图片上传失败...(image-4a702e-1555491859890)]

1.3.2.品牌选择

页面

品牌也是一个下拉选框,不过其选项是不确定的,只有当用户选择了商品分类,才会把这个分类下的所有品牌展示出来。

所以页面编写了watch函数,监控商品分类的变化,每当商品分类值有变化,就会发起请求,查询品牌列表:

    "goods.categories": {
      deep: true,
      handler(val) {
        // 判断商品分类是否存在,存在才查询
        if (val && val.length > 0) {
          // 根据分类查询品牌
          this.$http
            .get("/item/brand/cid/" + this.goods.categories[2].id)
            .then(({ data }) => {
              this.brandOptions = data;
            });

刷新页面,可以看到请求发起:

[图片上传失败...(image-478a3f-1555491859890)]

接下来,我们只要编写后台接口,根据商品分类id,查询对应品牌即可。

后台接口

页面需要去后台查询品牌信息,我们自然需要提供:

controller

/**
  * 根据分类查询品牌
  * @param cid
  * @return
  */
@GetMapping("cid/{cid}")
public ResponseEntity> queryBrandByCategory(@PathVariable("cid") Long cid) {
    List list = this.brandService.queryBrandByCategory(cid);
    if(list == null){
        new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
    return ResponseEntity.ok(list);
}

service

public List queryBrandByCategory(Long cid) {
    return this.brandMapper.queryByCategoryId(cid);
}

mapper

根据分类查询品牌有中间表,需要自己编写Sql:

@Select("SELECT b.* FROM tb_category_brand cb LEFT JOIN tb_brand b ON cb.brand_id = b.id WHERE cb.category_id = #{cid}")
List queryByCategoryId(Long cid);

1.4.商品规格参数

规格参数的查询我们之前也已经编写过接口,因为商品规格参数也是与商品分类绑定,所以需要在商品分类变化后去查询,我们也是通过watch监控来实现:

lyshop学习笔记七-商品管理(添加商品)_第2张图片
1529631056153

可以看到这里是根据商品分类id查询规格参数:SpecParam。我们之前写过一个根据gid(分组id)来查询规格参数的接口,我们可以对其进行扩展:

改造查询规格参数接口

我们在原来的根据 gid(规格组id)查询规格参数的接口上,添加一个参数:cid,即商品分类id。

等一下, 考虑到以后可能还会根据是否搜索、是否为通用属性等条件过滤,我们多添加几个过滤条件:

@GetMapping("/params")
public ResponseEntity> querySpecParam(
    @RequestParam(value="gid", required = false) Long gid,
    @RequestParam(value="cid", required = false) Long cid,
    @RequestParam(value="searching", required = false) Boolean searching,
    @RequestParam(value="generic", required = false) Boolean generic
    ){
        List list =
                this.specificationService.querySpecParams(gid,cid,searching,generic);
        if(list == null || list.size() == 0){
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return ResponseEntity.ok(list);
    }

改造service:

public List querySpecParams(Long gid, Long cid, Boolean searching, Boolean generic) {
    SpecParam param = new SpecParam();
    param.setGroupId(gid);
    param.setCid(cid);
    param.setSearching(searching);
    param.setGeneric(generic);
    return this.specParamMapper.select(param);
}

如果param中有属性为null,则不会吧属性作为查询条件,因此该方法具备通用性,即可根据gid查询,也可根据cid查询。

测试


lyshop学习笔记七-商品管理(添加商品)_第3张图片
image

刷新页面测试:

[图片上传失败...(image-abff9f-1555491859890)]

1.5.SKU信息

Sku属性是SPU下的每个商品的不同特征,如图:

lyshop学习笔记七-商品管理(添加商品)_第4张图片
1529656674978

当我们填写一些属性后,会在页面下方生成一个sku表格,大家可以计算下会生成多少个不同属性的Sku呢?

当你选择了上图中的这些选项时:

  • 颜色共2种:夜空黑,绚丽红
  • 内存共2种:4GB,6GB
  • 机身存储1种:64GB

此时会产生多少种SKU呢? 应该是 2 * 2 * 1 = 4种,这其实就是在求笛卡尔积。

我们会在页面下方生成一个sku的表格:

lyshop学习笔记七-商品管理(添加商品)_第5张图片
1528856353718

1.7.页面表单提交

在sku列表的下方,有一个提交按钮:

[图片上传失败...(image-3f3a7a-1555491859890)]

并且绑定了点击事件:

[图片上传失败...(image-8f8986-1555491859890)]

点击后会组织数据并向后台提交:

[图片上传失败...(image-4f782d-1555491859890)]

提交:

[图片上传失败...(image-b00b0a-1555491859890)]

点击提交,查看控制台提交的数据格式:

[图片上传失败...(image-619613-1555491859890)]

  • 整体是一个json格式数据,包含Spu表所有数据:
    • brandId:品牌id
    • cid1、cid2、cid3:商品分类id
    • subTitle:副标题
    • title:标题
    • spuDetail:是一个json对象,代表商品详情表数据
      • afterService:售后服务
      • description:商品描述
      • packingList:包装列表
      • specialSpec:sku规格属性模板
      • genericSpec:通用规格参数
    • skus:spu下的所有sku数组,元素是每个sku对象:
      • title:标题
      • images:图片
      • price:价格
      • stock:库存
      • ownSpec:特有规格参数
      • indexes:特有规格参数的下标

1.7.后台实现

实体类

Spu

@Table(name = "tb_spu")
public class Spu {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long brandId;
    private Long cid1;// 1级类目
    private Long cid2;// 2级类目
    private Long cid3;// 3级类目
    private String title;// 标题
    private String subTitle;// 子标题
    private Boolean saleable;// 是否上架
    private Boolean valid;// 是否有效,逻辑删除用
    private Date createTime;// 创建时间
    private Date lastUpdateTime;// 最后修改时间
}

SpuDetail

@Table(name="tb_spu_detail")
public class SpuDetail {
    @Id
    private Long spuId;// 对应的SPU的id
    private String description;// 商品描述
    private String specTemplate;// 商品特殊规格的名称及可选值模板
    private String specifications;// 商品的全局规格属性
    private String packingList;// 包装清单
    private String afterService;// 售后服务
}

Sku

@Table(name = "tb_sku")
public class Sku {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long spuId;
    private String title;
    private String images;
    private Long price;
    private String ownSpec;// 商品特殊规格的键值对
    private String indexes;// 商品特殊规格的下标
    private Boolean enable;// 是否有效,逻辑删除用
    private Date createTime;// 创建时间
    private Date lastUpdateTime;// 最后修改时间
    @Transient
    private Integer stock;// 库存
}

注意:这里保存了一个库存字段,在数据库中是另外一张表保存的,方便查询。

Stock

@Table(name = "tb_stock")
public class Stock {
    @Id
    private Long skuId;
    private Integer seckillStock;// 秒杀可用库存
    private Integer seckillTotal;// 已秒杀数量
    private Integer stock;// 正常库存
}

Controller

四个问题:

  • 请求方式:POST

  • 请求路径:/goods

  • 请求参数:Spu的json格式的对象,spu中包含spuDetail和Sku集合。这里我们该怎么接收?我们之前定义了一个SpuBo对象,作为业务对象。这里也可以用它,不过需要再扩展spuDetail和skus字段:

    public class SpuBo extends Spu {
    
        @Transient
        String cname;// 商品分类名称
        @Transient
        String bname;// 品牌名称
        @Transient
        SpuDetail spuDetail;// 商品详情
        @Transient
        List skus;// sku列表
    }
    
  • 返回类型:无

代码:

/**
 * 新增商品
 * @param spu
 * @return
 */
@PostMapping
public ResponseEntity saveGoods(@RequestBody Spu spu) {
    try {
        this.goodsService.save(spu);
        return new ResponseEntity<>(HttpStatus.CREATED);
    } catch (Exception e) {
        e.printStackTrace();
        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

注意:通过@RequestBody注解来接收Json请求

Service

这里的逻辑比较复杂,我们除了要对SPU新增以外,还要对SpuDetail、Sku、Stock进行保存

@Transactional
public void save(SpuBo spu) {
    // 保存spu
    spu.setSaleable(true);
    spu.setValid(true);
    spu.setCreateTime(new Date());
    spu.setLastUpdateTime(spu.getCreateTime());
    this.spuMapper.insert(spu);
    // 保存spu详情
    spu.getSpuDetail().setSpuId(spu.getId());
    this.spuDetailMapper.insert(spu.getSpuDetail());

    // 保存sku和库存信息
    saveSkuAndStock(spu.getSkus(), spu.getId());
}

private void saveSkuAndStock(List skus, Long spuId) {
    for (Sku sku : skus) {
        if (!sku.getEnable()) {
            continue;
        }
        // 保存sku
        sku.setSpuId(spuId);
        // 初始化时间
        sku.setCreateTime(new Date());
        sku.setLastUpdateTime(sku.getCreateTime());
        this.skuMapper.insert(sku);

        // 保存库存信息
        Stock stock = new Stock();
        stock.setSkuId(sku.getId());
        stock.setStock(sku.getStock());
        this.stockMapper.insert(stock);
    }
}

Mapper

都是通用Mapper,略

你可能感兴趣的:(lyshop学习笔记七-商品管理(添加商品))