021 商品规格管理

1.商品规格数据结构

乐优商城是一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别。为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU,了解一下:

(1)SPU和SKU

SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集

SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品

以图为例来看:

  • 本页的 华为Mate10 就是一个商品集(SPU)

  • 因为颜色、内存等不同,而细分出不同的Mate10,如亮黑色128G版。(SKU)

可以看出:

  • SPU是一个抽象的商品集概念,为了方便后台的管理。

  • SKU才是具体要销售的商品,每一个SKU的价格、库存可能会不一样,用户购买的是SKU而不是SPU

(2)规格参数表

<1>表结构

我们看下规格参数的格式:

可以看到规格参数是分组的,每一组都有多个参数键值对。不过对于规格参数的模板而言,其值现在是不确定的,不同的商品值肯定不同,模板中只要保存组信息、组内参数信息即可。

因此我们设计了两张表:

  • tb_spec_group:组,与商品分类关联

  • tb_spec_param:参数名,与组关联,一对多

<2>规格组

规格参数分组表:tb_spec_group

CREATE TABLE `tb_spec_group` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `cid` bigint(20) NOT NULL COMMENT '商品分类id,一个分类下有多个规格组',
  `name` varchar(50) NOT NULL COMMENT '规格组的名称',
  PRIMARY KEY (`id`),
  KEY `key_category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='规格参数的分组表,每个商品分类下有多个规格参数组';

规格组有3个字段:

  • id:主键

  • cid:商品分类id,一个分类下有多个模板

  • name:该规格组的名称。

<3>规格参数

规格参数表:tb_spec_param

CREATE TABLE `tb_spec_param` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `cid` bigint(20) NOT NULL COMMENT '商品分类id',
  `group_id` bigint(20) NOT NULL,
  `name` varchar(255) NOT NULL COMMENT '参数名',
  `numeric` tinyint(1) NOT NULL COMMENT '是否是数字类型参数,true或false',
  `unit` varchar(255) DEFAULT '' COMMENT '数字类型参数的单位,非数字类型可以为空',
  `generic` tinyint(1) NOT NULL COMMENT '是否是sku通用属性,true或false',
  `searching` tinyint(1) NOT NULL COMMENT '是否用于搜索过滤,true或false',
  `segments` varchar(1000) DEFAULT '' COMMENT '数值类型参数,如果需要搜索,则添加分段间隔值,如CPU频率间隔:0.5-1.0',
  PRIMARY KEY (`id`),
  KEY `key_group` (`group_id`),
  KEY `key_category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COMMENT='规格参数组下的参数名';

通用属性:

  用一个布尔类型字段来标记是否为通用:

  • generic来标记是否为通用属性:

    • true:代表通用属性

    • false:代表sku特有属性

搜索过滤:

  与搜索相关的有两个字段:

  • searching:标记是否用作过滤

    • true:用于过滤搜索

    • false:不用于过滤

  • segments:某些数值类型的参数,在搜索时需要按区间划分,这里提前确定好划分区间

    • 比如电池容量,0~2000mAh,2000mAh~3000mAh,3000mAh~4000mAh

数据类型:

  某些规格参数可能为数值类型,这样的数据才需要划分区间,我们有两个字段来描述:

  • numberic:是否为数值类型

    • true:数值类型

    • false:不是数值类型

  • unit:参数的单位

 (3)表关系总结:

 2.商品规格参数管理

 (1)页面布局

<1>整体布局

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

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

页面结构(Specification.vue):

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

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

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

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

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

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

(2)规格组的查询

<1>树节点的点击事件

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

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

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

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

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

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

 

<2>页面查询规格组

来看下SpecGroup.vue中的实现:

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

<3>后端代码

最终代码截图:

实体类:

service业务代码:

(1)实体类:SpecGroup

package lucky.leyou.item.domain;

import javax.persistence.*;
import java.util.List;

@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;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getCid() {
        return cid;
    }

    public void setCid(Long cid) {
        this.cid = cid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<SpecParam> getParams() {
        return params;
    }

    public void setParams(List<SpecParam> params) {
        this.params = params;
    }
}

实体类:SpecParam

package lucky.leyou.item.domain;

import javax.persistence.*;

@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;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getCid() {
        return cid;
    }

    public void setCid(Long cid) {
        this.cid = cid;
    }

    public Long getGroupId() {
        return groupId;
    }

    public void setGroupId(Long groupId) {
        this.groupId = groupId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Boolean getNumeric() {
        return numeric;
    }

    public void setNumeric(Boolean numeric) {
        this.numeric = numeric;
    }

    public String getUnit() {
        return unit;
    }

    public void setUnit(String unit) {
        this.unit = unit;
    }

    public Boolean getGeneric() {
        return generic;
    }

    public void setGeneric(Boolean generic) {
        this.generic = generic;
    }

    public Boolean getSearching() {
        return searching;
    }

    public void setSearching(Boolean searching) {
        this.searching = searching;
    }

    public String getSegments() {
        return segments;
    }

    public void setSegments(String segments) {
        this.segments = segments;
    }
}

(2)在leyou-item-service中编写业务:

<1>mapper

SpecParamMapper

package lucky.leyou.item.mapper;

import lucky.leyou.item.domain.SpecParam;
import tk.mybatis.mapper.common.Mapper;

public interface SpecParamMapper extends Mapper<SpecParam> {
}

SpecGroupMapper

package lucky.leyou.item.mapper;

import lucky.leyou.item.domain.SpecGroup;
import tk.mybatis.mapper.common.Mapper;

public interface SpecGroupMapper extends Mapper<SpecGroup> {
}

<2>controller

package lucky.leyou.item.controller;

import lucky.leyou.item.domain.SpecGroup;
import lucky.leyou.item.service.ISpecificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
@RequestMapping(path = "/spec")
public class SpecificationController {

    @Autowired
    private ISpecificationService specificationService;

    /**
     * 根据分类id查询分组
     * @param cid
     * @return
     */
    @GetMapping(path = "/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);
    }
}

<3>service

接口

package lucky.leyou.item.service;

import lucky.leyou.item.domain.SpecGroup;

import java.util.List;

public interface ISpecificationService {

    /**
     * 根据分类id查询分组
     * @param cid
     * @return
     */
    public List<SpecGroup> queryGroupsByCid(Long cid);
}

实现类

package lucky.leyou.item.service.impl;

import lucky.leyou.item.domain.SpecGroup;
import lucky.leyou.item.mapper.SpecGroupMapper;
import lucky.leyou.item.mapper.SpecParamMapper;
import lucky.leyou.item.service.ISpecificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SpecificationServiceImpl implements ISpecificationService {
    @Autowired
    private SpecGroupMapper specGroupMapper;

    @Autowired
    private SpecParamMapper specParamMapper;

    /**
     * 根据分类id查询分组
     * @param cid
     * @return
     */
    @Override
    public List<SpecGroup> queryGroupsByCid(Long cid) {
        SpecGroup specGroup = new SpecGroup();
        specGroup.setCid(cid);
        return this.specGroupMapper.select(specGroup);
    }
}

<4>效果图

(3)规格参数查询

<1>表格切换

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

我们看下事件处理:

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

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

事件处理:

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

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

 

<2>页面查询规格参数

我们来看SpecParam.vue的实现:

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

<3>后台代码

(1)在SpecificationController类中添加如下方法

/**
     * 根据条件查询规格参数
     * @param gid
     * @return
     */
    @GetMapping(path = "/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);
    }

(2)在ISpecificationService及其实现类中添加如下方法

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

<4>重启微服务

<5>效果图

原文地址:https://www.cnblogs.com/luckyplj/p/11526021.html