目录
四、商品修改
4.1 点击编辑出现弹窗
4.2 回显数据
回显数据就是把当前点击的商品数据传递到子组件(MyGoodsForm)中。父组件给子组件传递数据,通过props属性
第一步:在编辑时获取当前选中的品牌信息,记录在oldGoods中
先在data中定义属性,用来接收即将编辑的商品数据:
然后在页面触发编辑事件时,把当前的商品信息传递给editGoods方法:
第二步:把获取到的oldGoods数据传递给子组件
第三步:在子组件中通过props接收要编辑的数据:
通过watch函数监控oldGoods的变化,将值传递给本地goods,完成数据回显。
oldGoods:{
deep: true,
handler(val){
if (val !== null && val.spuDetail !== null){
this.goods = Object.deepCopy(val);
this.isEdit = true;
this.oldData = val.spuDetail.specifications;
}else {
this.clear();
}
}
}
4.2.1 存在的问题
看一下传入editGoods中的参数到底是什么:
为什么skus和spuDetail中没有数据呢?是因为分页查询时还没有这两个字段,这两个字段是在新增商品时在spuBo中添加的,所以值为空。但是数据回显时这两个字段非常重要,所以要新增接口。
4.2.2 解决方案
新增请求,查询skus和spuDeatil。
editGoods(oldGoods){
console.log(oldGoods);
this.oldGoods = oldGoods;
//构造商品分类
const cname=oldGoods.cname.split("/");
const categories=[];
categories.push({id:oldGoods.cid1,name:cname[0]});
categories.push({id:oldGoods.cid2,name:cname[1]});
categories.push({id:oldGoods.cid3,name:cname[2]});
this.oldGoods.categories = categories;
this.$http.get("/item/goods/spu/"+oldGoods.id).then(({data}) => {
this.isEdit = true;
this.oldGoods.skusList = data.skus;
this.oldGoods.spuDetail = data.spuDetail;
this.oldGoods.spuDetail.specTemplate = JSON.parse(data.spuDetail.specTemplate);
this.oldGoods.spuDetail.specifications = JSON.parse(data.spuDetail.specifications);
//显示弹窗
this.show = true;
}).catch();
}
}
结果:
除了skus和spuDeatil外其他字段都为空,因为在oldGoods中已经都有了。
后台接口:
Controller
/**
* 根据id查询商品
* @param id
* @return
*/
@GetMapping("/spu/{id}")
public ResponseEntity<SpuBo> queryGoodsById(@PathVariable("id") Long id){
SpuBo spuBo=this.goodsService.queryGoodsById(id);
if (spuBo == null){
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.ok(spuBo);
}
Service
接口
/**
* 根据id查询商品信息
* @param id
* @return
*/
SpuBo queryGoodsById(Long id);
实现类
/**
* 根据id查询商品信息
* @param id
* @return
*/
@Override
public SpuBo queryGoodsById(Long id) {
/**
* 第一页所需信息如下:
* 1.商品的分类信息、所属品牌、商品标题、商品卖点(子标题)
* 2.商品的包装清单、售后服务
*/
Spu spu=this.spuMapper.selectByPrimaryKey(id);
SpuDetail spuDetail = this.spuDetailMapper.selectByPrimaryKey(spu.getId());
Example example = new Example(Sku.class);
example.createCriteria().andEqualTo("spuId",spu.getId());
List<Sku> skuList = this.skuMapper.selectByExample(example);
List<Long> skuIdList = new ArrayList<>();
for (Sku sku : skuList){
System.out.println(sku);
skuIdList.add(sku.getId());
}
List<Stock> stocks = this.stockMapper.selectByIdList(skuIdList);
for (Sku sku:skuList){
for (Stock stock : stocks){
if (sku.getId().equals(stock.getSkuId())){
sku.setStock(stock.getStock());
}
}
}
SpuBo spuBo = new SpuBo();
spuBo.setSpuDetail(spuDetail);
spuBo.setSkus(skuList);
return spuBo;
}
spuDetail直接通过其对应的mapper查询即可
skuList根据spu的id查询得到,但是里面没有库存信息,所以需要从tb_stock表中继续查询,然后对stock字段进行赋值。
Mapper
都是通用mapper,当需要使用id数组查询时,继承通用mapper的SelectByIdListMapper即可。
4.2.3 前端执行逻辑
通过watch函数监听从父组件传过来的oldGoods,先把它赋值给goods,然后再将spuDetail下的specifications字段赋值给oldData,方便以后对数据进行处理。
在给goods赋值时,监听goods中的categories函数开始执行:
'goods.categories':{
deep:true,
handler(val){
//判断商品分类是否存在,存在才查询
if(val && val.length > 0) {
//根据分类查询品牌
this.$http.get("/item/brand/cid/" + this.goods.categories[2].id).then(({data}) => {
this.brandOptions = data;
}).catch();
//根据分类查询规格参数模板
this.$http.get("/item/spec/" + this.goods.categories[2].id).then(({data}) => {
//保存全部规格
//过滤出SKU属性,同时将SPU中的特有属性置空
this.allSpecs = data;
//console.log(this.allSpecs)
this.dataProces(data);
if (this.isEdit) {
this.editDataProces(this.oldData);
}
}).catch()
}
}
}
此时,商品品牌信息和规格参数模板就可以获取到。只不过在此基础上需要对oldData中的数据进行处理,因为此时通过dataProces函数,已经把所有普通和特殊规格参数模板信息处理好了,就差对应的值了,editDataProces的作用就是“赋值”,即数据回填。因为新增和修改是在同一个对话框中进行的,所以使用这种方法来进行数据回显。
dataProces(temp){
let commonTemplate = [];
let specialTemplate = [];
let count = 0;
temp.forEach(({params}) => {
params.forEach(({k, options, global}) => {
if (!global) {
specialTemplate.push({
k, options, selected: []
})
}
})
});
for (let i=0;i<temp.length;i++){
if (temp[i].params.length > 0){
let temp2 = temp[i].params;
let param = [];
for (let j = 0;j < temp2.length; j++){
if (temp2[j].global){
param.push(temp2[j]);
}
}
if (count !== temp2.length && param.length !== 0) {
commonTemplate.push({
group: temp[i].group,
params: param,
});
}
}
}
this.specialSpecs = specialTemplate;
this.specifications = commonTemplate;
},
//修改商品时数据处理
editDataProces(temp){
/**
* 特殊属性数据处理
* 将用户选择的相关信息显示到对应区域
* @type {Array}
*/
//console.log(temp);
const editSpecialTemplate = [];
temp.forEach(({params}) => {
params.forEach(({k, v,options,global}) => {
//要进行测试,特有属性不是数组!!!!!!
if (!global) {
if (options === null) {
editSpecialTemplate.push({
k, v
})
}
else {
editSpecialTemplate.push({
k, selected:options
})
}
}
})
});
for (let i = 0;i < editSpecialTemplate.length;i++){
for (let j = 0;j < this.specialSpecs.length;j++){
if (editSpecialTemplate[i].k === this.specialSpecs[j].k){
this.specialSpecs[i].selected = editSpecialTemplate[j].selected;
}
}
}
const editCommonTemplate = [];
let count = 0;
for (let i=0;i<temp.length;i++){
if (temp[i].params.length > 0){
let temp2 = temp[i].params;
let param = [];
for (let j = 0;j < temp2.length; j++){
if (temp2[j].global){
param.push(temp2[j]);
}
}
if (count !== temp2.length && param.length !== 0) {
editCommonTemplate.push({
group: temp[i].group,
params: param,
});
}
}
}
this.specifications.forEach(({group,params}) => {
editCommonTemplate.forEach(s => {
if (s.group === group){
s.params.forEach(({k,v}) => {
params.forEach(t => {
if (t.k === k){
t.v = v;
}
})
})
}
})
})
}
4.3 效果
4.4 修改商品信息的后台接口
商品信息修改涉及到四个表:tb_spu、tb_spuDetail、tb_stock\tb_sku,通过对应的mapper修改即可。
4.4.1 Controller
/**
* 修改商品
* @param spuBo
* @return
*/
@PutMapping
public ResponseEntity<Void> updateGoods(@RequestBody SpuBo spuBo){
this.goodsService.updateGoods(spuBo);
return ResponseEntity.status(HttpStatus.ACCEPTED).build();
}
4.4.2 Mapper
都是通用mapper
4.4.3 Service
接口
/**
* 更新商品信息
* @param spuBo
*/
void updateGoods(SpuBo spuBo);
实现类
对sku修改时要分情况讨论。
首先,要判断是否修改了特有属性:颜色、内存、机身存储。如果修改了的话,要将原来的所有sku删掉,然后将新的sku插入到数据库中;如果没有修改特有属性,只是对sku的价格、库存、图片等进行修改,那么就更新原来的sku,如果修改了是否启用,那么就需要将脏数据删除。(原来的状态是启用,修改完后变成未启用的数据)
/**
* 更新商品信息
* @param spuBo
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void updateGoods(SpuBo spuBo) {
/**
* 更新策略:
* 1.判断tb_spu_detail中的spec_template字段新旧是否一致
* 2.如果一致说明修改的只是库存、价格和是否启用,那么就使用update
* 3.如果不一致,说明修改了特有属性,那么需要把原来的sku全部删除,然后添加新的sku
*/
//更新spu
spuBo.setSaleable(true);
spuBo.setValid(true);
spuBo.setLastUpdateTime(new Date());
this.spuMapper.updateByPrimaryKeySelective(spuBo);
//更新spu详情
SpuDetail spuDetail = spuBo.getSpuDetail();
String oldTemp = this.spuDetailMapper.selectByPrimaryKey(spuBo.getId()).getSpecTemplate();
if (spuDetail.getSpecTemplate().equals(oldTemp)){
//相等,sku update
//更新sku和库存信息
updateSkuAndStock(spuBo.getSkus(),spuBo.getId(),true);
}else {
//不等,sku insert
//更新sku和库存信息
updateSkuAndStock(spuBo.getSkus(),spuBo.getId(),false);
}
spuDetail.setSpuId(spuBo.getId());
this.spuDetailMapper.updateByPrimaryKeySelective(spuDetail);
}
通过tag判断是新增还是修改
private void updateSkuAndStock(List<Sku> skus,Long id,boolean tag) {
//通过tag判断是insert还是update
//获取当前数据库中spu_id = id的sku信息
Example e = new Example(Sku.class);
e.createCriteria().andEqualTo("spuId",id);
//oldList中保存数据库中spu_id = id 的全部sku
List<Sku> oldList = this.skuMapper.selectByExample(e);
if (tag){
/**
* 判断是更新时是否有新的sku被添加:如果对已有数据更新的话,则此时oldList中的数据和skus中的ownSpec是相同的,否则则需要新增
*/
int count = 0;
for (Sku sku : skus){
if (!sku.getEnable()){
continue;
}
for (Sku old : oldList){
if (sku.getOwnSpec().equals(old.getOwnSpec())){
System.out.println("更新");
//更新
List<Sku> list = this.skuMapper.select(old);
if (sku.getPrice() == null){
sku.setPrice(0L);
}
if (sku.getStock() == null){
sku.setStock(0L);
}
sku.setId(list.get(0).getId());
sku.setCreateTime(list.get(0).getCreateTime());
sku.setSpuId(list.get(0).getSpuId());
sku.setLastUpdateTime(new Date());
this.skuMapper.updateByPrimaryKey(sku);
//更新库存信息
Stock stock = new Stock();
stock.setSkuId(sku.getId());
stock.setStock(sku.getStock());
this.stockMapper.updateByPrimaryKeySelective(stock);
//从oldList中将更新完的数据删除
oldList.remove(old);
break;
}else{
//新增
count ++ ;
}
}
if (count == oldList.size() && count != 0){
//当只有一个sku时,更新完因为从oldList中将其移除,所以长度变为0,所以要需要加不为0的条件
List<Sku> addSku = new ArrayList<>();
addSku.add(sku);
saveSkuAndStock(addSku,id);
count = 0;
}else {
count =0;
}
}
//处理脏数据
if (oldList.size() != 0){
for (Sku sku : oldList){
this.skuMapper.deleteByPrimaryKey(sku.getId());
Example example = new Example(Stock.class);
example.createCriteria().andEqualTo("skuId",sku.getId());
this.stockMapper.deleteByExample(example);
}
}
}else {
List<Long> ids = oldList.stream().map(Sku::getId).collect(Collectors.toList());
//删除以前的库存
Example example = new Example(Stock.class);
example.createCriteria().andIn("skuId",ids);
this.stockMapper.deleteByExample(example);
//删除以前的sku
Example example1 = new Example(Sku.class);
example1.createCriteria().andEqualTo("spuId",id);
this.skuMapper.deleteByExample(example1);
//新增sku和库存
saveSkuAndStock(skus,id);
}
}
private void saveSkuAndStock(List<Sku> skus, Long id) {
for (Sku sku : skus){
if (!sku.getEnable()){
continue;
}
//保存sku
sku.setSpuId(id);
//默认不参加任何促销
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);
}
}
4.5 测试
五、商品删除
5.1 点击删除商品
删除商品可以单个删除,也可以一次性删除多个,只有选中后才能进行删除操作。
单个删除:
传入函数的参数是当前操作的商品id
deleteItem(id){
const selectId = this.selected.map( s => {
return s.id;
});
if (selectId.length === 1 && selectId[0] === id) {
this.$message.confirm("删除后,不可恢复!").then(() => {
this.$http.delete("/item/goods/spu/"+id).then(() => {
this.getDataFromServer();
}).catch(() => {
this.$message.error("删除失败!");
})
}).catch(() => {
this.$message.info("删除取消!");
});
}
}
多个删除:
请求参数是拼接好的id字符串:
deleteAllGoods(){
const deleteGoodsId = this.selected.map(s => {
return s.id;
});
if (deleteGoodsId.length > 0){
this.$message.confirm("全部删除,不可恢复!").then(() => {
this.$http.delete("/item/goods/spu/"+deleteGoodsId.join("-")).then(() => {
this.getDataFromServer();
}).catch(() => {
this.$message.error("删除失败!");
})
}).catch(() => {
this.$message.info("删除取消!");
})
}
}
单个和多个删除对应的后台接口是一样的。
5.2 后台接口
5.2.1 Controller
/**
* 删除商品
* @param ids
* @return
*/
@DeleteMapping("/spu/{id}")
public ResponseEntity<Void> deleteGoods(@PathVariable("id") String ids){
String separator="-";
if (ids.contains(separator)){
String[] goodsId = ids.split(separator);
for (String id:goodsId){
this.goodsService.deleteGoods(Long.parseLong(id));
}
}
else {
this.goodsService.deleteGoods(Long.parseLong(ids));
}
return ResponseEntity.status(HttpStatus.OK).build();
}
5.2.2 Mapper
都是通用mapper。
5.2.3 Service
接口
/**
* 商品删除,单个多个二合一
* @param id
*/
void deleteGoods(long id);
实现类
/**
* 商品删除二合一(多个单个)
* @param id
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteGoods(long id) {
//删除spu表中的数据
this.spuMapper.deleteByPrimaryKey(id);
//删除spu_detail中的数据
Example example = new Example(SpuDetail.class);
example.createCriteria().andEqualTo("spuId",id);
this.spuDetailMapper.deleteByExample(example);
List<Sku> skuList = this.skuMapper.selectByExample(example);
for (Sku sku : skuList){
//删除sku中的数据
this.skuMapper.deleteByPrimaryKey(sku.getId());
//删除stock中的数据
this.stockMapper.deleteByPrimaryKey(sku.getId());
}
}
5.3 测试
六、商品下架/上架
6.1 预期效果
通过点击数据后面下架按钮完成商品下架,通过顶部的上下架状态按钮可以对显示的数据进行过滤。所以上下架也是对商品信息的修改,所以需要提供新的接口。
6.2 前端
6.2.1 下架按钮
上下架按钮是根据字段saleable来进行渲染的,点击时将当前数据的id传入soldOutPut函数。
soldOutPut(id){
const selectId = this.selected.map( s => {
return s.id;
});
if (selectId.length === 1 && selectId[0] === id) {
this.$http.put("/item/goods/spu/out/" + id).then(() => {
this.$message.success("操作成功!");
this.getDataFromServer();
}).catch(() => {
this.$message.error("操作失败!");
});
}else {
this.$message.info("选中后再进行操作!");
}
}
6.2.2 上下架状态按钮
这个是上下架的过滤按钮,通过watch函数来监控filter,然后调用getDataFromServer来进行数据过滤。
6.3 后台接口
6.3.1 Controller
/**
* 商品上下架
* @param id
* @return
*/
@PutMapping("/spu/out/{id}")
public ResponseEntity<Void> goodsSoldOut(@PathVariable("id") Long id){
this.goodsService.goodsSoldOut(id);
return ResponseEntity.status(HttpStatus.OK).build();
}
6.3.2 Mapper
都是通用mapper
6.3.3 Service
接口
/**
* 商品下架
* @param id
*/
void goodsSoldOut(Long id);
实现类
/**
* 商品下架
* @param id
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void goodsSoldOut(Long id) {
//下架或者上架spu中的商品
Spu oldSpu = this.spuMapper.selectByPrimaryKey(id);
Example example = new Example(Sku.class);
example.createCriteria().andEqualTo("spuId",id);
List<Sku> skuList = this.skuMapper.selectByExample(example);
if (oldSpu.getSaleable()){
//下架
oldSpu.setSaleable(false);
this.spuMapper.updateByPrimaryKeySelective(oldSpu);
//下架sku中的具体商品
for (Sku sku : skuList){
sku.setEnable(false);
this.skuMapper.updateByPrimaryKeySelective(sku);
}
}else {
//上架
oldSpu.setSaleable(true);
this.spuMapper.updateByPrimaryKeySelective(oldSpu);
//上架sku中的具体商品
for (Sku sku : skuList){
sku.setEnable(true);
this.skuMapper.updateByPrimaryKeySelective(sku);
}
}
}
6.4 测试