微信小程序 SKU設(shè)計(jì)與實(shí)現(xiàn) 前端 數(shù)據(jù)結(jié)構(gòu)分析

預(yù)覽

image

功能說(shuō)明

  • SKU。存貨單位(英語(yǔ):stock keeping unit,SKU),也翻譯為庫(kù)存單元,是一個(gè)會(huì)計(jì)學(xué)名詞,定義為庫(kù)存管理中的最小可用單元。例如紡織品中一個(gè)SKU通常表示規(guī)格、顏色、款式,而在連鎖零售門店中有時(shí)稱單品為一個(gè)SKU。

  • 以IPhone為例,SKU指的是具體規(guī)格單品(銀色 64GB),SKU屬性就是會(huì)影響到庫(kù)存和價(jià)格的屬性,又叫銷售屬性,與商品是多對(duì)一的關(guān)系,比如

    • 顏色:銀色、深空灰
    • 容量:64GB、128GB、 512GB
    • 因此所有屬性規(guī)格的排列組合則會(huì)生成 2 * 3 = 6 個(gè)SKU

需求與細(xì)節(jié)分析

  • 僅有一種(一維,如顏色:白、黑)屬性時(shí),默認(rèn)選中該屬性第一個(gè)值(如白)
  • 載入數(shù)據(jù)后即需判斷所有組合情況是否有庫(kù)存,設(shè)置規(guī)格屬性是否可點(diǎn)擊
  • 每選中或取消選中后,需判斷所有組合情況,設(shè)置規(guī)格屬性是否可點(diǎn)擊
  • 應(yīng)提供是否選中所有維度方法,判斷是否選了所有規(guī)格,用于進(jìn)行購(gòu)買或加入購(gòu)物車時(shí)判斷
  • 僅第一種維度中每個(gè)值可提供不同商品圖片
  • 選中所有維度后更新該規(guī)格屬性對(duì)應(yīng)價(jià)格等商品信息,否則顯示默認(rèn)商品信息

數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)

  • 每種屬性規(guī)格組合應(yīng)有唯一id
  • 每種維度需提供ks值用于標(biāo)明該維度,如顏色為s1、容量為s2依次類推
  • 所有排列組合情況和對(duì)應(yīng)庫(kù)存應(yīng)全部由后端提供
  • 需記錄當(dāng)前選中的規(guī)格屬性,且一開始初始化所有維度,默認(rèn)值為空
    商品
product: {
      id: 0,
      picUrl: "https://img11.360buyimg.com/n1/s450x450_jfs/t1/62813/33/2131/584186/5d079803E03084b0d/2b4970456b7bf49f.png", // 默認(rèn)商品圖片
      promotion: 1, // 促銷活動(dòng) 0無(wú) 1限時(shí)購(gòu) 2領(lǐng)劵
      gallery: [{
        picUrl: "https://img11.360buyimg.com/n1/s450x450_jfs/t1/62813/33/2131/584186/5d079803E03084b0d/2b4970456b7bf49f.png",
        sortOrder: 1
      },
      {
        picUrl: "https://img10.360buyimg.com/n1/s450x450_jfs/t1/4176/23/3653/281477/5b9a15d4E97e09d00/887e76e6c525324c.jpg",
        sortOrder: 2
      }
      ], // 輪播圖
      title: "Apple iPhone XS Max (A2104)",
      description: "A12仿生芯片流暢體驗(yàn),支持雙卡!",
      defaultPrice: 1.00, // 默認(rèn)顯示價(jià)格
      price: 1.00,
      originPrice: 9588.00,
      detail: '<div><img src="https://img14.360buyimg.com/cms/jfs/t1/25195/1/9487/388554/5c7f80a5E8b8f8f0c/46818404849d6ec6.jpg"><img src="https://img12.360buyimg.com/cms/jfs/t1/15853/18/9628/325164/5c7f80a5E7172b236/ba9f3f63a83a9b65.jpg"></div>', // 商品詳情
      tags: [{
        id: 1,
        title: "官方自營(yíng)品牌"
      },
      {
        id: 2,
        title: "新品"
      }
      ],
      serviceList: [{
        id: 1,
        title: "48小時(shí)快速退款",
        desc: "收到退貨包裹并確認(rèn)無(wú)誤后,將在48小時(shí)內(nèi)辦理退款,退款將原路返回,不同銀行處理時(shí)間不同,預(yù)計(jì)1-5個(gè)工作日到賬。"
      },
      {
        id: 2,
        title: "滿88元免郵費(fèi)",
        desc: "單筆訂單金額(不含運(yùn)費(fèi)),大陸地區(qū)滿88元免郵,不滿88元收取10元郵費(fèi);港澳臺(tái)地區(qū)滿500元免郵,不滿500元收取30元運(yùn)費(fèi);海外地區(qū)以下單頁(yè)提示運(yùn)費(fèi)為準(zhǔn)。"
      }
      ],
      comment: {
        goodCommentRate: 100, // 好評(píng)率
        count: 3986, // 評(píng)論計(jì)數(shù)
        goodComment: {
          nickname: "Exrick",
          avatar: "https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJTqQ5hNKicCNEwW3cATfOTaXk6xMlNEfh1gm0kicPDtJrXwTf5YEqQXYea3m5vsuPyJUXc3U0OicXtA/132",
          content: "很好,手機(jī)很有質(zhì)感,值得購(gòu)買。",
          rate: 5,
          time: "2019.06.18",
          spec: "銀色 64G",
          pics: [
            "https://img30.360buyimg.com/shaidan/s616x405_jfs/t1/65005/20/4818/92581/5d2ffdb6Ebcbf3018/35411a583e29d52d.jpg",
            "https://img30.360buyimg.com/shaidan/s616x405_jfs/t1/74460/28/4830/96562/5d2ffdb7Ed5e9ce7a/e764b3daa92a9c67.jpg"
          ]
        } // 精選評(píng)論
      }

SKU

sku: {
      show: false, // 顯示屬性規(guī)格
      noneSku: false, // 有無(wú)規(guī)格選擇
      quota: 100, // 限購(gòu)數(shù)量
      productId: 1, // 商品id
      picUrl: "", // 當(dāng)前選擇圖片
      specText: "", // 所選規(guī)格屬性
      specTextNoCount: "", // 所選規(guī)格屬性 無(wú)數(shù)量
      tree: [
        {
          k: '顏色', // skuKeyName:規(guī)格類目名稱
          v: [
            {
              id: 1, // skuValueId:規(guī)格值 id
              name: '銀色', // skuValueName:規(guī)格值名稱
              picUrl: 'https://img11.360buyimg.com/n1/s450x450_jfs/t1/62813/33/2131/584186/5d079803E03084b0d/2b4970456b7bf49f.png', // 規(guī)格類目圖片,只有第一個(gè)規(guī)格類目可以定義圖片
              selected: false, // 是否選擇
              disabled: false // 禁用
            },
            {
              id: 2,
              name: '深空灰色',
              picUrl: 'https://img14.360buyimg.com/n0/jfs/t1/3/15/4536/138660/5b997bf8Ed72ebce7/819dcf182d743897.jpg',
              selected: false,
              disabled: false
            }
          ],
          ks: 's1' // skuKeyStr:sku 組合列表(下方 list)中當(dāng)前類目對(duì)應(yīng)的 key 值,value 值會(huì)是從屬于當(dāng)前類目的一個(gè)規(guī)格值 id
        },
        {
          k: '內(nèi)存',
          v: [
            {
              id: 3,
              name: '64GB',
              picUrl: '',
              selected: false,
              disabled: false
            },
            {
              id: 4,
              name: '256GB',
              picUrl: '',
              selected: false,
              disabled: false
            },
            {
              id: 5,
              name: '512GB',
              picUrl: '',
              selected: false,
              disabled: false
            }
          ],
          ks: 's2'
        }
      ],
      // 所有 sku 的組合列表,比如紅色、M 碼為一個(gè) sku 組合,紅色、S 碼為另一個(gè)組合
      list: [
        {
          id: 1, // skuId,下單時(shí)后端需要
          price: 1.00, // 價(jià)格
          s1: 1, // 規(guī)格類目 ks 為 s1 的對(duì)應(yīng)規(guī)格值 id
          s2: 3, // 規(guī)格類目 ks 為 s2 的對(duì)應(yīng)規(guī)格值 id
          stockNum: 50 // 當(dāng)前 sku 組合對(duì)應(yīng)的庫(kù)存
        },
        {
          id: 2,
          price: 2.00,
          s1: 1,
          s2: 4,
          stockNum: 100
        },
        {
          id: 3,
          price: 3.00,
          s1: 1,
          s2: 5,
          stockNum: 0
        },
        {
          id: 4,
          price: 4.00,
          s1: 2,
          s2: 3,
          stockNum: 100
        },
        {
          id: 5,
          price: 5.00,
          s1: 2,
          s2: 4,
          stockNum: 100
        },
        {
          id: 6,
          price: 6.00,
          s1: 2,
          s2: 5,
          stockNum: 50
        }
      ],
      // 選擇的 sku 組合
      selectedSku: {
      },
      count: 1 // 選擇的商品數(shù)量
}

關(guān)鍵實(shí)現(xiàn)方法

  • 初始化
    // 加載sku后初始化selectedSku
    let tree = this.data.sku.tree;
    for (let i = 0; i < tree.length; i++) {
      let s = 'sku.selectedSku.' + tree[i].ks;
      this.setData({
        [s]: ''
      })
    }

    // 只有一種 sku 規(guī)格值時(shí)默認(rèn)選中第一個(gè)
    if (tree.length == 1) {
      let k = 'sku.selectedSku.' + this.data.sku.tree[0].ks;
      this.setData({
        [`sku.tree[${0}].v[${0}].selected`]: true,
        [k]: this.data.sku.tree[0].v[0].id
      });
    }
  • 核心判斷所有屬性是否可選
    • 每當(dāng)點(diǎn)擊一個(gè)規(guī)格屬性后,循環(huán)判斷其他所有規(guī)格屬性,模擬依次點(diǎn)擊組合其他所有規(guī)格屬性,與所有排列組合匹配,統(tǒng)計(jì)庫(kù)存總數(shù),若庫(kù)存總數(shù)>0則可選,詳見(jiàn)下方isSkuChoosable方法
  // 循環(huán)判斷所有屬性是否可選
  judgeAllItem: function () {
    // 判斷庫(kù)存
    let tree = this.data.sku.tree;
    for (let i = 0; i < tree.length; i++) {
      let v = tree[i].v;
      for (let j = 0; j < v.length; j++) {
        if (this.isSkuChoosable(tree[i].ks, v[j].id)) {
          // 可點(diǎn)擊
          this.setData({
            [`sku.tree[${i}].v[${j}].disabled`]: false
          })
        } else {
          // 不可點(diǎn)擊
          this.setData({
            [`sku.tree[${i}].v[${j}].disabled`]: true
          })
        }
      }
    }
   ... ...
  }
  • 關(guān)鍵判斷方法
isSkuChoosable: function (ks, vId) {

    let selectedSku = this.data.sku.selectedSku;
    let list = this.data.sku.list;

    // 先假設(shè)sku已選中,拼入已選中sku對(duì)象中
    let matchedSku = Object.assign({}, selectedSku, {
      [ks]: vId
    });

    // 再判斷剩余sku是否全部不可選,若不可選則當(dāng)前sku不可選中
    let skusToCheck = Object.keys(matchedSku).filter(
      skuKey => matchedSku[skuKey] != ''
    );

    let filteredSku = list.filter(sku => (
      skusToCheck.every(
        skuKey => String(matchedSku[skuKey]) == String(sku[skuKey])
      )
    ));

    let stock = filteredSku.reduce((total, sku) => {
      total += sku.stockNum;
      return total;
    }, 0);
    return stock > 0;
}
  • 是否所有維度規(guī)格屬性已選
  // 是否所有規(guī)格已選
  isAllSelected: function () {
    let selectedSku = this.data.sku.selectedSku;
    let selected = Object.keys(selectedSku).filter(
      skuKeyStr => selectedSku[skuKeyStr] != ""
    );
    return this.data.sku.tree.length == selected.length;
  }

源碼獲取

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容