(9)规格参数

1.1.页面布局

1.1.1.整体布局

打开规格参数页面,看到如下内容:

商品分类树我们之前已经做过,所以这里可以直接展示出来。

因为规格是跟商品分类绑定的,因此首先会展现商品分类树,并且提示你要选择商品分类,才能看到规格参数的模板。一起了解下页面的实现:

页面结构:

这里使用了v-layout来完成页面布局,并且添加了row属性,代表接下来的内容是行布局(左右)。

可以看出页面分成1个部分:

  • <v-flex xs3>:左侧,内部又分上下两部分:商品分类树及标题

    • v-card-title:标题部分,这里是提示信息,告诉用户要先选择分类,才能看到模板

    • v-tree:这里用到的是我们之前讲过的树组件,展示商品分类树,

  • <v-flex xs9 class="px-1">:右侧:内部是规格参数展示

1.1.2.右侧规格

当我们点击一个分类时,最终要达到的效果:

可以看到右侧分为上下两部分:

  • 上部:面包屑,显示当前选中的分类

  • 下部:table,显示规格参数信息

页面实现:

可以看到右侧并不是我们熟悉的 v-data-table,而是一个spec-group组件(规格组)和spec-param组件(规格参数),这是我们定义的独立组件:

在SpecGroup中定义了表格:

 

1.2.规格组的查询

1.2.1.树节点的点击事件

当我们点击树节点时,要将v-dialog打开,因此必须绑定一个点击事件:(Specification.vue)

我们来看下handleClick方法:(Specification.vue)

点击事件发生时,发生了两件事:

  • 记录当前选中的节点,选中的就是商品分类

  • showGroup被置为true,则规格组就会显示了。

同时,我们把被选中的节点(商品分类)的id传递给了SpecGroup组件:(Specification.vue)

 

1.2.2.页面查询规格组

来看下SpecGroup.vue中的实现:

我们查看页面控制台,可以看到请求已经发出:

 

1.2.3.后端代码

实体类

leyou-item-interface中添加实体类:

内容:

@Table(name = "tb_spec_group")
public class SpecGroup {
​
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
​
    private Long cid;
​
    private String name;
​
    @Transient
    private List<SpecParam> params;
​
   // getter和setter省略
}
@Table(name = "tb_spec_param")
public class SpecParam {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long cid;
    private Long groupId;
    private String name;
    @Column(name = "`numeric`")
    private Boolean numeric;
    private String unit;
    private Boolean generic;
    private Boolean searching;
    private String segments;
    
    // getter和setter ...
}

leyou-item-service中编写业务:

mapper

public interface SpecGroupMapper extends Mapper<SpecGroup> {
}

controller

先分析下需要的东西,在页面的ajax请求中可以看出:

  • 请求方式:get

  • 请求路径:/spec/groups/{cid} ,这里通过路径占位符传递商品分类的id

  • 请求参数:商品分类id

  • 返回结果:页面是直接把resp.data赋值给了groups:

那么我们返回的应该是规格组SpecGroup的集合

代码:

@RestController
@RequestMapping("spec")
public class SpecificationController {
​
    @Autowired
    private SpecificationService specificationService;
​
    /**
     * 根据分类id查询分组
     * @param cid
     * @return
     */
    @GetMapping("groups/{cid}")
    public ResponseEntity<List<SpecGroup>> queryGroupsByCid(@PathVariable("cid")Long cid){
        List<SpecGroup> groups = this.specificationService.queryGroupsByCid(cid);
        if (CollectionUtils.isEmpty(groups)){
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(groups);
    }
}

service

@Service
public class SpecificationService {
​
    @Autowired
    private SpecGroupMapper groupMapper;
​
    /**
     * 根据分类id查询分组
     * @param cid
     * @return
     */
    public List<SpecGroup> queryGroupsByCid(Long cid) {
        SpecGroup specGroup = new SpecGroup();
        specGroup.setCid(cid);
        return this.groupMapper.select(specGroup);
    }
}

页面访问测试:

目前,我们数据库只为手机分类(76)提供了规格组:

我们访问:http://api.leyou.com/api/item/spec/groups/76

然后在后台系统中测试:

 

1.3.规格参数查询

1.3.1.表格切换

当我们点击规格组,会切换到规格参数显示,肯定是在规格组中绑定了点击事件:

我们看下事件处理:

可以看到这里是使用了父子通信,子组件触发了select事件

再来看下父组件的事件绑定:

事件处理:

这里我们记录了选中的分组,并且把标记设置为false,这样规格组就不显示了,而是显示:SpecParam

并且,我们把group也传递到spec-param组件:

 

1.3.2.页面查询规格参数

我们来看SpecParam.vue的实现:

查看页面控制台,发现请求已经发出:

报404,因为我们还没有实现后台逻辑,接下来就去实现。

1.3.3.后台实现

SpecificationController

分析:

  • 请求方式:GET

  • 请求路径:/spec/params

  • 请求参数:gid,分组id

  • 返回结果:该分组下的规格参数集合List<SpecParam>

代码:

/**
     * 根据条件查询规格参数
     * @param gid
     * @return
     */
@GetMapping("params")
public ResponseEntity<List<SpecParam>> queryParams(@RequestParam("gid")Long gid){
    List<SpecParam>  params = this.specificationService.queryParams(gid);
    if (CollectionUtils.isEmpty(params)){
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(params);
}

SpecificationService

@Autowired
private SpecParamMapper paramMapper;
​
/**
     * 根据条件查询规格参数
     * @param gid
     * @return
     */
public List<SpecParam> queryParams(Long gid) {
    SpecParam param = new SpecParam();
    param.setGroupId(gid);
    return this.paramMapper.select(param);
}

SpecParamMapper

public interface SpecParamMapper extends Mapper<SpecParam> {
}

测试:

2. spu sku

sku:标准产品单元,商品集,spuDetail(generic_spec:通用规格参数(id,value) special_spec:特殊规格参数(id,[value]))

spu:标准库存单元,具体的商品(indexes:0_0_0 own_spec:具体的值) stock

3. 商品查询

  1. 添加查询条件
  2. 添加过滤条件
  3. 添加分页
  4. 执行查询
  5. List<spu> --> List<spuBo>
  6. new PageResult<>(total,spuBos)

1.效果预览

接下来,我们实现商品管理的页面,先看下我们要实现的效果:

 

 

 可以看出整体是一个table,然后有新增按钮。

2.页面请求

先看整体页面结构(Goods.vue):

 

 

 并且在Vue实例挂载后就会发起查询(mounted调用getDataFromServer方法初始化数据):

 

 

 我们刷新页面,可以看到浏览器发起已经发起了查询商品数据的请求:

 

 

 因此接下来,我们编写接口即可。

3.后台提供接口

页面已经准备好,接下来在后台提供分页查询SPU的功能。

 

 

 

3.1.实体类

在leyou-item-interface工程中添加实体类:

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;// 最后修改时间
    // 省略getter和setter
}

SPU详情

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

3.2.mapper

public interface SpuMapper extends Mapper<Spu> {
}

3.3.controller

先分析:

  • 请求方式:GET

  • 请求路径:/spu/page

  • 请求参数:

    • page:当前页

    • rows:每页大小

    • key:过滤条件

    • saleable:上架或下架

  • 返回结果:商品SPU的分页信息。

    • 要注意,页面展示的是商品分类和品牌名称,而数据库中保存的是id,怎么办?

      我们可以新建一个类,继承SPU,并且拓展cname和bname属性,写到leyou-item-interface

public class SpuBo extends Spu {

    String cname;// 商品分类名称
    
    String bname;// 品牌名称
    
    // 略 。。
}

编写controller代码:

我们把与商品相关的一切业务接口都放到一起,起名为GoodsController,业务层也是这样

@Controller
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

    @GetMapping("spu/page")
    public ResponseEntity<PageResult<SpuBo>> querySpuBoByPage(
            @RequestParam(value = "key", required = false)String key,
            @RequestParam(value = "saleable", required = false)Boolean saleable,
            @RequestParam(value = "page", defaultValue = "1")Integer page,
            @RequestParam(value = "rows", defaultValue = "5")Integer rows
    ){
        PageResult<SpuBo> pageResult = this.goodsService.querySpuBoByPage(key, saleable, page, rows);
        if(CollectionUtils.isEmpty(pageResult.getItems())){
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(pageResult);
    }

}

3.4.service

所有商品相关的业务(包括SPU和SKU)放到一个业务下:GoodsService。

@Service
public class GoodsService {

    @Autowired
    private SpuMapper spuMapper;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private BrandMapper brandMapper;

    public PageResult<SpuBo> querySpuBoByPage(String key, Boolean saleable, Integer page, Integer rows) {

        Example example = new Example(Spu.class);
        Example.Criteria criteria = example.createCriteria();
        // 搜索条件
        if (StringUtils.isNotBlank(key)) {
            criteria.andLike("title", "%" + key + "%");
        }
        if (saleable != null) {
            criteria.andEqualTo("saleable", saleable);
        }

        // 分页条件
        PageHelper.startPage(page, rows);

        // 执行查询
        List<Spu> spus = this.spuMapper.selectByExample(example);
        PageInfo<Spu> pageInfo = new PageInfo<>(spus);

        List<SpuBo> spuBos = new ArrayList<>();
        spus.forEach(spu->{
            SpuBo spuBo = new SpuBo();
            // copy共同属性的值到新的对象
            BeanUtils.copyProperties(spu, spuBo);
            // 查询分类名称
            List<String> names = this.categoryService.queryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
            spuBo.setCname(StringUtils.join(names, "/"));

            // 查询品牌的名称
            spuBo.setBname(this.brandMapper.selectByPrimaryKey(spu.getBrandId()).getName());

            spuBos.add(spuBo);
        });

        return new PageResult<>(pageInfo.getTotal(), spuBos);

    }
}

4.5.Category中拓展查询名称的功能

页面需要商品的分类名称需要在这里查询,因此要额外提供查询分类名称的功能,

在CategoryService中添加功能:

public List<String> queryNamesByIds(List<Long> ids) {
    List<Category> list = this.categoryMapper.selectByIdList(ids);
    List<String> names = new ArrayList<>();
    for (Category category : list) {
        names.add(category.getName());
    }
    return names;
    // return list.stream().map(category -> category.getName()).collect(Collectors.toList());
}

mapper的selectByIdList方法是来自于通用mapper。不过需要我们在mapper上继承一个通用mapper接口:

public interface CategoryMapper extends Mapper<Category>, SelectByIdListMapper<Category, Long> { 
}

4.测试

刷新页面,查看效果:

基本与预览的效果一致,OK!

posted @ 2019-11-24 18:47  tunan96  阅读(290)  评论(0编辑  收藏  举报