title: 樂優(yōu)商城學(xué)習(xí)筆記七-商品管理(添加商品)
date: 2019-04-17 16:18:10
tags:
- 樂優(yōu)商城
- java
- springboot
categories:
- 樂優(yōu)商城
0.學(xué)習(xí)目標(biāo)
- 獨(dú)立實(shí)現(xiàn)商品新增后臺(tái)
- 獨(dú)立實(shí)現(xiàn)商品編輯后臺(tái)
- 獨(dú)立搭建前臺(tái)系統(tǒng)頁面
1.商品新增
1.1.頁面預(yù)覽
當(dāng)我們點(diǎn)擊新增商品按鈕:
就會(huì)出現(xiàn)一個(gè)彈窗:
[圖片上傳失敗...(image-55dc49-1555491859890)]
里面把商品的數(shù)據(jù)分為了4部分來填寫:
- 基本信息:主要是一些簡(jiǎn)單的文本數(shù)據(jù),包含了SPU和SpuDetail的部分?jǐn)?shù)據(jù)导而,如
- 商品分類:是SPU中的
cid1
润绵,cid2
,cid3
屬性 - 品牌:是spu中的
brandId
屬性 - 標(biāo)題:是spu中的
title
屬性 - 子標(biāo)題:是spu中的
subTitle
屬性 - 售后服務(wù):是SpuDetail中的
afterService
屬性 - 包裝列表:是SpuDetail中的
packingList
屬性
- 商品分類:是SPU中的
- 商品描述:是SpuDetail中的
description
屬性,數(shù)據(jù)較多,所以單獨(dú)放一個(gè)頁面 - 規(guī)格參數(shù):商品規(guī)格信息,對(duì)應(yīng)SpuDetail中的
genericSpec
屬性 - SKU屬性:spu下的所有Sku信息
對(duì)應(yīng)到頁面中的四個(gè)stepper-content
:
[圖片上傳失敗...(image-4fa092-1555491859890)]
1.2.彈窗事件
彈窗是一個(gè)獨(dú)立組件:
[圖片上傳失敗...(image-591a50-1555491859890)]
并且在Goods組件中已經(jīng)引用它:
[圖片上傳失敗...(image-465e8e-1555491859890)]
并且在頁面中渲染:
[圖片上傳失敗...(image-44bb64-1555491859890)]
在新增商品
按鈕的點(diǎn)擊事件中颜屠,改變這個(gè)dialog
的show
屬性:
[圖片上傳失敗...(image-4e78e8-1555491859890)]
[圖片上傳失敗...(image-d90975-1555491859890)]
1.3.基本數(shù)據(jù)
我們先來看下基本數(shù)據(jù):
[圖片上傳失敗...(image-1e3aeb-1555491859890)]
1.3.1.商品分類
商品分類信息查詢我們之前已經(jīng)做過辰妙,所以這里的級(jí)聯(lián)選框已經(jīng)實(shí)現(xiàn)完成:
[圖片上傳失敗...(image-211bab-1555491859890)]
刷新頁面,可以看到請(qǐng)求已經(jīng)發(fā)出:
[圖片上傳失敗...(image-47d990-1555491859890)]
[圖片上傳失敗...(image-4d6430-1555491859890)]
[圖片上傳失敗...(image-4a702e-1555491859890)]
1.3.2.品牌選擇
頁面
品牌也是一個(gè)下拉選框汽纤,不過其選項(xiàng)是不確定的,只有當(dāng)用戶選擇了商品分類福荸,才會(huì)把這個(gè)分類下的所有品牌展示出來蕴坪。
所以頁面編寫了watch函數(shù),監(jiān)控商品分類的變化敬锐,每當(dāng)商品分類值有變化背传,就會(huì)發(fā)起請(qǐng)求,查詢品牌列表:
"goods.categories": {
deep: true,
handler(val) {
// 判斷商品分類是否存在台夺,存在才查詢
if (val && val.length > 0) {
// 根據(jù)分類查詢品牌
this.$http
.get("/item/brand/cid/" + this.goods.categories[2].id)
.then(({ data }) => {
this.brandOptions = data;
});
刷新頁面径玖,可以看到請(qǐng)求發(fā)起:
[圖片上傳失敗...(image-478a3f-1555491859890)]
接下來,我們只要編寫后臺(tái)接口颤介,根據(jù)商品分類id梳星,查詢對(duì)應(yīng)品牌即可。
后臺(tái)接口
頁面需要去后臺(tái)查詢品牌信息滚朵,我們自然需要提供:
controller
/**
* 根據(jù)分類查詢品牌
* @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
根據(jù)分類查詢品牌有中間表冤灾,需要自己編寫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);
1.4.商品規(guī)格參數(shù)
規(guī)格參數(shù)的查詢我們之前也已經(jīng)編寫過接口,因?yàn)樯唐芬?guī)格參數(shù)也是與商品分類綁定辕近,所以需要在商品分類變化后去查詢韵吨,我們也是通過watch監(jiān)控來實(shí)現(xiàn):
可以看到這里是根據(jù)商品分類id查詢規(guī)格參數(shù):SpecParam。我們之前寫過一個(gè)根據(jù)gid(分組id)來查詢規(guī)格參數(shù)的接口移宅,我們可以對(duì)其進(jìn)行擴(kuò)展:
改造查詢規(guī)格參數(shù)接口
我們?cè)谠瓉淼母鶕?jù) gid(規(guī)格組id)查詢規(guī)格參數(shù)的接口上归粉,添加一個(gè)參數(shù):cid,即商品分類id漏峰。
等一下糠悼, 考慮到以后可能還會(huì)根據(jù)是否搜索、是否為通用屬性等條件過濾浅乔,我們多添加幾個(gè)過濾條件:
@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绢掰,則不會(huì)吧屬性作為查詢條件,因此該方法具備通用性童擎,即可根據(jù)gid查詢滴劲,也可根據(jù)cid查詢。
測(cè)試
刷新頁面測(cè)試:
[圖片上傳失敗...(image-abff9f-1555491859890)]
1.5.SKU信息
Sku屬性是SPU下的每個(gè)商品的不同特征顾复,如圖:
當(dāng)我們填寫一些屬性后班挖,會(huì)在頁面下方生成一個(gè)sku表格,大家可以計(jì)算下會(huì)生成多少個(gè)不同屬性的Sku呢芯砸?
當(dāng)你選擇了上圖中的這些選項(xiàng)時(shí):
- 顏色共2種:夜空黑萧芙,絢麗紅
- 內(nèi)存共2種:4GB给梅,6GB
- 機(jī)身存儲(chǔ)1種:64GB
此時(shí)會(huì)產(chǎn)生多少種SKU呢? 應(yīng)該是 2 * 2 * 1 = 4種双揪,這其實(shí)就是在求笛卡爾積动羽。
我們會(huì)在頁面下方生成一個(gè)sku的表格:
1.7.頁面表單提交
在sku列表的下方,有一個(gè)提交按鈕:
[圖片上傳失敗...(image-3f3a7a-1555491859890)]
并且綁定了點(diǎn)擊事件:
[圖片上傳失敗...(image-8f8986-1555491859890)]
點(diǎn)擊后會(huì)組織數(shù)據(jù)并向后臺(tái)提交:
[圖片上傳失敗...(image-4f782d-1555491859890)]
提交:
[圖片上傳失敗...(image-b00b0a-1555491859890)]
點(diǎn)擊提交渔期,查看控制臺(tái)提交的數(shù)據(jù)格式:
[圖片上傳失敗...(image-619613-1555491859890)]
- 整體是一個(gè)json格式數(shù)據(jù)运吓,包含Spu表所有數(shù)據(jù):
- brandId:品牌id
- cid1、cid2疯趟、cid3:商品分類id
- subTitle:副標(biāo)題
- title:標(biāo)題
- spuDetail:是一個(gè)json對(duì)象拘哨,代表商品詳情表數(shù)據(jù)
- afterService:售后服務(wù)
- description:商品描述
- packingList:包裝列表
- specialSpec:sku規(guī)格屬性模板
- genericSpec:通用規(guī)格參數(shù)
- skus:spu下的所有sku數(shù)組,元素是每個(gè)sku對(duì)象:
- title:標(biāo)題
- images:圖片
- price:價(jià)格
- stock:庫(kù)存
- ownSpec:特有規(guī)格參數(shù)
- indexes:特有規(guī)格參數(shù)的下標(biāo)
1.7.后臺(tái)實(shí)現(xiàn)
實(shí)體類
Spu
@Table(name = "tb_spu")
public class Spu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long brandId;
private Long cid1;// 1級(jí)類目
private Long cid2;// 2級(jí)類目
private Long cid3;// 3級(jí)類目
private String title;// 標(biāo)題
private String subTitle;// 子標(biāo)題
private Boolean saleable;// 是否上架
private Boolean valid;// 是否有效信峻,邏輯刪除用
private Date createTime;// 創(chuàng)建時(shí)間
private Date lastUpdateTime;// 最后修改時(shí)間
}
SpuDetail
@Table(name="tb_spu_detail")
public class SpuDetail {
@Id
private Long spuId;// 對(duì)應(yīng)的SPU的id
private String description;// 商品描述
private String specTemplate;// 商品特殊規(guī)格的名稱及可選值模板
private String specifications;// 商品的全局規(guī)格屬性
private String packingList;// 包裝清單
private String afterService;// 售后服務(wù)
}
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;// 商品特殊規(guī)格的鍵值對(duì)
private String indexes;// 商品特殊規(guī)格的下標(biāo)
private Boolean enable;// 是否有效倦青,邏輯刪除用
private Date createTime;// 創(chuàng)建時(shí)間
private Date lastUpdateTime;// 最后修改時(shí)間
@Transient
private Integer stock;// 庫(kù)存
}
注意:這里保存了一個(gè)庫(kù)存字段,在數(shù)據(jù)庫(kù)中是另外一張表保存的盹舞,方便查詢产镐。
Stock
@Table(name = "tb_stock")
public class Stock {
@Id
private Long skuId;
private Integer seckillStock;// 秒殺可用庫(kù)存
private Integer seckillTotal;// 已秒殺數(shù)量
private Integer stock;// 正常庫(kù)存
}
Controller
四個(gè)問題:
請(qǐng)求方式:POST
請(qǐng)求路徑:/goods
-
請(qǐng)求參數(shù):Spu的json格式的對(duì)象,spu中包含spuDetail和Sku集合踢步。這里我們?cè)撛趺唇邮樟渍耍课覀冎岸x了一個(gè)SpuBo對(duì)象,作為業(yè)務(wù)對(duì)象贾虽。這里也可以用它逃糟,不過需要再擴(kuò)展spuDetail和skus字段:
public class SpuBo extends Spu { @Transient String cname;// 商品分類名稱 @Transient String bname;// 品牌名稱 @Transient SpuDetail spuDetail;// 商品詳情 @Transient List<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注解來接收J(rèn)son請(qǐng)求
Service
這里的邏輯比較復(fù)雜,我們除了要對(duì)SPU新增以外蓬豁,還要對(duì)SpuDetail绰咽、Sku、Stock進(jìn)行保存
@Transactional
public void save(SpuBo spu) {
// 保存spu
spu.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和庫(kù)存信息
saveSkuAndStock(spu.getSkus(), spu.getId());
}
private void saveSkuAndStock(List<Sku> skus, Long spuId) {
for (Sku sku : skus) {
if (!sku.getEnable()) {
continue;
}
// 保存sku
sku.setSpuId(spuId);
// 初始化時(shí)間
sku.setCreateTime(new Date());
sku.setLastUpdateTime(sku.getCreateTime());
this.skuMapper.insert(sku);
// 保存庫(kù)存信息
Stock stock = new Stock();
stock.setSkuId(sku.getId());
stock.setStock(sku.getStock());
this.stockMapper.insert(stock);
}
}
Mapper
都是通用Mapper地粪,略