乐优商城学习笔记七-商品管理(添加商品)

0.学习目标

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

1.商品新增

1.1.页面预览

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

1528083727447

就会出现一个弹窗:

1528086595597

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

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

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

1528102363342

1.2.弹窗事件

弹窗是一个独立组件:

1528084394245

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

1528084463927

并且在页面中渲染:

1529806981038

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

1528084601205

1528084678549

1.3.基本数据

我们先来看下基本数据:

1528086595597

1.3.1.商品分类

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

1528101303110

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

1528101483023

1528101622952

1528101649596

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;
            });

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

1528102075895

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

后台接口

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

controller

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

service

public List<Brand> 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<Brand> queryByCategoryId(Long cid);

1.4.商品规格参数

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

1529631056153

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

改造查询规格参数接口

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

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

@GetMapping("/params")
public ResponseEntity<List<SpecParam>> 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<SpecParam> 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<SpecParam> 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查询。

测试

刷新页面测试:

1529631887407

1.5.SKU信息

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

1529656674978

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

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

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

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

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

1528856353718

1.7.页面表单提交

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

1528860775149

并且绑定了点击事件:

1528860848276

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

1528861258072

提交:

1528861282193

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

1529667397378

  • 整体是一个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<Sku> skus;// sku列表
    }
    
  • 返回类型:无

代码:

/**
 * 新增商品
 * @param spu
 * @return
 */
@PostMapping
public ResponseEntity<Void> 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<Sku> 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,略

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值