电商项目——商品新增/修改
商品新增
- 基本信息:主要是一些简单的文本数据,包含了SPU和SpuDetail的部分数据,如
- 商品分类:是SPU中的
cid1
,cid2
,cid3
属性 - 品牌:是spu中的
brandId
属性 - 标题:是spu中的
title
属性 - 子标题:是spu中的
subTitle
属性 - 售后服务:是SpuDetail中的
afterService
属性 - 包装列表:是SpuDetail中的
packingList
属性
- 商品分类:是SPU中的
- 商品描述:是SpuDetail中的
description
属性,数据较多,所以单独放一个页面 - 规格参数:商品规格信息,对应SpuDetail中的
genericSpec
属性 - SKU属性:spu下的所有Sku信息
基本数据
商品分类
商品分类信息查询之前已经做过,所以这里的级联选框已经实现完成。
品牌选择
页面
品牌也是一个下拉选框,不过其选项是不确定的,只有当用户选择了商品分类,才会把这个分类下的所有品牌展示出来。
所以页面编写了watch函数,监控商品分类的变化,每当商品分类值有变化,就会发起请求,查询品牌列表
接下来,只要编写后台接口,根据商品分类id,查询对应品牌即可。
后台接口
页面需要去后台查询品牌信息,我们自然需要提供:
controller
/*** 根据分类查询品牌* @param cid* @return*/
@GetMapping("cid/{cid}")
public ResponseEntity<List<Brand>> queryBrandByCategory(@PathVariable("cid") Long cid) {List<Brand> list = this.brandService.queryBrandByCategory(cid);if(list == null){new ResponseEntity<>(HttpStatus.NOT_FOUND);}return ResponseEntity.ok(list);
}
service
public List<Brand> queryBrandByCategory(Long cid) {return this.brandMapper.queryByCategoryId(cid);
}
mapper
根据分类查询品牌有中间表,需要自己编写Sql:
@Select("SELECT b.* FROM tb_category_brand cb LEFT JOIN tb_brand b ON cb.brand_id = b.id WHERE cb.category_id = #{cid}")
List<Brand> queryByCategoryId(Long cid);
其它文本框
剩余的几个属性:标题、子标题等都是普通文本框,直接填写即可,没有需要特别注意的
商品描述
商品描述信息比较复杂,而且图文并茂,甚至包括视频。
这样的内容,一般都会使用富文本编辑器。
什么是富文本编辑器
通俗来说:富文本,就是比较丰富的文本编辑器。普通的框只能输入文字,而富文本还能给文字加颜色样式等。
富文本编辑器有很多,例如:KindEditor、Ueditor。但并不原生支持vue
Vue-Quill-Editor
GitHub的主页:https://github.com/surmon-china/vue-quill-editor
Vue-Quill-Editor是一个基于Quill的富文本编辑器:Quill的官网
使用指南
第一步:安装,使用npm命令:
npm install vue-quill-editor --save
第二步:加载,在js中引入:
全局使用:
import Vue from 'vue'
import VueQuillEditor from 'vue-quill-editor'const options = {}; /* { default global options } */Vue.use(VueQuillEditor, options); // options可选
局部使用:
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'import {quillEditor} from 'vue-quill-editor'var vm = new Vue({components:{quillEditor}
})
第三步:页面引用:
<quill-editor v-model="goods.spuDetail.description" :options="editorOption"/>
自定义的富文本编辑器
不过这个组件有个小问题,就是图片上传的无法直接上传到后台,因此我们对其进行了封装,支持了图片的上传。
使用也非常简单:
<v-stepper-content step="2"><v-editor v-model="goods.spuDetail.description" upload-url="/upload/image"/>
</v-stepper-content>
- upload-url:是图片上传的路径
- v-model:双向绑定,将富文本编辑器的内容绑定到goods.spuDetail.description
商品规格参数
规格参数的查询我们之前也已经编写过接口,因为商品规格参数也是与商品分类绑定,所以需要在商品分类变化后去查询,我们也是通过watch监控来实现
可以看到这里是根据商品分类id查询规格参数:SpecParam。我们之前写过一个根据gid(分组id)来查询规格参数的接口,我们可以对其进行扩展:
改造查询规格参数接口
我们在原来的根据 gid(规格组id)查询规格参数的接口上,添加一个参数:cid,即商品分类id。
等一下, 考虑到以后可能还会根据是否搜索、是否为通用属性等条件过滤,我们多添加几个过滤条件:
@GetMapping("/params")
public ResponseEntity<List<SpecParam>> querySpecParam(@RequestParam(value="gid", required = false) Long gid,@RequestParam(value="cid", required = false) Long cid,@RequestParam(value="searching", required = false) Boolean searching,@RequestParam(value="generic", required = false) Boolean generic){List<SpecParam> list =this.specificationService.querySpecParams(gid,cid,searching,generic);if(list == null || list.size() == 0){return new ResponseEntity<>(HttpStatus.NOT_FOUND);}return ResponseEntity.ok(list);}
改造service:
public List<SpecParam> querySpecParams(Long gid, Long cid, Boolean searching, Boolean generic) {SpecParam param = new SpecParam();param.setGroupId(gid);param.setCid(cid);param.setSearching(searching);param.setGeneric(generic);return this.specParamMapper.select(param);
}
如果param中有属性为null,则不会吧属性作为查询条件,因此该方法具备通用性,即可根据gid查询,也可根据cid查询。
SKU信息
Sku属性是SPU下的每个商品的不同特征,
当我们填写一些属性后,会在页面下方生成一个sku表格,大家可以计算下会生成多少个不同属性的Sku呢?
当你选择了上图中的这些选项时:
- 颜色共2种:夜空黑,绚丽红
- 内存共2种:4GB,6GB
- 机身存储1种:64GB
此时会产生多少种SKU呢? 应该是 2 * 2 * 1 = 4种,这其实就是在求笛卡尔积。
我们会在页面下方生成一个sku的表格
页面表单提交
在sku列表的下方,有一个提交按钮
整体是一个json格式数据,包含Spu表所有数据:
- brandId:品牌id
- cid1、cid2、cid3:商品分类id
- subTitle:副标题
- title:标题
- spuDetail:是一个json对象,代表商品详情表数据
- afterService:售后服务
- description:商品描述
- packingList:包装列表
- specialSpec:sku规格属性模板
- genericSpec:通用规格参数
- skus:spu下的所有sku数组,元素是每个sku对象:
- title:标题
- images:图片
- price:价格
- stock:库存
- ownSpec:特有规格参数
- indexes:特有规格参数的下标
后台实现
实体类
Spu
@Table(name = "tb_spu")
public class Spu {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private Long brandId;private Long cid1;// 1级类目private Long cid2;// 2级类目private Long cid3;// 3级类目private String title;// 标题private String subTitle;// 子标题private Boolean saleable;// 是否上架private Boolean valid;// 是否有效,逻辑删除用private Date createTime;// 创建时间private Date lastUpdateTime;// 最后修改时间
}
SpuDetail
@Table(name="tb_spu_detail")
public class SpuDetail {@Idprivate Long spuId;// 对应的SPU的idprivate String description;// 商品描述private String specTemplate;// 商品特殊规格的名称及可选值模板private String specifications;// 商品的全局规格属性private String packingList;// 包装清单private String afterService;// 售后服务
}
Sku
@Table(name = "tb_sku")
public class Sku {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private Long spuId;private String title;private String images;private Long price;private String ownSpec;// 商品特殊规格的键值对private String indexes;// 商品特殊规格的下标private Boolean enable;// 是否有效,逻辑删除用private Date createTime;// 创建时间private Date lastUpdateTime;// 最后修改时间@Transientprivate Integer stock;// 库存
}
注意:这里保存了一个库存字段,在数据库中是另外一张表保存的,方便查询。
Stock
@Table(name = "tb_stock")
public class Stock {@Idprivate Long skuId;private Integer seckillStock;// 秒杀可用库存private Integer seckillTotal;// 已秒杀数量private Integer stock;// 正常库存
}
Controller
四个问题:
请求方式:POST
请求路径:/goods
请求参数:Spu的json格式的对象,spu中包含spuDetail和Sku集合。这里我们该怎么接收?我们之前定义了一个SpuBo对象,作为业务对象。这里也可以用它,不过需要再扩展spuDetail和skus字段:
public class SpuBo extends Spu {@TransientString cname;// 商品分类名称@TransientString bname;// 品牌名称@TransientSpuDetail spuDetail;// 商品详情@TransientList<Sku> skus;// sku列表 }
返回类型:无
代码:
/*** 新增商品* @param spu* @return*/
@PostMapping
public ResponseEntity<Void> saveGoods(@RequestBody Spu spu) {try {this.goodsService.save(spu);return new ResponseEntity<>(HttpStatus.CREATED);} catch (Exception e) {e.printStackTrace();return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);}
}
注意:通过@RequestBody注解来接收Json请求
Service
这里的逻辑比较复杂,我们除了要对SPU新增以外,还要对SpuDetail、Sku、Stock进行保存
@Transactional
public void save(SpuBo spu) {// 保存spuspu.setSaleable(true);spu.setValid(true);spu.setCreateTime(new Date());spu.setLastUpdateTime(spu.getCreateTime());this.spuMapper.insert(spu);// 保存spu详情spu.getSpuDetail().setSpuId(spu.getId());this.spuDetailMapper.insert(spu.getSpuDetail());// 保存sku和库存信息saveSkuAndStock(spu.getSkus(), spu.getId());
}private void saveSkuAndStock(List<Sku> skus, Long spuId) {for (Sku sku : skus) {if (!sku.getEnable()) {continue;}// 保存skusku.setSpuId(spuId);// 初始化时间sku.setCreateTime(new Date());sku.setLastUpdateTime(sku.getCreateTime());this.skuMapper.insert(sku);// 保存库存信息Stock stock = new Stock();stock.setSkuId(sku.getId());stock.setStock(sku.getStock());this.stockMapper.insert(stock);}
}
Mapper
都是通用Mapper,略
商品修改
编辑按钮点击事件
在商品详情页,每一个商品后面,都会有一个编辑按钮
点击这个按钮,就会打开一个商品编辑窗口,我们看下它所绑定的点击事件
可以看到这里发起了两个请求,在查询商品详情和sku信息。
因为在商品列表页面,只有spu的基本信息:id、标题、品牌、商品分类等。比较复杂的商品详情(spuDetail)和sku信息都没有,编辑页面要回显数据,就需要查询这些内容。
因此,接下来我们就编写后台接口,提供查询服务接口。
查询SpuDetail接口
controller
需要分析的内容:
- 请求方式:GET
- 请求路径:/spu/detail/{id}
- 请求参数:id,应该是spu的id
- 返回结果:SpuDetail对象
@GetMapping("/spu/detail/{id}")
public ResponseEntity<SpuDetail> querySpuDetailById(@PathVariable("id") Long id) {SpuDetail detail = this.goodsService.querySpuDetailById(id);if (detail == null) {return new ResponseEntity<>(HttpStatus.NOT_FOUND);}return ResponseEntity.ok(detail);
}
service
public SpuDetail querySpuDetailById(Long id) {return this.spuDetailMapper.selectByPrimaryKey(id);
}
查询sku
分析
- 请求方式:Get
- 请求路径:/sku/list
- 请求参数:id,应该是spu的id
- 返回结果:sku的集合
controller
@GetMapping("sku/list")
public ResponseEntity<List<Sku>> querySkuBySpuId(@RequestParam("id") Long id) {List<Sku> skus = this.goodsService.querySkuBySpuId(id);if (skus == null || skus.size() == 0) {return new ResponseEntity<>(HttpStatus.NOT_FOUND);}return ResponseEntity.ok(skus);
}
service
需要注意的是,为了页面回显方便,我们一并把sku的库存stock也查询出来
public List<Sku> querySkuBySpuId(Long spuId) {// 查询skuSku record = new Sku();record.setSpuId(spuId);List<Sku> skus = this.skuMapper.select(record);for (Sku sku : skus) {// 同时查询出库存sku.setStock(this.stockMapper.selectByPrimaryKey(sku.getId()).getStock());}return skus;
}
页面提交
这里的保存按钮与新增其实是同一个,因此提交的逻辑也是一样的,这里不再赘述。
随便修改点数据,然后点击保存,可以看到浏览器已经发出请求
后台实现
Controller
- 请求方式:PUT
- 请求路径:/goods
- 请求参数:Spubo对象
- 返回结果:无
/*** 新增商品* @param spu* @return*/
@PutMapping
public ResponseEntity<Void> updateGoods(@RequestBody SpuBo spu) {try {this.goodsService.update(spu);return new ResponseEntity<>(HttpStatus.NO_CONTENT);} catch (Exception e) {e.printStackTrace();return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);}
}
Service
spu数据可以修改,但是SKU数据无法修改,因为有可能之前存在的SKU现在已经不存在了,或者以前的sku属性都不存在了。比如以前内存有4G,现在没了。
因此这里直接删除以前的SKU,然后新增即可。
代码:
@Transactional
public void update(SpuBo spu) {// 查询以前skuList<Sku> skus = this.querySkuBySpuId(spu.getId());// 如果以前存在,则删除if(!CollectionUtils.isEmpty(skus)) {List<Long> ids = skus.stream().map(s -> s.getId()).collect(Collectors.toList());// 删除以前库存Example example = new Example(Stock.class);example.createCriteria().andIn("skuId", ids);this.stockMapper.deleteByExample(example);// 删除以前的skuSku record = new Sku();record.setSpuId(spu.getId());this.skuMapper.delete(record);}// 新增sku和库存saveSkuAndStock(spu.getSkus(), spu.getId());// 更新spuspu.setLastUpdateTime(new Date());spu.setCreateTime(null);spu.setValid(null);spu.setSaleable(null);this.spuMapper.updateByPrimaryKeySelective(spu);// 更新spu详情this.spuDetailMapper.updateByPrimaryKeySelective(spu.getSpuDetail());
}
mapper
与以前一样。
电商项目——商品新增/修改相关推荐
- 电商项目——商品服务-API-属性分组——第十一章——上篇
电商项目--初识电商--第一章--上篇 电商项目--分布式基础概念和电商项目微服务架构图,划分图的详解--第二章--上篇 电商项目--电商项目的虚拟机环境搭建_VirtualBox,Vagrant-- ...
- 微信小程序电商项目商品详情页开发实战之数据绑定与事件应用
各位CSDN的朋友,我们都知道,现在微信小程序电商平台特别火爆,所以我将以一个生鲜电商项目为例,为大家讲述微信小程序的实战化开发,价值几万元的成熟项目,你可千万不要错过哦. 大家直接通过视频链接直接看 ...
- 电商项目—商品的spu、sku概念及其之间的关系
电商项目-商品的spu.sku概念及其之间的关系 电商项目中涉及到商品时必然会遇到的几个概念,SPU.SKU.单品等.彻底搞懂和明白了这几个概念对我们设计商品表是十分必要的前提条件. SPU:标准化产 ...
- 电商项目(一)---------Sku和Spu以及电商项目商品的设计思路
一,在电商项目里面为了准确的描述商品的区别,我们抽象出来两个概念,Spu和Sku这两个概念. Spu(标准产品单位 ):一组具有共同属性的商品集 Sku(库存量单位):SPU商品集因具体特性不同而细分 ...
- 电商项目商品详情页架构设计
当用户进入京东首页,点击搜索手机进入搜索页面,点击一款手机进入商品详情页面,主要展示商品的信息,主要分为三块信息: 1:基本信息:展示商品的基本信息,包括sku价格等基本信息. 2:商品描述(商品详情 ...
- 电商项目——商品规格管理
商品规格管理 商品规格数据结构 淘淘商城是一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别.为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU SPU和SKU SPU:S ...
- 【愚公系列】2022年10月 微信小程序-电商项目-商品购物车功能实现
文章目录 前言 一.商品购物车功能实现 二.效果 前言 在电商的核心交易流程中,购物车是其中非常重要的一环,它承担商品加购.价格计算.促销活动展示等功能,与会员系统.商品系统.库存系统.订单系统等紧密 ...
- 【愚公系列】2022年11月 微信小程序-优购电商项目-商品支付页面
文章目录 前言 1. 商品⽀付页面设计规范 一.商品支付页面 1.业务逻辑 2.涉及的接口数据 3. 关键技术 二.商品购物车页面相关代码 1.页面代码 2.效果 前言 1. 商品⽀付页面设计规范 第 ...
- 电商项目商品搜索模块 - ESik分词器安装
2019独角兽企业重金招聘Python工程师标准>>> 洋鼹鼠-满足国人一切海外需求! 洋鼹鼠PC端搜索商品 com.ningpai.site.goods.controller.Go ...
最新文章
- oracle 11g asm 磁盘组兼容属性
- 御剑情缘服务器维护,御剑情缘7月27日更新维护内容及活动详解介绍
- 使用ABAP和JavaScript代码生成PDF文件的几种方式
- opencv python安装linux_Ubuntu16.04、Python3.6下安装opencv4遇到的问题
- 南华大学计算机学院吴取劲,一种基于图深度优先搜索的基本路径集自动生成优化算法-南华大学学报.PDF...
- python实现字母的加密和解密 字典_python实现AES加密与解密
- java xml 合并_Java中合并XML文档的合并
- 软考 | 软考高项论文该如何去写?
- 干货!闲鱼上哪些商品抢手?Python 分析后告诉你
- BZOJ #3653. 谈笑风生
- 两种web crawler方案
- GitHub开源了一款程序员摸鱼神器!上班摸鱼还不会被老板发现。。。
- TCP三次握手中SYN,ACK,seq ack的含义
- 英安特1600说明书_英安特AW-BM1600-8A电话线/GSM/IP三网合一报警控制主机
- 洛谷 1078 文化之旅
- 一阶电路实验报告心得_rc一阶电路的响应测试心得体会
- IP地址划分时192.168.1.0/24含义是什么
- hr看php简历,HR:“有这样的简历,才值得一见!”
- 学环境工程我后悔了_十大最烂专业 后悔坑人专业有哪些
- 跟着团子学SAP PS—项目的成本流 F-02/CJ20N/CJ88/KKA2
热门文章
- [CCPNet]Cascaded Context Pyramid for Full-Resolution 3D Semantic Scene Completion
- 机器学习——决策树剪枝处理
- 开篇 - 个人理财课程
- 数据采集和监控(SCADA)系统
- 大屏UI设计-看这一篇就够了
- 哪个公司的外贸邮箱好用?
- 对“歧视”以及“平等”的看法
- 北京精雕电主轴用海德汉编码器AK ERM280/2480/TTRERM2404/vs sensorik编码器RGM2G-AE3-V3Z参数接线定义
- 阿里云网盘福利码分享-100M/S不限速网盘
- 舍弗勒牵头德国氢能项目,助力制氢技术产业化发展