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>效果图