lyshop學習筆記五-商品規格管理


title: 樂優商城學習筆記五-商品規格管理
date: 2019-04-15 13:29:57
tags:
- 樂優商城
- java
- springboot
- FastDFS
categories:
- 樂優商城


0.學習目標

  • 了解商品規格數據結構設計思路
  • 實現商品規格查詢
  • 了解SPU和SKU數據結構設計思路
  • 實現商品查詢
  • 了解商品新增的頁面實現
  • 獨立編寫商品新增后臺功能

1.商品規格數據結構

樂優商城是一個全品類的電商網站,因此商品的種類繁多,每一件商品,其屬性又有差別。為了更準確描述商品及細分差別,抽象出兩個概念:SPU和SKU,了解一下:

1.1.SPU和SKU

SPU:Standard Product Unit (標準產品單位) ,一組具有共同屬性的商品集

SKU:Stock Keeping Unit(庫存量單位),SPU商品集因具體特性不同而細分的每個商品

以圖為例來看:
!](http://upload-images.jianshu.io/upload_images/7149586-6df76897c452dbdd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

  • 本頁的 華為Mate10 就是一個商品集(SPU)
  • 因為顏色、內存等不同,而細分出不同的Mate10,如亮黑色128G版。(SKU)

可以看出:

  • SPU是一個抽象的商品集概念,為了方便后臺的管理。
  • SKU才是具體要銷售的商品,每一個SKU的價格、庫存可能會不一樣,用戶購買的是SKU而不是SPU

1.2.數據庫設計分析

1.2.1.思考并發現問題

弄清楚了SPU和SKU的概念區分,接下來我們一起思考一下該如何設計數據庫表。

首先來看SPU,大家一起思考下SPU應該有哪些字段來描述?

id:主鍵
title:標題
description:描述
specification:規格
packaging_list:包裝
after_service:售后服務
comment:評價
category_id:商品分類
brand_id:品牌

似乎并不復雜,但是大家仔細思考一下,商品的規格字段你如何填寫?

1526086539789

不同商品的規格不一定相同,數據庫中要如何保存?

再看下SKU,大家覺得應該有什么字段?

id:主鍵
spu_id:關聯的spu
price:價格
images:圖片
stock:庫存
顏色?
內存?
硬盤?

碰到難題了,不同的商品分類,可能屬性是不一樣的,比如手機有內存,衣服有尺碼,我們是全品類的電商網站,這些不同的商品的不同屬性,如何設計到一張表中?

1.2.2.分析規格參數

仔細查看每一種商品的規格你會發現:

雖然商品規格千變萬化,但是同一類商品(如手機)的規格是統一的,有圖為證:

華為的規格:

1526087063700

三星的規格:

1526087142454

也就是說,商品的規格參數應該是與分類綁定的。每一個分類都有統一的規格參數模板,但不同商品其參數值可能不同

如下圖所示:

1526088168565

1.2.3.SKU的特有屬性

SPU中會有一些特殊屬性,用來區分不同的SKU,我們稱為SKU特有屬性。如華為META10的顏色、內存屬性。

不同種類的商品,一個手機,一個衣服,其SKU屬性不相同。

同一種類的商品,比如都是衣服,SKU屬性基本是一樣的,都是顏色、尺碼等。

這樣說起來,似乎SKU的特有屬性也是與分類相關的?事實上,仔細觀察你會發現,SKU的特有屬性是商品規格參數的一部分

1526088981953

也就是說,我們沒必要單獨對SKU的特有屬性進行設計,它可以看做是規格參數中的一部分。這樣規格參數中的屬性可以標記成兩部分:

  • 所有sku共享的規格屬性(稱為全局屬性)
  • 每個sku不同的規格屬性(稱為特有屬性)
1526089506566

1.2.4.搜索屬性

打開一個搜索頁,我們來看看過濾的條件:

1526090072535

你會發現,過濾條件中的屏幕尺寸、運行內存、網路、機身內存、電池容量、CPU核數等,在規格參數中都能找到:

1526090228171

也就是說,規格參數中的數據,將來會有一部分作為搜索條件來使用。我們可以在設計時,將這部分屬性標記出來,將來做搜索的時候,作為過濾條件。要注意的是,無論是SPU的全局屬性,還是SKU的特有屬性,都有可能作為搜索過濾條件的,并不沖突,而是有一個交集:

1526091216124

1.3.規格參數表

1.3.1.表結構

先看下規格參數表:

CREATE TABLE `tb_specification` (
  `category_id` bigint(20) NOT NULL COMMENT '規格模板所屬商品分類id',
  `specifications` varchar(3000) NOT NULL DEFAULT '' COMMENT '規格參數模板,json格式',
  PRIMARY KEY (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品規格參數模板,json格式。';

很奇怪是吧,只有兩個字段。特別需要注意的是第二個字段:

  • specificatons:規格參數模板,json格式

為什么是一個json?我們看下規格參數的格式:

1526092179381

如果按照傳統數據庫設計,這里至少需要3張表:

  • group:代表組,與商品分類關聯
  • param_key:屬性名,與組關聯,一對多
  • param_value:屬性備選值,與屬性名關聯,一對多

這樣程序的復雜度大大增加,但是提高了數據的復用性。

我們的解決方案是,采用json來保存整個規格參數模板,不需要額外的表,一個字符串就夠了。

1.3.2.json結構分析

先整體看一下:

1526092693138
  • 因為規格參數分為很多組,所以json最外層是一個數組。
  • 數組中是對象類型,每個對象代表一個組的數據,對象的屬性包括:
    • group:組的名稱
    • params:該組的所有屬性

接下來是params:

1526093111370

主芯片這一組為例:

  • group:注明,這里是主芯片

  • params:該組的所有規格屬性,因為不止一個,所以是一個數組。這里包含四個規格屬性:CPU品牌,CPU型號,CPU頻率,CPU核數。每個規格屬性都是一個對象,包含以下信息:

    • k:屬性名稱
    • searchable:是否作為搜索字段,將來在搜索頁面使用,boolean類型
    • global:是否是SPU全局屬性,boolean類型。true為全局屬性,false為SKU的特有屬性
    • options:屬性值的可選項,數組結構。起約束作用,不允許填寫可選項以外的值,比如CPU核數,有人添10000核豈不是很扯淡
    • numerical:是否為數值,boolean類型,true則為數值,false則不是。為空也代表非數值
    • unit:單位,如:克,毫米。如果是數值類型,那么就需要有單位,否則可以不填。

上面的截圖中所有屬性都是全局屬性,我們來看看內存,應該是特有屬性:

1526262641446

總結下:

  • 規格參數分組,每組有多個參數
  • 參數的 k代表屬性名稱,沒有值,具體的SPU才能確定值
  • 參數會有不同的屬性:是否可搜索,是否是全局、是否是數值,這些都用boolean值進行標記:
    • SPU下的多個SKU共享的參數稱為全局屬性,用global標記
    • SPU下的多個SKU特有的參數稱為特有屬性
    • 如果參數是數值類型,用numerical標記,并且指定單位unit
    • 如果參數可搜索,用searchable標記

2.商品規格參數管理

2.1.1.頁面實現

因為規格是跟商品分類綁定的,因此首先會展現商品分類樹,并且提示你要選擇商品分類,才能看到規格參數的模板。一起了解下頁面的實現:

1526095548672

可以看出頁面分成3個部分:

  • v-card-title:標題部分,這里是提示信息,告訴用戶要先選擇分類,才能看到模板

  • v-tree:這里用到的是我們之前講過的樹組件,展示商品分類樹,不過現在是假數據,我們只要把treeData屬性刪除,它就會走url屬性指定的路徑去查詢真實的商品分類樹了。

    <v-tree url="/item/category/list" :isEdit="false"  @handleClick="handleClick" />
    
  • v-dialog:Vuetify提供的對話框組件,v-model綁定的dialog屬性是boolean類型:

    • true則顯示彈窗
    • false則隱藏彈窗

2.1.2.data中定義的屬性

接下來,看看Vue實例中data定義了哪些屬性,對頁面會產生怎樣的影響:

[圖片上傳失敗...(image-3b8613-1555314257815)]

  • specifications:選中一個商品分類后,需要查詢后臺獲取規格參數信息,保存在這個對象中,Vue會完成頁面渲染。
  • oldSpec:當前頁兼具了規格的增、改、查等功能,這個對象記錄被修改前的規格參數,以防用戶撤銷修改,用來恢復數據。
  • dialog:是否顯示對話框的標記。true則顯示,false則不顯示
  • currentNode:記錄當前選中的商品分類節點
  • isInsert:判斷接下來是新增還是修改

2.2.規格參數的查詢

點擊樹節點后要顯示規格參數,因此查詢功能應該編寫在點擊事件中。

了解一下:

2.2.1.樹節點的點擊事件

當我們點擊樹節點時,要將v-dialog打開,因此必須綁定一個點擊事件:

1526095959539

我們來看下handleClick方法:

handleClick(node) {
    // 判斷點擊的節點是否是父節點(只有點擊到葉子節點才會彈窗)
    if (!node.isParent) {
        // 如果是葉子節點,那么就發起ajax請求,去后臺查詢商品規格數據。
        this.$http.get("/item/spec/" + node.id)
            .then(resp => {
            // 查詢成功后,把響應結果賦值給specifications屬性,Vue會進行自動渲染。
            this.specifications = resp.data;
            // 記錄下此時的規格數據,當頁面撤銷修改時,用來恢復原始數據
            this.oldSpec = resp.data;
            // 打開彈窗
            this.dialog = true;
            // 標記此時要進行修改操作
            this.isInsert = false;
        })
            .catch(() => {
            // 如果沒有查詢成功,那么詢問是否添加規格
            this.$message.confirm('該分類還沒有規格參數,是否添加?')
                .then(() => {
                // 如果要添加,則將specifications初始化為空
                this.specifications = [{
                    group: '',
                    params: []
                }];
                // 打開彈窗
                this.dialog = true;
                // 標記為新增
                this.isInsert = true;
            })
        })
    }
}

2.2.2.后端代碼

entity

@Data
@Table(name = "tb_specification")
public class Specification {

    @Id
    private Long categoryId;
    private String specifications;
}

mapper

public interface SpecificationMapper extends Mapper<Specification> {
}

controller
先分析下需要的東西,在頁面的ajax請求中可以看出:

  • 請求方式:查詢,肯定是get

  • 請求路徑:/spec/{cid} ,這里通過路徑占位符傳遞商品分類的id

  • 請求參數:商品分類id

  • 返回結果:頁面是直接把resp.data賦值給了specifications:

    1526104087329

    那么我們返回的應該是規格參數的字符串

代碼:

@RestController
@RequestMapping("spec")
public class SpecificationController {

    @Autowired
    private SpecificationService specificationService;

    @GetMapping("{id}")
    public ResponseEntity<String> querySpecificationByCategoryId(@PathVariable("id") Long id){
        Specification spec = this.specificationService.queryById(id);
        if (spec == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return ResponseEntity.ok(spec.getSpecifications());
    }
}

service:

@Service
public class SpecificationService {

    @Autowired
    private SpecificationMapper specificationMapper;

    public Specification queryById(Long id) {
        return this.specificationMapper.selectByPrimaryKey(id);
    }
}

頁面訪問測試:

我們訪問:http://api.leyou.com/api/item/spec/76

image

2.3 規格參數添加

2.3.1 前端代碼

      //添加分組
      addGroup() {
        this.specifications.push({
          group: '',
          params: []
        })
      },
      // 添加新模板
      addParam(i) {
        this.specifications[i].params.push({
          k: "",
          searchable: false,
          global: true,
          numerical:false,
          unit:"",
          options: []
        })
      },
      // 添加默認值
      addOption(i, j) {
        this.specifications[i].params[j].options.push("")
      },

重點就是saveTemplate函數了,包含對規格模板的增加、修改和刪除等功能


// 保存、修改、刪除模板
      saveTemplate() {
        this.dialog = true;
 
        //模板刪除
        if (this.specifications.length === 0){
          //console.log("刪除:"+this.currentNode.id);
          this.$http.delete("/item/spec/"+this.currentNode.id).then(() => {
            this.dialog = false;
            this.$message.success("刪除成功!");
            this.oldSpec = [];
          }).catch(() => {
            this.$message.error("刪除失敗");
          });
        }else {
          this.$http({
            method: this.oldSpec.length === 0 ? 'post' : 'put',
            url: '/item/spec',
            data: this.$qs.stringify({
              categoryId: this.currentNode.id,
              specifications: JSON.stringify(this.specifications)
            })
          })
            .then(() => {
              this.dialog = false;
              this.$message.success("保存成功!")
              this.oldSpec = [];
            })
            .catch(() => {
              this.$message.error("保存失敗!")
            });
        }

2.3.2后臺

contoller

    /**
     *添加規格模板
     * @param specification
     * @return
     */
    @PostMapping
    public ResponseEntity<Void> saveSpecification(Specification specification){
        this.specificationService.saveSpecification(specification);
        return ResponseEntity.status(HttpStatus.OK).build();
    }

    /**
     * 更新規格模板
     * @param specification
     * @return
     */
    @PutMapping
    public ResponseEntity<Void> updateSpecification(Specification specification){
        this.specificationService.updateSpecification(specification);
        return ResponseEntity.status(HttpStatus.OK).build();
    }

    /**
     * 刪除規格模板
     * @param id
     * @return
     */
    @DeleteMapping("{id}")
    public ResponseEntity<Void> deleteSpecification(@PathVariable("id")Long id){

        Specification specification = new Specification();
        specification.setCategoryId(id);
        this.specificationService.deleteSpecification(specification);
        return ResponseEntity.status(HttpStatus.OK).build();
    }

mapper

/**
 * @Author smallmartial
 * @Date 2019/4/14
 * @Email smallmarital@qq.com
 */
public interface SpecificationMapper extends Mapper<Specification> {
}

service

 public void saveSpecification(Specification specification) {
        this.specificationMapper.insert(specification);
    }

    public void updateSpecification(Specification specification) {
        /**
         *  updateByPrimaryKeySelective會對字段進行判斷再更新(如果為Null就忽略更新),
         *  如果你只想更新某一字段,可以用這個方法。
         *
         * updateByPrimaryKey對你注入的字段全部更新,
         * 如果為字段不更新,數據庫的值就為null。
         */
        this.specificationMapper.updateByPrimaryKeySelective(specification);
    }

    public void deleteSpecification(Specification specification) {
        this.specificationMapper.deleteByPrimaryKey(specification);
    }

2.4測試

image

image
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • 昨天看到一則新聞:11月8日晚十點在西安文理學院北門,一學生發現小偷偷東西,當場即揭穿他,剛走幾步,就被小偷用刀捅...
    惑然閱讀 1,444評論 0 1
  • 姓名:富智燚 單位:海南蔚藍時代實業有限公司 361期努力一組 【日精進打卡第344天】 【知~學習】 《六項精進...
    復制2閱讀 109評論 0 0
  • 作者:Jim Rutenberg,翻譯:ONES Piece 翻譯計劃 姜雯、塔娜、王紫涵譯者按:前陣子小米發布了...
    ONES_Piece閱讀 814評論 0 12
  • 你會不會也在秋風起時感念時光。 葦叢嚷嚷聲里每次都有風的到來,塘下的水波再也圈不住冒頭的魚,圓形排開涌向遠處的岸,...
    好吧緘默閱讀 273評論 0 0
  • ——《親愛的蘇格拉底》讀后感 后來我知道,撒旦教有九宗罪:愚蠢,自負,唯我,自欺,隨波逐...
    Anna小天后閱讀 716評論 0 2