乐优商城(十二)商品管理

目录

四、商品修改

4.1 点击编辑出现弹窗

4.2 回显数据

4.2.1 存在的问题

4.2.2 解决方案

4.2.3 前端执行逻辑

4.3 效果

4.4 修改商品信息的后台接口

4.4.1 Controller

4.4.2 Mapper

4.4.3 Service

4.5 测试 

五、商品删除

5.1 点击删除商品

5.2 后台接口

5.2.1 Controller

5.2.2 Mapper

5.2.3 Service

5.3 测试

六、商品下架/上架

6.1 预期效果

6.2 前端

6.2.1 下架按钮

6.2.2 上下架状态按钮

6.3 后台接口

6.3.1 Controller

6.3.2 Mapper

6.3.3 Service

6.4 测试


四、商品修改

4.1 点击编辑出现弹窗

4.2 回显数据

回显数据就是把当前点击的商品数据传递到子组件(MyGoodsForm)中。父组件给子组件传递数据,通过props属性

第一步:在编辑时获取当前选中的品牌信息,记录在oldGoods中

先在data中定义属性,用来接收即将编辑的商品数据:

然后在页面触发编辑事件时,把当前的商品信息传递给editGoods方法:

第二步:把获取到的oldGoods数据传递给子组件

第三步:在子组件中通过props接收要编辑的数据:

通过watch函数监控oldGoods的变化,将值传递给本地goods,完成数据回显。

        oldGoods:{
          deep: true,
          handler(val){
            if (val !== null && val.spuDetail !== null){
              this.goods = Object.deepCopy(val);
              this.isEdit = true;
              this.oldData = val.spuDetail.specifications;
            }else {
              this.clear();
            }
          }
        }

4.2.1 存在的问题

看一下传入editGoods中的参数到底是什么:

为什么skus和spuDetail中没有数据呢?是因为分页查询时还没有这两个字段,这两个字段是在新增商品时在spuBo中添加的,所以值为空。但是数据回显时这两个字段非常重要,所以要新增接口。

4.2.2 解决方案

新增请求,查询skus和spuDeatil。

        editGoods(oldGoods){
          console.log(oldGoods);
          this.oldGoods = oldGoods;
          //构造商品分类
          const cname=oldGoods.cname.split("/");
          const categories=[];
          categories.push({id:oldGoods.cid1,name:cname[0]});
          categories.push({id:oldGoods.cid2,name:cname[1]});
          categories.push({id:oldGoods.cid3,name:cname[2]});
          this.oldGoods.categories = categories;

          this.$http.get("/item/goods/spu/"+oldGoods.id).then(({data}) => {
            this.isEdit = true;
            this.oldGoods.skusList = data.skus;
            this.oldGoods.spuDetail = data.spuDetail;
            this.oldGoods.spuDetail.specTemplate = JSON.parse(data.spuDetail.specTemplate);
            this.oldGoods.spuDetail.specifications = JSON.parse(data.spuDetail.specifications);

            //显示弹窗
            this.show = true;
          }).catch();
        }
      }

结果:

除了skus和spuDeatil外其他字段都为空,因为在oldGoods中已经都有了。

后台接口:

Controller

    /**
     * 根据id查询商品
     * @param id
     * @return
     */
    @GetMapping("/spu/{id}")
    public ResponseEntity<SpuBo> queryGoodsById(@PathVariable("id") Long id){
        SpuBo spuBo=this.goodsService.queryGoodsById(id);
        if (spuBo == null){
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }
        return ResponseEntity.ok(spuBo);
    }

Service

接口

    /**
     * 根据id查询商品信息
     * @param id
     * @return
     */
    SpuBo queryGoodsById(Long id);

实现类

    /**
     * 根据id查询商品信息
     * @param id
     * @return
     */
    @Override
    public SpuBo queryGoodsById(Long id) {
        /**
         * 第一页所需信息如下:
         * 1.商品的分类信息、所属品牌、商品标题、商品卖点(子标题)
         * 2.商品的包装清单、售后服务
         */
        Spu spu=this.spuMapper.selectByPrimaryKey(id);
        SpuDetail spuDetail = this.spuDetailMapper.selectByPrimaryKey(spu.getId());

        Example example = new Example(Sku.class);
        example.createCriteria().andEqualTo("spuId",spu.getId());
        List<Sku> skuList = this.skuMapper.selectByExample(example);
        List<Long> skuIdList = new ArrayList<>();
        for (Sku sku : skuList){
            System.out.println(sku);
            skuIdList.add(sku.getId());
        }

        List<Stock> stocks = this.stockMapper.selectByIdList(skuIdList);

        for (Sku sku:skuList){
            for (Stock stock : stocks){
                if (sku.getId().equals(stock.getSkuId())){
                    sku.setStock(stock.getStock());
                }
            }
        }

        SpuBo spuBo = new SpuBo();
        spuBo.setSpuDetail(spuDetail);
        spuBo.setSkus(skuList);
        return spuBo;
    }

spuDetail直接通过其对应的mapper查询即可

skuList根据spu的id查询得到,但是里面没有库存信息,所以需要从tb_stock表中继续查询,然后对stock字段进行赋值。

Mapper

都是通用mapper,当需要使用id数组查询时,继承通用mapper的SelectByIdListMapper即可。

4.2.3 前端执行逻辑

通过watch函数监听从父组件传过来的oldGoods,先把它赋值给goods,然后再将spuDetail下的specifications字段赋值给oldData,方便以后对数据进行处理。

在给goods赋值时,监听goods中的categories函数开始执行:

        '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;
              }).catch();
              //根据分类查询规格参数模板
              this.$http.get("/item/spec/" + this.goods.categories[2].id).then(({data}) => {
                //保存全部规格
                //过滤出SKU属性,同时将SPU中的特有属性置空
                this.allSpecs = data;
                //console.log(this.allSpecs)
                this.dataProces(data);

                if (this.isEdit) {
                  this.editDataProces(this.oldData);
                }
              }).catch()
            }
          }
        }

此时,商品品牌信息和规格参数模板就可以获取到。只不过在此基础上需要对oldData中的数据进行处理,因为此时通过dataProces函数,已经把所有普通和特殊规格参数模板信息处理好了,就差对应的值了,editDataProces的作用就是“赋值”,即数据回填。因为新增和修改是在同一个对话框中进行的,所以使用这种方法来进行数据回显。

        dataProces(temp){
          let commonTemplate = [];
          let specialTemplate = [];
          let count = 0;
          temp.forEach(({params}) => {
            params.forEach(({k, options, global}) => {
              if (!global) {
                specialTemplate.push({
                  k, options, selected: []
                })
              }
            })
          });

          for (let i=0;i<temp.length;i++){
            if (temp[i].params.length > 0){
              let temp2 = temp[i].params;
              let param = [];
              for (let j = 0;j < temp2.length; j++){
                if (temp2[j].global){
                  param.push(temp2[j]);
                }
              }
              if (count !== temp2.length && param.length !== 0) {
                commonTemplate.push({
                  group: temp[i].group,
                  params: param,
                });
              }
            }
          }

          this.specialSpecs = specialTemplate;
          this.specifications = commonTemplate;
        },
        //修改商品时数据处理
        editDataProces(temp){
          /**
           * 特殊属性数据处理
           * 将用户选择的相关信息显示到对应区域
           * @type {Array}
           */
          //console.log(temp);
          const editSpecialTemplate = [];
            temp.forEach(({params}) => {
              params.forEach(({k, v,options,global}) => {
                //要进行测试,特有属性不是数组!!!!!!
                if (!global) {
                  if (options === null) {
                    editSpecialTemplate.push({
                      k, v
                    })
                  }
                  else {
                    editSpecialTemplate.push({
                      k, selected:options
                    })
                  }
                }
              })
            });
            for (let i = 0;i < editSpecialTemplate.length;i++){
              for (let j = 0;j < this.specialSpecs.length;j++){
                if (editSpecialTemplate[i].k === this.specialSpecs[j].k){
                  this.specialSpecs[i].selected = editSpecialTemplate[j].selected;
                }
              }
            }
          const editCommonTemplate = [];
          let count = 0;
          for (let i=0;i<temp.length;i++){
            if (temp[i].params.length > 0){
              let temp2 = temp[i].params;
              let param = [];
              for (let j = 0;j < temp2.length; j++){
                if (temp2[j].global){
                  param.push(temp2[j]);
                }
              }
              if (count !== temp2.length && param.length !== 0) {
                editCommonTemplate.push({
                  group: temp[i].group,
                  params: param,
                });
              }
            }
          }

          this.specifications.forEach(({group,params}) => {
              editCommonTemplate.forEach(s => {
                if (s.group === group){
                  s.params.forEach(({k,v}) => {
                    params.forEach(t => {
                      if (t.k === k){
                        t.v = v;
                      }
                    })
                  })
                }
              })
          })
        }

4.3 效果

4.4 修改商品信息的后台接口

商品信息修改涉及到四个表:tb_spu、tb_spuDetail、tb_stock\tb_sku,通过对应的mapper修改即可。

4.4.1 Controller

    /**
     * 修改商品
     * @param spuBo
     * @return
     */
    @PutMapping
    public ResponseEntity<Void> updateGoods(@RequestBody SpuBo spuBo){
        this.goodsService.updateGoods(spuBo);
        return ResponseEntity.status(HttpStatus.ACCEPTED).build();
    }

4.4.2 Mapper

都是通用mapper

4.4.3 Service

接口

    /**
     * 更新商品信息
     * @param spuBo
     */
    void updateGoods(SpuBo spuBo);

实现类

对sku修改时要分情况讨论。

首先,要判断是否修改了特有属性:颜色、内存、机身存储。如果修改了的话,要将原来的所有sku删掉,然后将新的sku插入到数据库中;如果没有修改特有属性,只是对sku的价格、库存、图片等进行修改,那么就更新原来的sku,如果修改了是否启用,那么就需要将脏数据删除。(原来的状态是启用,修改完后变成未启用的数据)

    /**
     * 更新商品信息
     * @param spuBo
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateGoods(SpuBo spuBo) {
        /**
         * 更新策略:
         *      1.判断tb_spu_detail中的spec_template字段新旧是否一致
         *      2.如果一致说明修改的只是库存、价格和是否启用,那么就使用update
         *      3.如果不一致,说明修改了特有属性,那么需要把原来的sku全部删除,然后添加新的sku
         */

        //更新spu
        spuBo.setSaleable(true);
        spuBo.setValid(true);
        spuBo.setLastUpdateTime(new Date());
        this.spuMapper.updateByPrimaryKeySelective(spuBo);

        //更新spu详情
        SpuDetail spuDetail = spuBo.getSpuDetail();
        String oldTemp = this.spuDetailMapper.selectByPrimaryKey(spuBo.getId()).getSpecTemplate();
        if (spuDetail.getSpecTemplate().equals(oldTemp)){
            //相等,sku update
            //更新sku和库存信息
            updateSkuAndStock(spuBo.getSkus(),spuBo.getId(),true);
        }else {
            //不等,sku insert
            //更新sku和库存信息
            updateSkuAndStock(spuBo.getSkus(),spuBo.getId(),false);
        }
        spuDetail.setSpuId(spuBo.getId());
        this.spuDetailMapper.updateByPrimaryKeySelective(spuDetail);
    }

通过tag判断是新增还是修改

 private void updateSkuAndStock(List<Sku> skus,Long id,boolean tag) {
        //通过tag判断是insert还是update
        //获取当前数据库中spu_id = id的sku信息
        Example e = new Example(Sku.class);
        e.createCriteria().andEqualTo("spuId",id);
        //oldList中保存数据库中spu_id = id 的全部sku
        List<Sku> oldList = this.skuMapper.selectByExample(e);
        if (tag){
            /**
             * 判断是更新时是否有新的sku被添加:如果对已有数据更新的话,则此时oldList中的数据和skus中的ownSpec是相同的,否则则需要新增
             */
            int count = 0;
            for (Sku sku : skus){
                if (!sku.getEnable()){
                    continue;
                }
                for (Sku old : oldList){
                    if (sku.getOwnSpec().equals(old.getOwnSpec())){
                        System.out.println("更新");
                        //更新
                        List<Sku> list = this.skuMapper.select(old);
                        if (sku.getPrice() == null){
                            sku.setPrice(0L);
                        }
                        if (sku.getStock() == null){
                            sku.setStock(0L);
                        }
                        sku.setId(list.get(0).getId());
                        sku.setCreateTime(list.get(0).getCreateTime());
                        sku.setSpuId(list.get(0).getSpuId());
                        sku.setLastUpdateTime(new Date());
                        this.skuMapper.updateByPrimaryKey(sku);
                        //更新库存信息
                        Stock stock = new Stock();
                        stock.setSkuId(sku.getId());
                        stock.setStock(sku.getStock());
                        this.stockMapper.updateByPrimaryKeySelective(stock);
                        //从oldList中将更新完的数据删除
                        oldList.remove(old);
                        break;
                    }else{
                        //新增
                        count ++ ;
                    }
                }
                if (count == oldList.size() && count != 0){
                    //当只有一个sku时,更新完因为从oldList中将其移除,所以长度变为0,所以要需要加不为0的条件
                    List<Sku> addSku = new ArrayList<>();
                    addSku.add(sku);
                    saveSkuAndStock(addSku,id);
                    count = 0;
                }else {
                    count =0;
                }
            }
            //处理脏数据
            if (oldList.size() != 0){
                for (Sku sku : oldList){
                    this.skuMapper.deleteByPrimaryKey(sku.getId());
                    Example example = new Example(Stock.class);
                    example.createCriteria().andEqualTo("skuId",sku.getId());
                    this.stockMapper.deleteByExample(example);
                }
            }
        }else {
            List<Long> ids = oldList.stream().map(Sku::getId).collect(Collectors.toList());
            //删除以前的库存
            Example example = new Example(Stock.class);
            example.createCriteria().andIn("skuId",ids);
            this.stockMapper.deleteByExample(example);
            //删除以前的sku
            Example example1 = new Example(Sku.class);
            example1.createCriteria().andEqualTo("spuId",id);
            this.skuMapper.deleteByExample(example1);
            //新增sku和库存
            saveSkuAndStock(skus,id);
        }


    }
    private void saveSkuAndStock(List<Sku> skus, Long id) {
        for (Sku sku : skus){
            if (!sku.getEnable()){
                continue;
            }
            //保存sku
            sku.setSpuId(id);
            //默认不参加任何促销
            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);
        }
    }

4.5 测试 

五、商品删除

5.1 点击删除商品

删除商品可以单个删除,也可以一次性删除多个,只有选中后才能进行删除操作。

单个删除:

传入函数的参数是当前操作的商品id

        deleteItem(id){
          const selectId = this.selected.map( s => {
            return s.id;
          });
          if (selectId.length === 1 && selectId[0] === id) {
            this.$message.confirm("删除后,不可恢复!").then(() => {
              this.$http.delete("/item/goods/spu/"+id).then(() => {
                this.getDataFromServer();
              }).catch(() => {
                this.$message.error("删除失败!");
              })
            }).catch(() => {
              this.$message.info("删除取消!");
            });
          }
        }

多个删除:

请求参数是拼接好的id字符串:

        deleteAllGoods(){

          const deleteGoodsId = this.selected.map(s => {
            return s.id;
          });

          if (deleteGoodsId.length > 0){
            this.$message.confirm("全部删除,不可恢复!").then(() => {
              this.$http.delete("/item/goods/spu/"+deleteGoodsId.join("-")).then(() => {
                this.getDataFromServer();
              }).catch(() => {
                this.$message.error("删除失败!");
              })
            }).catch(() => {
              this.$message.info("删除取消!");
            })
          }
        }

单个和多个删除对应的后台接口是一样的。

5.2 后台接口

5.2.1 Controller

    /**
     * 删除商品
     * @param ids
     * @return
     */
    @DeleteMapping("/spu/{id}")
    public ResponseEntity<Void> deleteGoods(@PathVariable("id") String ids){
        String separator="-";
        if (ids.contains(separator)){
            String[] goodsId = ids.split(separator);
            for (String id:goodsId){
                this.goodsService.deleteGoods(Long.parseLong(id));
            }
        }
        else {
            this.goodsService.deleteGoods(Long.parseLong(ids));
        }
        return ResponseEntity.status(HttpStatus.OK).build();
    }

5.2.2 Mapper

都是通用mapper。

5.2.3 Service

接口

    /**
     * 商品删除,单个多个二合一
     * @param id
     */
    void deleteGoods(long id);

实现类

    /**
     * 商品删除二合一(多个单个)
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteGoods(long id) {
        //删除spu表中的数据
        this.spuMapper.deleteByPrimaryKey(id);

        //删除spu_detail中的数据
        Example example = new Example(SpuDetail.class);
        example.createCriteria().andEqualTo("spuId",id);
        this.spuDetailMapper.deleteByExample(example);


        List<Sku> skuList = this.skuMapper.selectByExample(example);
        for (Sku sku : skuList){
            //删除sku中的数据
            this.skuMapper.deleteByPrimaryKey(sku.getId());
            //删除stock中的数据
            this.stockMapper.deleteByPrimaryKey(sku.getId());
        }

    }

5.3 测试

六、商品下架/上架

6.1 预期效果

通过点击数据后面下架按钮完成商品下架,通过顶部的上下架状态按钮可以对显示的数据进行过滤。所以上下架也是对商品信息的修改,所以需要提供新的接口。

6.2 前端

6.2.1 下架按钮

上下架按钮是根据字段saleable来进行渲染的,点击时将当前数据的id传入soldOutPut函数。

soldOutPut(id){
          const selectId = this.selected.map( s => {
            return s.id;
          });
          if (selectId.length === 1 && selectId[0] === id) {
            this.$http.put("/item/goods/spu/out/" + id).then(() => {
              this.$message.success("操作成功!");
              this.getDataFromServer();
            }).catch(() => {
              this.$message.error("操作失败!");
            });
          }else {
            this.$message.info("选中后再进行操作!");
          }
        }

6.2.2 上下架状态按钮

这个是上下架的过滤按钮,通过watch函数来监控filter,然后调用getDataFromServer来进行数据过滤。

6.3 后台接口

6.3.1 Controller

    /**
     * 商品上下架
     * @param id
     * @return
     */
    @PutMapping("/spu/out/{id}")
    public ResponseEntity<Void> goodsSoldOut(@PathVariable("id") Long id){
        this.goodsService.goodsSoldOut(id);
        return ResponseEntity.status(HttpStatus.OK).build();
    }

6.3.2 Mapper

都是通用mapper

6.3.3 Service

接口

    /**
     * 商品下架
     * @param id
     */
    void goodsSoldOut(Long id);

实现类

    /**
     * 商品下架
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void goodsSoldOut(Long id) {
        //下架或者上架spu中的商品

        Spu oldSpu = this.spuMapper.selectByPrimaryKey(id);
        Example example = new Example(Sku.class);
        example.createCriteria().andEqualTo("spuId",id);
        List<Sku> skuList = this.skuMapper.selectByExample(example);
        if (oldSpu.getSaleable()){
            //下架
            oldSpu.setSaleable(false);
            this.spuMapper.updateByPrimaryKeySelective(oldSpu);
            //下架sku中的具体商品
            for (Sku sku : skuList){
                sku.setEnable(false);
                this.skuMapper.updateByPrimaryKeySelective(sku);
            }

        }else {
            //上架
            oldSpu.setSaleable(true);
            this.spuMapper.updateByPrimaryKeySelective(oldSpu);
            //上架sku中的具体商品
            for (Sku sku : skuList){
                sku.setEnable(true);
                this.skuMapper.updateByPrimaryKeySelective(sku);
            }
        }

    }

6.4 测试

 

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值