欧美三级国产三级日韩三级_亚洲熟妇丰满大屁股熟妇_欧美亚洲成人一区二区三区_国产精品久久久久久模特

業(yè)務(wù)場(chǎng)景:小程序sku算法(商品多規(guī)格選擇) - 新聞資訊 - 云南小程序開(kāi)發(fā)|云南軟件開(kāi)發(fā)|云南網(wǎng)站建設(shè)-昆明葵宇信息科技有限公司

159-8711-8523

云南網(wǎng)建設(shè)/小程序開(kāi)發(fā)/軟件開(kāi)發(fā)

知識(shí)

不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們?cè)谧非笃湟曈X(jué)表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營(yíng)銷的便利,運(yùn)營(yíng)的高效,讓網(wǎng)站成為營(yíng)銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!

您當(dāng)前位置>首頁(yè) » 新聞資訊 » 小程序相關(guān) >

業(yè)務(wù)場(chǎng)景:小程序sku算法(商品多規(guī)格選擇)

發(fā)表時(shí)間:2021-1-8

發(fā)布人:葵宇科技

瀏覽次數(shù):106

話不多說(shuō),先上圖,就知道要做什么了

在掘金上或者各種網(wǎng)址上有很多關(guān)于“商品多規(guī)格選擇”的問(wèn)題,例如有全排列組合的,我寫該需求的時(shí)候,也在網(wǎng)上找了很多解答,思路都非常棒,但是并沒(méi)有緊貼業(yè)務(wù)場(chǎng)景。(有些緊貼業(yè)務(wù)場(chǎng)景,但是不符合我所需要的業(yè)務(wù)場(chǎng)景)。

真正的業(yè)務(wù)場(chǎng)景是,首次渲染,就需要把所有支持的規(guī)格都呈現(xiàn)出來(lái),不能選擇的規(guī)格置灰,我們要根據(jù)用戶每一次選擇的規(guī)格,找出剩下可選的規(guī)格和不可選的規(guī)格,就是如下效果:

什么是sku

經(jīng)常會(huì)聽(tīng)到一個(gè)詞“SKU”,“SKU”是什么呢?通俗來(lái)說(shuō)就是身份證,產(chǎn)品的身份證。我們每一個(gè)人,都有一個(gè)身份證號(hào)碼,同樣的,每一個(gè)產(chǎn)品在電商系統(tǒng)中也有一個(gè)身份證號(hào)碼,那就是SKU。 英文全稱為 stock keeping unit, 簡(jiǎn)稱SKU,定義為保存庫(kù)存控制的最小可用單位,例如紡織品中一個(gè)SKU通常表示規(guī)格,顏色,款式)。 STOCK KEEP UNIT.這是客戶拿到商品放到倉(cāng)庫(kù)后給商品編號(hào),歸類的一種方法

SKU中包含的信息

1. 品項(xiàng)

可以結(jié)合上面關(guān)于單品、SKU和品種的解釋來(lái)理解。也就是只要屬性有不同,那么就是不同的品項(xiàng)(SKU)??梢哉f(shuō)這是SKU看作是一種產(chǎn)品的角度來(lái)分析理解的。屬性有很多種,也就是說(shuō)同樣的產(chǎn)品只要在人們對(duì)其進(jìn)行保存、管理、銷售、服務(wù)上有不同的方式,那么它(SKU)就不再是相同的了。

2. 編碼

這個(gè)概念是基于信息系統(tǒng)和貨物編碼管理來(lái)說(shuō)的,像“品項(xiàng)”中介紹的那樣,不同的品項(xiàng)(SKU)就有不同的編碼。這樣子,我們才可以依照不同的SKU數(shù)據(jù)來(lái)分析庫(kù)存、銷售狀況。但是這里的產(chǎn)品如“品項(xiàng)”所說(shuō),并非是一個(gè)泛泛的產(chǎn)品的概念,而是很精確的產(chǎn)品概念。

3.單位單位

基本上就是基于管理來(lái)說(shuō)的吧,這個(gè)名字上是數(shù)字化管理方式的產(chǎn)物。但是這里的單位和我們平時(shí)的“單位”有什么區(qū)別呢?看看產(chǎn)品的包裝單位的不同,SKU就不同——你就知道了。

產(chǎn)品SKU在電商倉(cāng)庫(kù)中的作用

1.從貨品角度看

SKU是指單獨(dú)一種商品,其貨品屬性已經(jīng)被確定。也就是說(shuō)同樣的貨品只要在人們對(duì)其進(jìn)行保存、管理、銷售、服務(wù)上有不同的方式,那么就需要被定義為不同的SKU

2.從業(yè)務(wù)管理的角度看

SKU還含有貨品包裝單位的信息。例如:SKU#123是指330ml瓶裝黑啤(以瓶為單位);SKU#456 是指330ml瓶裝黑啤(以提為單位,6瓶為1提);SKU#789 是指330ml瓶裝黑?。ㄒ韵錇閱挝?,24瓶為1箱)。由于計(jì)量單位(包裝單位)不同,為業(yè)務(wù)管理需要,應(yīng)劃歸于不同的SKU,當(dāng)然可以有單位轉(zhuǎn)換的算法協(xié)助轉(zhuǎn)換SKU。

3.從信息系統(tǒng)和貨物編碼角度看

SKU只是一個(gè)編碼。不同的一種商品(商品名稱)就有不同的編碼(SKU#)。而這個(gè)編碼與被定義的商品做了一一對(duì)應(yīng)的關(guān)聯(lián),這樣我們才可以依照不同SKU的數(shù)據(jù)來(lái)記錄和分析庫(kù)存和銷售情況。

一般講SKU是在某一體系(例如:公司或工廠)內(nèi)部自定義和使用的??珞w系需要重新定義或做SKU轉(zhuǎn)換。

業(yè)務(wù)場(chǎng)景

筆者沒(méi)接觸過(guò)商品多規(guī)格業(yè)務(wù),以及自己對(duì)于小程序這方面,接觸的也不多,代碼質(zhì)量不足,不要介意。

使用技術(shù)棧

主要是針對(duì) taro+react+hook 版本的小程序,由于以前的開(kāi)發(fā)都是用js+class,所以hook用的比較少(雖然自己也很想用hook)

下面重點(diǎn)解析sku選擇器

主要代碼

1.sku的格式
spu的所有sku規(guī)格形式 (spu_spec_all_values )

表明一共有兩個(gè)規(guī)格,name為該規(guī)格的名字,values是該規(guī)格下的種類

全部目前已上架的sku商品規(guī)格 (sku_setting_specs )

進(jìn)入商品詳情頁(yè)默認(rèn)選中的sku規(guī)格 (specification )

2.代碼

我這里主要呈現(xiàn)payModal 的核心代碼,商品詳情頁(yè)的代碼就不呈現(xiàn)了

主要思路: 進(jìn)來(lái)商品詳情頁(yè)面默認(rèn)選中規(guī)格(如果沒(méi)有該sku規(guī)格,或者sku的庫(kù)存為0,則置灰) -> 切換可點(diǎn)擊的不同規(guī)格 -> 獲取到選擇完的sku信息,加入購(gòu)物車或者立即購(gòu)買,或者查看該sku的詳情

因?yàn)樾〕绦蛴昧薽obx,class添加了observer()包裹,所以獲取的接口數(shù)據(jù)為object或者array 都需要用mobx內(nèi)置toJS方法解析出來(lái)

  • 核心代碼
  
  const [skuInfo, setSkuInfo] = useState({});// 存儲(chǔ)sku規(guī)格的信息
  const [skuHold, setSkuHold] = useState(0); // sku的庫(kù)存
  
  useEffect(() => {
    if (toJS(specification) && toJS(specification).length) {
      setActiveSizeValue(formatSpecification(toJS(specification)));
      setDrawOptions();
    }
    setSkuHold(stock);
    setSkuInfo({
      skuStock: stock, // 庫(kù)存
      skuImage: sku_img, // sku圖片
      skuOriPrice: ori_price, // 舊價(jià)格
      skuIsShowLinePrice: is_show_line_price, // 是否展示劃線
      skuShowPrice: show_price // 現(xiàn)在sku的價(jià)格
    });

  }, [spu_spec_all_values, specification, sku_setting_specs, ori_price, is_show_line_price])


/**
  * 核心代碼
  * @param selectedSpec 已選中的數(shù)組
  * @param currentSpecName 當(dāng)前點(diǎn)擊的規(guī)格的名稱
  * @param value 默認(rèn)已選的規(guī)格(只會(huì)剛進(jìn)來(lái)的時(shí)候有值)
  * @param typeClick 是否點(diǎn)擊選擇了
  */
  const skuCore = (selectedSpec, currentSpecName, value, typeClick) => {
    const spec1 = typeClick !== 'click' && value ? value : spec;
    const skus = toJS(sku_setting_specs);
    Object.keys(spec1).forEach((sk) => {
      if (sk !== currentSpecName) {
        // 找出該規(guī)格中選中的值
        const currentSpecSelectedValue = http://www.wxapp-union.com/spec1[Object.keys(spec1).find((_sk) => sk === _sk) ||''].find((sv) => sv.select)
        spec1[sk].forEach((sv) => {
          // 判斷當(dāng)前的規(guī)格的值是否是選中的,如果是選中的 就不要判斷是否可以點(diǎn)擊直接跳過(guò)循環(huán)
          if (!sv.select) {
            const _ssTemp = [...selectedSpec]
            // 如果當(dāng)前規(guī)格有選中的值
            if (!!currentSpecSelectedValue) {
              const sIndex = _ssTemp.findIndex((_sv) => _sv === `${sk}:${currentSpecSelectedValue.value}`)
              _ssTemp.splice(sIndex, 1)
            }
            _ssTemp.push(`${sk}:${sv.value}`)
            const _tmpPath = []
            // 找到包含該路徑的全部sku
            skus.forEach((sku) => {
              // 找出skus里面包含目前所選中的規(guī)格的路徑的數(shù)組的數(shù)量
              const querSkus = _ssTemp.filter((_sst) => {
                const querySpec = objTransformArr(sku.specs).some(p => {
                  return p === _sst
                })
                return querySpec
              })
              const i = querSkus.length
              if (i === _ssTemp.length) {
                _tmpPath.push(sku) // 把包含該路徑的sku全部放到一個(gè)數(shù)組里
              }
            })
            const hasHoldPath = _tmpPath.find((p) => p.stock) // 判斷里面是要有個(gè)sku不為0 則可點(diǎn)擊
            let isNotEmpty = hasHoldPath ? hasHoldPath.stock : 0
            sv.disable = !isNotEmpty
          }
        })
      }
    })

    // 判斷是否可以添加進(jìn)購(gòu)物車,比如屬性是否有選,庫(kù)存情況等
    if (judgeCanAdd(skus)) {
      const sku_info = getSkuInfoByKey(spec1) || {}; // 獲取sku信息
      const hold = sku_info.stock || 0; // 獲取sku的庫(kù)存
      setSkuHold(hold);
      setSkuInfo({
        skuStock: sku_info.stock,
        skuImage: sku_info.image,
        skuOriPrice: sku_info.ori_price,
        skuIsShowLinePrice: sku_info.is_show_line_price,
        skuShowPrice: sku_info.show_price
      });
    }
  }
復(fù)制代碼
  • 通過(guò)skus初始化 各個(gè)規(guī)格

// 初始化已選的規(guī)格 格式 ["尺寸:S", "毫升:100ml"]
const formatSpecification = (specification) => {
  if (!Array.isArray(specification)) {
    return []
  }
  return specification.map(i =>
    `${i.key}:${i.val}`
  )
}

// activeSizeValue 格式 ["尺寸:S", "毫升:100ml"]
const [activeSizeValue, setActiveSizeValue] = useState([]);

// spec 格式 {尺寸:{value: "S", disable: false, select: true}}
const [spec, setSpec] = useState({});


 useEffect(() => {
 	// 如果存在多規(guī)格 則執(zhí)行skus初始化 各個(gè)規(guī)格
    if (toJS(specification) && toJS(specification).length) {
      // 把默認(rèn)已選的規(guī)格初始化給 activeSizeValue
      setActiveSizeValue(formatSpecification(toJS(specification)));
      setDrawOptions();
    }
  }, [specification])

/**
   * 通過(guò)skus初始化 各個(gè)規(guī)格
   */
  const setDrawOptions = () => {
  	// spu的所有sku規(guī)格形式
    const skus = toJS(spu_spec_all_values) || [];
    let _tags = {};//臨時(shí)存儲(chǔ)
    const _tempTagsStrArray = {}; //臨時(shí)存儲(chǔ)
    const defaultValue = http://www.wxapp-union.com/toJS(specification) || []; // 默認(rèn)規(guī)格
    // 所有的規(guī)格name 
    const nameKeys = defaultValue.map(item => item.key);
    
    skus.forEach(s => {
      s.values.forEach(p => {
      	// 不存在該key時(shí),初始化對(duì)象
        if (!_tags[s.name]) {
          _tags[s.name] = []
          _tempTagsStrArray[s.name] = [];
        }
        // 
        if (!_tempTagsStrArray[s.name].includes(p)) {
          _tempTagsStrArray[s.name].push(p);
          // 為了判斷規(guī)格 是否已選
          const result = defaultValue.some(function (item) {
            if (item.val === p && item.key === s.name) {
              return true;
            }
          })
          _tags[s.name].push({
            value: p,
            disable: true,
            select: result ? true : false
          })
        }
      })
    });
    
    setSpecDisable(_tags)
  }
復(fù)制代碼
  • 設(shè)置規(guī)格是否可選
// 為了每次第一次渲染都渲染spu下的默認(rèn)sku規(guī)格
const [num, setNum] = useState(0)


  /** 
  * 用于初始化規(guī)格和規(guī)格都沒(méi)選中的時(shí)候 設(shè)置 規(guī)格是否可以點(diǎn)擊,
  * 該路徑上如果跟該屬性的組合沒(méi)有則該屬性不能點(diǎn)擊 
  */
  const setSpecDisable = (tags, isReset) => {
    const skus = toJS(sku_setting_specs) || [];
    const defaultValue = http://www.wxapp-union.com/toJS(specification) || [];
    
    Object.keys(tags).forEach((sk) => {
      tags[sk].forEach((sv) => {
        const currentSpec = `${sk}:${sv.value}`;
        // 找到含有該規(guī)格的路徑下 庫(kù)存不為0的 sku
        const querySku = skus.find((sku) => {
          for (let key in sku.specs) {
            const queryProperty = `${key}:${sku.specs[key]}` === currentSpec;
            if (queryProperty) {
              return queryProperty && sku.stock
            }
          }
        })
        // 如果找到 對(duì)應(yīng)該屬性的路徑 sku有不為0 的則可選
        sv.disable = !querySku
      })
    })

    setSpec({ ...tags })

    // activeSizeValue不為空
    if (!isReset) {
      // 這是因?yàn)閙obx的問(wèn)題,導(dǎo)致activeSizeValue初始化有問(wèn)題,臨時(shí)存儲(chǔ),后期優(yōu)化
      const a = formatSpecification(defaultValue);
      // 第一次進(jìn)來(lái)渲染接口的數(shù)據(jù),以后都是點(diǎn)擊的數(shù)據(jù)
      // num > (defaultValue.length - 1) 默認(rèn)規(guī)格的長(zhǎng)度
      const b = activeSizeValue.length || num > (defaultValue.length - 1) ? activeSizeValue : a;
      defaultValue.forEach(item => {
        if (b.length) {
          skuCore(b, item.key, tags);
          setNum(num + 1)
        }
      })
    }
  }
復(fù)制代碼
  • 規(guī)格點(diǎn)擊事件
/** 
* k - 規(guī)格名稱,如:尺寸
* currentSpectValue - 規(guī)格value 如:'s'
* 規(guī)格選項(xiàng)點(diǎn)擊事件 
*/
  const onPressSpecOption = (k, currentSpectValue) => {
    let isCancel = false;
    // 找到在全部屬性spec中對(duì)應(yīng)的屬性
    const currentSpects = spec[Object.keys(spec).find((sk) => sk === k) || ''] || [];

    // 上一個(gè)被選中的的屬性
    const prevSelectedSpectValue = http://www.wxapp-union.com/currentSpects.find((cspec) => cspec.select) || {}

    // 設(shè)置前一個(gè)被選中的值為未選中
    prevSelectedSpectValue.select = false;

    // 只有當(dāng)當(dāng)前點(diǎn)擊的屬性值不等于上一個(gè)點(diǎn)擊的屬性值時(shí)候設(shè)置為選中狀態(tài)
    if (prevSelectedSpectValue === currentSpectValue) {
      isCancel = true
    } else {
      // 設(shè)置當(dāng)前點(diǎn)擊的狀態(tài)為選中
      currentSpectValue.select = true
    }

    // 全部有選中的規(guī)格數(shù)組 ##可優(yōu)化
    const selectedSpec = Object.keys(spec)
      .filter((sk) => spec[sk].find((sv) => sv.select))
      .reduce((prev, currentSpecKey) => {
        return [...prev, `${currentSpecKey}:${spec[currentSpecKey].find((__v) => __v.select).value}`]
      }, [])

    if (isCancel) {
      // 如果是取消且全部沒(méi)選中
      if (!selectedSpec.length) {
        // 初始化是否可點(diǎn)
        setSpecDisable(spec,'reset')
      }
    }
    // 如果規(guī)格中有選中的 則對(duì)整個(gè)規(guī)格就行 庫(kù)存判斷 是否可點(diǎn)
    if (selectedSpec.length) {
      skuCore(selectedSpec, k, '', 'click');
    }

	// 把選中的規(guī)格賦值給activeSizeValue
    setActiveSizeValue(selectedSpec);

    setSpec({ ...spec });
  }

復(fù)制代碼
  • 判斷是否可以購(gòu)買或加入購(gòu)物車
// 判斷是否可以加入購(gòu)物車
const [canFlag, setCanFlag] = useState(false);

/** 判斷是否可以購(gòu)買或加入購(gòu)物車,比如屬性是否有選,庫(kù)存情況等 */

  const judgeCanAdd = (skus = []) => {
    const sks = Object.keys(spec);
    // 已經(jīng)選擇的規(guī)格個(gè)數(shù)
    let s = sks.filter((sk) => spec[sk].some((sv) => sv.select)).length 
    // 比較已選的長(zhǎng)度是否和需要選擇的長(zhǎng)度一致
    let _cf = s === sks.length
    if (!skus || !skus.length) {
      _cf = false
    }
    if (skus && skus.length === 1 && !Object.keys(skus[0].specs).length && skus[0].stock <= 0) {
      _cf = false
    }
    setCanFlag(_cf)
    return _cf
  }

復(fù)制代碼
  • 返回規(guī)格信息
/**
  * @param _spec 規(guī)格屬性
  * 返回所有信息
  */
  const getSkuInfoByKey = (_spec) => {
    // 已選的規(guī)格:[{ name:規(guī)格名稱, value:已選規(guī)格內(nèi)容 }]
    const selectedSpec = {};

    Object.keys(_spec).forEach((k) => {
      const selectedValue = http://www.wxapp-union.com/_spec[k].find((sv) => sv.select);
      if (selectedValue) {
        // 這塊部分也可以在選擇的時(shí)候直接處理
        selectedSpec[k] = selectedValue.value
      }
    })


    const skus = toJS(sku_setting_specs) || [];
    const querySku = skus.find((sku) => {

      // 對(duì)比兩個(gè)數(shù)組找到 兩個(gè)都不存在的sku 如果為0 則說(shuō)明完全匹配就是該sku
      const diffSkus = isObjShallowEqual(selectedSpec, sku.specs)
      return diffSkus
    }) || {};

    return querySku;
  }
復(fù)制代碼

總結(jié)

因?yàn)榇a一直修修改改,所有有很多冗余和質(zhì)量不好的代碼,后期會(huì)優(yōu)化,敬請(qǐng)諒解(因?yàn)闃I(yè)務(wù)需求,時(shí)間太趕了,先湊合著把功能實(shí)現(xiàn),后期再進(jìn)去優(yōu)化)。

如果你覺(jué)得這篇文章對(duì)你有用的話,請(qǐng)給個(gè)贊吧??!

也可以掃碼進(jìn)入小程序查看,掃掃下面的二維碼