乐优商城(04)–商品规格
一、商品规格数据结构
乐优商城是一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别。为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU,了解一下:
1.1.SPU和SKU
SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集
SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品
可以看出:
- SPU是一个抽象的商品集概念,为了方便后台的管理。
- SKU才是具体要销售的商品,每一个SKU的价格、库存可能会不一样,用户购买的是SKU而不是SPU
1.2、数据库表设计分析
数据库中关于商品规格的表有五个即:
tb_spu表分析
以上面华为手机商品为例,头部会显示分类和品牌,所以需要该商品所属的三个级别的分类id以及品牌id,
每个商品都会有标题和副标题,SPU是一个抽象的商品集概念,是为了方便后台的管理的,所以需要该商品的上下架的状态修改,以及创建时间和最后修改时间
这里是做了表的垂直拆分,将SPU的详情放到了另一张表:tb_spu_detail
tb_spu_detail这张表中的数据都比较大,为了不影响主表的查询效率所以拆分出这张表。
tb_spu_detail表分析
所以包含的字段有
description:描述
specification:规格
packaging_list:包装
after_service:售后服务
comment:评价
评价的数据量很庞大且数据价值很低,若存储在mysql中性能很不好,后面使用mongoDB存储
一个分类下的所有SPU具有类似的规格参数。SPU下的SKU可能会有不同的规格参数信息,因此:
- SPUDetail中保存通用的规格参数信息。
- SKU中保存特有规格参数。
需要注意的是这两个字段:generic_spec和special_spec。
-
generic_spec字段
其中保存通用规格参数信息的值,为了方便查询,使用json格式
- key:对应的规格参数的
spec_param
的id - value:对应规格参数的值
- key:对应的规格参数的
-
special_spec字段
以手机为例,品牌、操作系统等肯定是全局通用属性,内存、颜色等肯定是特有属性。
当确定了一个SPU,比如小米的:小米11
全局属性值都是固定的了:
品牌:小米 型号:小米11
特有属性举例:
颜色:[香槟金, 樱花粉, 磨砂黑] 内存:[6G, 8G] 机身存储:[56GB, 128GB, 256GB]
颜色、内存、机身存储,作为SKU特有属性,key虽然一样,但是SPU下的每一个SKU,其值都不一样,所以值会有很多,形成数组。
在SPU中,会把特有属性的所有值都记录下来,形成一个数组,也是json结构:
- key:规格参数id
- value:spu属性的数组
之所以在spu中也记录一份特有规格参数,是因为有时候需要把所有规格参数都查询出来,而不是只查询1个sku的属性。比如,商品详情页展示可选的规格参数时
tb_sku表分析
不同的商品分类,属性是不同,比如手机有内存,衣服有尺码,所以很难设计在同一张表中
但是注意SKU是具体的商品信息,也是有一些单个属性的
即:与spu关联、标题、产品图片、价格、特有属性
SKU的特有属性是商品规格参数的一部分:
这样规格参数中的属性可以标记成两部分:
- spu下所有sku共享的规格属性(称为全局属性)
- 每个sku不同的规格属性(称为特有属性)
查看搜索面板:
不难发现很多过滤条件在规格参数中都能找到,规格参数中的数据,将来会有一部分作为搜索条件来使用。所以在设计时,将这部分属性标记出来,将来做搜索的时候,作为过滤条件。要注意的是,无论是SPU的全局属性,还是SKU的特有属性,都有可能作为搜索过滤条件的,并不冲突,而是有一个交集:
-
indexes字段
保存的是特有属性待选项的下标组合,中间用’_'连接
拿上面的产品图为例:
选中了:羽墨黑,4GB+128GB,官方标配,优惠套餐2
indexes字段内容为:0_0_0_1
这样设计当用户点击选中一个特有属性,就能根据角标快速定位到sku。
tb_spec_group
可以看到规格参数是分组的,每一组都有多个参数键值对。不过对于规格参数的模板而言,其值现在是不确定的,不同的商品值肯定不同,模板中只要保存组信息、组内参数信息即可。
因此设计了两张表:
- tb_spec_group:组,与商品分类关联
- tb_spec_param:参数名,与组关联,一对多
该tb_spec_group
表中有三个字段:
- id:主键
- cid:商品分类id,一个分类下有多个模板
- name:该规格组的名称。
tb_spec_param
-
通用属性
用一个布尔类型字段来标记是否为通用:
- generic来标记是否为通用属性:
- true:代表通用属性
- false:代表sku特有属性
- generic来标记是否为通用属性:
-
搜索过滤
与搜索相关的有两个字段:
- searching:标记是否用作过滤
- true:用于过滤搜索
- false:不用于过滤
- segments:某些数值类型的参数,在搜索时需要按区间划分,这里提前确定好划分区间
- 比如电池容量,02000mAh,2000mAh3000mAh,3000mAh~4000mAh
- searching:标记是否用作过滤
-
数值类型
某些规格参数可能为数值类型,这样的数据才需要划分区间,我们有两个字段来描述:
- numberic:是否为数值类型
- true:数值类型
- false:不是数值类型
- unit:参数的单位
- numberic:是否为数值类型
二、商品规格管理
2.1、页面布局
打开规格参数页面,看到如下内容:
因为规格是跟商品分类绑定的,因此首先会展现商品分类树,并且提示你要选择商品分类,才能看到规格参数的模板。一起了解下页面的实现:
点击该页面,可以发现这里使用了v-layout
来完成页面布局,并且添加了row属性,代表接下来的内容是行布局(左右)。
可以看出页面分成2个部分:
<v-flex xs3>
:左侧,内部又分上下两部分:商品分类树及标题v-card-title
:标题部分,这里是提示信息,告诉用户要先选择分类,才能看到模板v-tree
:这里用到的是我们之前讲过的树组件,展示商品分类树,
<v-flex xs9 class="px-1">
:右侧:内部是规格参数展示
2.2、右侧规格
右侧规格最终效果:
可以看到右侧分为上下两部分:
- 上部:面包屑,显示当前选中的分类
- 下部:table,显示规格参数信息
页面实现:
这是一个spec-group
组件(规格组)和spec-param
组件(规格参数),这是提前定义的独立组件。在SpecGroup中定义了表格。
2.3、规格组的查询
2.3.1、前端页面
当点击树节点时,要将v-dialog
打开,因此必须绑定一个点击事件:(Specification.vue)
来看下handleClick
方法:(Specification.vue)
点击事件发生时,发生了两件事:
- 记录当前选中的节点,选中的就是商品分类
showGroup
被置为true,则规格组就会显示了。
同时,把被选中的节点(商品分类)的id传递给了SpecGroup
组件:(Specification.vue)
来看下SpecGroup.vue
中的实现:
查看页面控制台,可以看到请求已经发出:
2.3.1、后端实现
实体类
在leyou-item-interface
中添加实体类:
@Table(name = "tb_spec_param")
public class SpecParam {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; //主键
private Long cid; //商品分类id
private Long groupId; //分组id
private String name; //参数名
@Column(name = "`numeric`"