乐优商城(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:对应规格参数的值
  • 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特有属性
  • 搜索过滤

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

    • searching:标记是否用作过滤

      • true:用于过滤搜索
      • false:不用于过滤
    • segments:某些数值类型的参数,在搜索时需要按区间划分,这里提前确定好划分区间
      • 比如电池容量,02000mAh,2000mAh3000mAh,3000mAh~4000mAh
  • 数值类型

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

    • numberic:是否为数值类型

      • true:数值类型
      • false:不是数值类型
    • unit:参数的单位

二、商品规格管理

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;           //商品分类idprivate Long groupId;       //分组idprivate String name;        //参数名@Column(name = "`numeric`") //该字段在数据库中为关键字,需特殊处理private Boolean numeric;    //是否是数字类型参数private String unit;        //数字类型参数的单位private Boolean generic;    //是否是通用参数private Boolean searching;  //是否可用于查询private String segments;    //若数值类型为搜素,需添加一个数字范围//get和set方法
}
@Table(name = "tb_spec_group")
public class SpecGroup {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;        //主键private Long cid;       //分类id 一个分类下会有多个规格组private String name;    //规格组的名称@Transientprivate List<SpecParam> params;     //参数集合//get和set方法
}

mapper

/*** SpecGroup 通用mapper*/
public interface SpecGroupMapper extends Mapper<SpecGroup> {}

这里将SpecGroup和SpecParam两个放在一起实现

controller

  • 请求方式:get
  • 请求路径:/spec/groups/{cid} ,这里通过路径占位符传递商品分类的id
  • 请求参数:商品分类id
  • 返回结果:页面是直接把resp.data赋值给了groups:
@RestController
@RequestMapping("/spec")
public class SpecificationController {@Autowiredprivate SpecificationService specificationService;/*** 根据分类id查询参数组* @param cid* @return*/@GetMapping("/groups/{cid}")public ResponseEntity<List<SpecGroup>> queryGroupsByCid(@PathVariable("cid") Long cid){List<SpecGroup> specGroups = this.specificationService.queryGroupsByCid(cid);if (CollectionUtils.isEmpty(specGroups)){return ResponseEntity.notFound().build();}return ResponseEntity.ok(specGroups);}
}

service

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

实现类:

@Service
public class SpecificationServiceImpl implements SpecificationService {@Autowiredprivate SpecGroupMapper specGroupMapper;/*** 根据分类id查询参数组** @param cid* @return*/@Overridepublic List<SpecGroup> queryGroupsByCid(Long cid) {SpecGroup specGroup = new SpecGroup();specGroup.setCid(cid);return this.specGroupMapper.select(specGroup);}
}

2.4、规格参数查询

2.4.1、前端页面

表格切换:

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

看下事件处理:

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

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

事件处理:

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

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

来看SpecParam.vue的实现:

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

2.4.2、后端实现

mapper

/*** SpecParam 的通用mapper*/
public interface SpecParamMapper extends Mapper<SpecParam> {}

controller

  • 请求方式:GET
  • 请求路径:/spec/params
  • 请求参数:gid,分组id
  • 返回结果:该分组下的规格参数集合List<SpecParam>
/*** 根据gid查询具体参数* @param gid* @return*/
@GetMapping("/params")
public ResponseEntity<List<SpecParam>> queryParamsByGid(@RequestParam("gid") Long gid){List<SpecParam> specParams = this.specificationService.queryParamsByGid(gid);if (CollectionUtils.isEmpty(specParams)){return ResponseEntity.notFound().build();}return ResponseEntity.ok(specParams);
}

service

/*** 根据gid查询具体参数* @param gid* @return*/
List<SpecParam> queryParamsByGid(Long gid);

实现类:

@Autowired
private SpecParamMapper specParamMapper;/*** 根据gid查询具体参数** @param gid* @return*/
@Override
public List<SpecParam> queryParamsByGid(Long gid) {SpecParam specParam = new SpecParam();specParam.setGroupId(gid);return this.specParamMapper.select(specParam);
}

2.5、规格参数组增、删、改

2.5.1、前端页面

点击新增分组按钮,请求已发:

查看前端页面代码:

2.5.2、后端实现

增加:

controller

  • 请求方式:POST
  • 请求路径:/item/spec/group
  • 请求参数:specGroup
  • 返回结果:无,响应状态码为201
/*** 添加一个参数分组* @param specGroup* @return*/
@PostMapping("/group")
public ResponseEntity<Void> addSpecGroup(@RequestBody SpecGroup specGroup){this.specificationService.addSpecGroup(specGroup);return ResponseEntity.status(HttpStatus.CREATED).build();
}

service

/*** 添加一个参数分组* @param specGroup* @return*/
void addSpecGroup(SpecGroup specGroup);

实现类:

/*** 添加一个参数分组** @param specGroup* @return*/
@Override
public void addSpecGroup(SpecGroup specGroup) {this.specGroupMapper.insert(specGroup);
}

修改:

controller

  • 请求方式:PUT
  • 请求路径:/item/spec/group
  • 请求参数:specGroup
  • 返回结果:无,响应状态码为202
/*** 修改一个参数分组* @param specGroup* @return*/@PutMapping("/group")public ResponseEntity<Void> updateSpecGroup(@RequestBody SpecGroup specGroup){this.specificationService.updateSpecGroup(specGroup);return ResponseEntity.status(HttpStatus.ACCEPTED).build();}

service

/*** 修改一个参数分组* @param specGroup* @return*/
void updateSpecGroup(SpecGroup specGroup);

实现类

/*** 修改一个参数分组** @param specGroup* @return*/
@Override
public void updateSpecGroup(SpecGroup specGroup) {this.specGroupMapper.updateByPrimaryKeySelective(specGroup);
}

删除:

controller

  • 请求方式:POST
  • 请求路径:/item/spec/group
  • 请求参数:id,即参数组的id
  • 返回结果:无,响应状态码为200
/*** 根据组id删除参数组* @param gid* @return*/
@DeleteMapping("/group/{id}")
public ResponseEntity<Void> deleteSpecGroup(@PathVariable("id") Long gid){this.specificationService.deleteSpecGroup(gid);return ResponseEntity.ok().build();
}

service

/*** 根据组id删除参数组* @param gid* @return*/
void deleteSpecGroup(Long gid);

实现类:

/*** 根据组id删除参数组** @param gid* @return*/
@Override
@Transactional
public void deleteSpecGroup(Long gid) {//先查询该参数组内的参数List<SpecParam> specParams = queryParamsByGid(gid);//如果组内有数据if (specParams.size() > 0){//删除组内的具体参数specParams.forEach(specParam -> this.specParamMapper.deleteByPrimaryKey(specParam));}//再删除参数组this.specGroupMapper.deleteByPrimaryKey(gid);
}

2.6、规格参数的增、删、改

2.6.1、前端页面

点即新增参数,查看表单和发送的请求

查看前端页面代码:

2.6.2、后端实现

新增:

controller

  • 请求方式:POST
  • 请求路径:/item/spec/group
  • 请求参数:SpecParam
  • 返回结果:无,响应状态码为201
/*** 添加一个参数* @param specParam* @return*/
@PostMapping("/param")
public ResponseEntity<Void> addSpecParam(@RequestBody SpecParam specParam){this.specificationService.addSpecParam(specParam);return ResponseEntity.status(HttpStatus.CREATED).build();
}

service

/*** 添加一个参数* @param specParam* @return*/
void addSpecParam(SpecParam specParam);

实现类:

/*** 添加一个参数** @param specParam* @return*/
@Override
public void addSpecParam(SpecParam specParam) {this.specParamMapper.insert(specParam);
}

修改:

controller

  • 请求方式:PUT
  • 请求路径:/item/spec/group
  • 请求参数:SpecParam
  • 返回结果:无,响应状态码为202
/*** 修改一个参数* @param specParam* @return*/
@PutMapping("/param")
public ResponseEntity<Void> updateSpecParam(@RequestBody SpecParam specParam){this.specificationService.updateSpecParam(specParam);return ResponseEntity.status(HttpStatus.ACCEPTED).build();
}

service

/*** 修改一个参数* @param specParam* @return*/
void updateSpecParam(SpecParam specParam);

实现类:

/*** 修改一个参数** @param specParam* @return*/
@Override
public void updateSpecParam(SpecParam specParam) {this.specParamMapper.updateByPrimaryKeySelective(specParam);
}

删除:

controller

  • 请求方式:DELETE
  • 请求路径:/item/spec/group
  • 请求参数:id,即参数的id
  • 返回结果:无,响应状态码为202
/*** 根据参数的id删除参数* @param id* @return*/
@DeleteMapping("/param/{id}")
public ResponseEntity<Void> deleteSpecParam(@PathVariable("id") Long id){this.specificationService.deleteSpecParam(id);return ResponseEntity.ok().build();
}

service

/*** 根据参数的id删除参数* @param id* @return*/
void deleteSpecParam(Long id);

实现类:

/*** 根据参数的id删除参数** @param id* @return*/
@Override
public void deleteSpecParam(Long id) {this.specParamMapper.deleteByPrimaryKey(id);
}

乐优商城(04)--商品规格相关推荐

  1. 乐优商城day13(商品详情页,rabbitMQ安装)

    所有代码发布在 [https://github.com/hades0525/leyou] Day13(rabbitmq) 2019年2月13日 14:45 使用thymeleaf thymeleaf基 ...

  2. 乐优商城(四)商品规格管理

    文章目录 1. 商品规格 1.1 SPU 和 SKU 1.2 分析商品规格的关系 1.3 数据库设计 1.3.1 商品规格组表 1.3.2 商品规格参数表 2. 商品规格组 2.1 商品规格组前端 2 ...

  3. 【javaWeb微服务架构项目——乐优商城day05】——商品规格参数管理(增、删、改,查已完成),SPU和SKU数据结构,商品查询

    乐优商城day05 0.学习目标 1.商品规格数据结构 1.1.SPU和SKU 1.2.数据库设计分析 1.2.1.思考并发现问题 1.2.2.分析规格参数 1.2.3.SKU的特有属性 1.2.4. ...

  4. 乐优商城学习笔记五-商品规格管理

    0.学习目标 了解商品规格数据结构设计思路 实现商品规格查询 了解SPU和SKU数据结构设计思路 实现商品查询 了解商品新增的页面实现 独立编写商品新增后台功能 1.商品规格数据结构 乐优商城是一个全 ...

  5. 乐优商城(05)--商品管理

    乐优商城(05)–商品管理 一.导入图片资源 现在商品表中虽然有数据,但是所有的图片信息都是无法访问的,因此需要把图片导入到服务器中: 将images.zip文件上传至/leyou/static目录: ...

  6. 乐优商城笔记六:商品详情页

    使用模板引擎 Thymeleaf + nginx 完成商品详情页静态化 完成乐优商城商品详情页 搭建商品详情页微服务 创建子工程 GroupId:com.leyou.service ArtifactI ...

  7. 乐优商城 Day 09(thymeleaf,Rabbitmq,商品详情页,非教程)

    乐优商城学习Day09: 注意:此次代码都是在第八天的基础上 第八天的链接如下: https://blog.csdn.net/zcylxzyh/article/details/100859210 此次 ...

  8. 乐优商城之项目搭建(四)

    文章目录 (一)项目分类 (二)电商行业 (三)专业术语 (四)项目介绍 (五)技术选型 (六)开发环境 (七)搭建后台环境:父工程 (八)搭建后台环境:eureka (九)搭建后台环境:zuul ( ...

  9. 商城项目-商品规格数据结构

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

  10. 学习乐优商城中遇到的坑

    本人暑假期间学习了乐优商城这个项目,历时二十天,遇到了许多的麻烦,应该会有挺多人遇到.所以想记录一下,让大家跳坑. 首先,我建议jar包的版本,都选择和老师的一样,不然会出现一些莫名奇妙的报错. 在本 ...

最新文章

  1. MIT-THU未来城市创新网络即将和你见面!
  2. Application,Session和Cookies的区别
  3. Linux有问必答:如何检查Linux的内存使用状况
  4. 怎样将html转换do,html代码格式化 Reddo的教程
  5. CodeAbstract
  6. Github Actions:再次改变软件开发
  7. SpringBoot应用部署[转]
  8. 为GitHub项目加入Travis-CI的自动集成
  9. 建设自己拥有的B2C网站一般需要什么样的插件以及大体花费
  10. .php on line 0,控制台运行php报错 undefined symbol: gdImageCreateFromJpeg in Unknown on line 0 解决方法...
  11. CSS-----盒子模型
  12. 部署ganglia3.7
  13. 备课手记:把Ken Olsen换成姚明
  14. 马云的教、马云的会、马云的墓
  15. php调用手写板,手写板使用起来方便、快捷 其原理你知道吗?
  16. MySQL高级部分理论知识细讲
  17. /sdcard目录详解
  18. 第十五篇,man手册
  19. 袁春风老师:计算机系统基础(一) 第一章
  20. 日本味之素EB21二丁基乙基己酰基谷氨酸酰胺型胶凝化剂TDS产品说明书

热门文章

  1. 如何利用 Flutter 实现炫酷的 3D 卡片和帅气的 360° 展示效果
  2. 【软件工程】安装rational rose的步骤
  3. Dubbo入门详细教程
  4. 使用opencv-python读取webm格式的视频并转换成图片和avi格式的视频
  5. 四种优秀的数据库设计工具
  6. 各大浏览器兼容性报告
  7. Linux桌面文件被隐藏,在Deepin系统中隐藏桌面图标的好办法
  8. python中wordcloud函数不同形状云图_Python实现Wordcloud生成词云图的示例
  9. 思科网络设备命令大全
  10. cisco思科交换机命令参考大全