1.商品规格数据结构
乐优商城是一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别。为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU,了解一下:
(1)SPU和SKU
SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品
-
-
因为颜色、内存等不同,而细分出不同的Mate10,如亮黑色128G版。(SKU)
可以看出:
-
SPU是一个抽象的商品集概念,为了方便后台的管理。
-
SKU才是具体要销售的商品,每一个SKU的价格、库存可能会不一样,用户购买的是SKU而不是SPU
(2)规格参数表
<1>表结构
我们看下规格参数的格式:
因此我们设计了两张表:
-
tb_spec_group:组,与商品分类关联
-
<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='规格参数的分组表,每个商品分类下有多个规格参数组';
-
-
cid:商品分类id,一个分类下有多个模板
-
<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:不是数值类型
-
-
(3)表关系总结:
2.商品规格参数管理
(1)页面布局
<1>整体布局
打开规格参数页面,看到如下内容:
因为规格是跟商品分类绑定的,因此首先会展现商品分类树,并且提示你要选择商品分类,才能看到规格参数的模板。一起了解下页面的实现:
页面结构(Specification.vue):
可以看出页面分成2个部分:
-
<v-flex xs3>
:左侧,内部又分上下两部分:商品分类树及标题-
v-card-title
:标题部分,这里是提示信息,告诉用户要先选择分类,才能看到模板 -
v-tree
:这里用到的是我们之前讲过的树组件,展示商品分类树,
-
-
<v-flex xs9 class="px-1">
(2)规格组的查询
<1>树节点的点击事件
-
记录当前选中的节点,选中的就是商品分类
-
showGroup
被置为true,则规格组就会显示了。
同时,我们把被选中的节点(商品分类)的id传递给了SpecGroup
<2>页面查询规格组
我们查看页面控制台,可以看到请求已经发出:
<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; } }
<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事件:
再来看下父组件的事件绑定:
事件处理:
并且,我们把group也传递到spec-param
<2>页面查询规格参数
查看页面控制台,发现请求已经发出:
<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>效果图