一、概念及需求

  • SPU(Standard Product Unit):标准化产品单元。是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。

  • SKU(Stock Keeping Unit)库存量单位,即库存进出计量的单位, 可以是以件、盒、托盘等为单位。SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。

区别:

SPU:属性值 特性相同的就是SPU  例如 iphone 12pro就是一个SPU

SKU:代表该商品可选规格的任意组合,他是库存单位的唯一标识 例如挑选手机时的 规格、容量、颜色  组合起来就是SKU  iphone12pro 紫色 +256

需求:在平常买东西时可能当前商品的SKU规格库存没有 例如下面左图中 20cm的锅没有库存,在看右图中点击中国后30cm的尺寸也没有库存,怎么判断有没有库存呢? 这就需要每点击一个规格属性 都要去判断其他有没有库存

二、实现

(1)创建组件

goods-sku.vue

<template><div class="goods-sku"><dl><dt>颜色</dt><dd><img class="selected" src="https://yanxuan-item.nosdn.127.net/d77c1f9347d06565a05e606bd4f949e0.png" alt=""><img class="disabled" src="https://yanxuan-item.nosdn.127.net/d77c1f9347d06565a05e606bd4f949e0.png" alt=""></dd></dl><dl><dt>尺寸</dt><dd><span class="disabled">10英寸</span><span class="selected">20英寸</span><span>30英寸</span></dd></dl><dl><dt>版本</dt><dd><span>美版</span><span>港版</span></dd></dl></div>
</template>
<script>
export default {name: 'GoodsSku'
}
</script>
<style scoped lang="less">
.sku-state-mixin () {border: 1px solid #e4e4e4;margin-right: 10px;cursor: pointer;&.selected {border-color: @xtxColor;}&.disabled {opacity: 0.6;border-style: dashed;cursor: not-allowed;}
}
.goods-sku {padding-left: 10px;padding-top: 20px;dl {display: flex;padding-bottom: 20px;align-items: center;dt {width: 50px;color: #999;}dd {flex: 1;color: #666;> img {width: 50px;height: 50px;.sku-state-mixin ();}> span {display: inline-block;height: 30px;line-height: 28px;padding: 0 20px;.sku-state-mixin ();}}}
}
</style>

注册组件

index.vue

<div v-if="goodsData.id">  //当有数据的时候再去执行
<GoodsSku />
</div>
+ import GoodsSku from './components/goods-sku'name: 'XtxGoodsPage',
+  components: { GoodsRelevant, GoodsImage, GoodsSales, GoodsName, GoodsSku },setup () {

目前效果

(2)规格数据渲染

goosData 是通过商品的id获取的  把他传给子组件GoodsSku

<GoodsSku :goodsData="goodsData"/>

返回的数据  goodsData

子组件接收

<script>
export default {name: 'GoodsSku',props: {goodsData: {type: Object,default: () => ({ specs: [], skus: [] })// 防止第一次打开页面没数据报错}}
}
</script>

数据渲染

<template><div class="goods-sku"><dl v-for="item in goodsData.specs" :key="item.id"><dt> {{item.name}} </dt><dd><template v-for="val in item.values" :key="val.name"><img v-if="val.picture" class="selected" :src="val.picture" alt="val.picture" :title="val.picture"><span v-else> {{val.name}} </span></template></dd></dl></div>
</template>

目前效果

(3)切换逻辑

点击时:

它是已选中:则从已选中-->改成-->未选中

它是未选中:则把它的兄弟改成未选中,把它自己改成选中

 <template v-for="val in item.values"  :key="val.name"><img @click="clickSpecs(item,val)" v-if="val.picture" :class="{selected:val.selected}" :src="val.picture" alt="val.picture" :title="val.picture"><span @click="clickSpecs(item,val)" :class="{selected:val.selected}" v-else> {{val.name}} </span></template>

把当前项和 所有项传下去   进行排他思想

setup () {// 选中排他  item所有项  val当前项const clickSpecs = (item, val) => {// 如果当前选中if (val.selected) {// 就把当前取消选中val.selected = false} else {//  否则把每一项都取消选中item.values.forEach(item => {item.selected = false})// 把自己选中val.selected = true}}return { clickSpecs }}

(4)禁用效果

通过skus中的数据,可以来计算当前情况下,某个规格是否可选。

  1. 在组件初始化的时候去判断每个规格是否点击

  2. 在选中某个规格值之后,去判断其他规格值是否可选

当SKU规格库存为0的时候应该 禁用此规格的点击

 (5)禁用效果 采用方式 路径字典

根据当前商品的skus数据生成路径查询字典对象,方便控制后期判断属性是否可用。

计算集合的子集的方法

src/vender/power-set.js

/*** Find power-set of a set using BITWISE approach.** @param {*[]} originalSet* @return {*[][]}*/
export default function bwPowerSet(originalSet) {const subSets = [];// We will have 2^n possible combinations (where n is a length of original set).// It is because for every element of original set we will decide whether to include// it or not (2 options for each set element).const numberOfCombinations = 2 ** originalSet.length;// Each number in binary representation in a range from 0 to 2^n does exactly what we need:// it shows by its bits (0 or 1) whether to include related element from the set or not.// For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to// include only "2" to the current set.for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {const subSet = [];for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {// Decide whether we need to include current element into the subset or not.if (combinationIndex & (1 << setElementIndex)) {subSet.push(originalSet[setElementIndex]);}}// Add current subset to the list of all subsets.subSets.push(subSet);}return subSets;
}

js算法库 https://github.com/trekhleb/javascript-algorithms

幂集算法 https://raw.githubusercontent.com/trekhleb/javascript-algorithms/master/src/algorithms/sets/power-set/bwPowerSet.js

封装方法getPowerSet

goods-sku.vue

// 计算合集子集的方法
import getPowerSet from '@/vender/power-set'
// 定义要分隔的字符串
const spliter = '*'
// 根据sku数据得到的字典对象
const getPathMap = (skus) => {// 用来保存的路径字典const pathMap = {}//   遍历每一个sku规格skus.forEach(sku => {// 1.过滤出有效的sku  因为库存会出有0的情况if (sku.inventory) {// 2. 拼接起来得到属性值数组例如 ['黑色','中国','10cm']const specs = sku.specs.map(spec => spec.valueName)// 3.得到sku的子集 [[] ["蓝色"] ["中国"]["蓝色", "中国"] ["10cm"]...]const powerSet = getPowerSet(specs)// 将子集循环powerSet.forEach(set => {const key = set.join(spliter)if (pathMap[key]) {// 已经有key往数组追加pathMap[key].push(sku.id)} else {// 没有key设置一个数组pathMap[key] = [sku.id]}})}})return pathMap
}setup (props) {// 省略其他...const pathMap = getPathMap(props.goods.skus)console.log(pathMap)}

路径字典代表所有出现的可能性

(6)初始禁用状态

例如路径字典上的没有20cm 那么视图上 20cm的规格应该不能选中

添加 动态添加class类 每一项中的disabled为ture就触发disabled类

<template v-for="val in item.values" :key="val.name"><img v-if="val.picture" :class="{selected:val.selected,disabled:val.disabled}"@click="clickSpecs(item,val)":src="val.picture" :title="val.name"><span v-else:class="{selected:val.selected,disabled:val.disabled}"@click="clickSpecs(item,val)">{{val.name}}</span>
</template>
// 更新按钮的禁用状态
const updateDisabledStatus = (pathMap, specs) => {specs.forEach((spec, i) => {spec.values.forEach(val => {// 当前的状态 = 去路径字典里里找name 如果找到了就为说明存在true 取个反val.disabled = !pathMap[val.name]})})
}
  setup (props) {const pathMap = getPathMap(props.goodsData.skus)
// 组件初始化的更新禁用状态 参数路径地图,初始数据
+    updateDisabledStatus(pathMap,props.goodsData.specs)const clickSpecs = (item, val) => {// 目前加上类还是可以点击 如果是禁用状态 不作为
+      if (val.disabled) return false// 1. 选中与取消选中逻辑if (val.selected) {val.selected = false} else {item.values.forEach(bv => { bv.selected = false })val.selected = true}}return { clickSpecs }}

(7)点击时禁用状态

首先要获取当前用户的选择,然后

对于每一个按钮:

  1. 如果它已经选中了。就忽略。

  2. 如果它没有选中,假设它已经选中,与当前已经选中的条件组合在一起,然后去路径字典中查找,如果找到,就表示它可选,找不到就说明它的disabled为true

补充一个函数 每点击一次 就拿到拼接的数组 getSelectedArr的结果是:[undefined, ‘中国’, undefined]

// 当前选中规格集合  [undefined, "中国", undefined]
const getSelectedArr = (specs) => {return specs.map((spec) => {const selectedVal = spec.values.find((val) => val.selected)// 找到了要里面的name 没有要 undefinedreturn selectedVal ? selectedVal.name : undefined})
}
    const clickSpecs = (item, val) => {if (val.disabled) return false// 如果当前选中if (val.selected) {// 就把当前取消选中val.selected = false} else {//  否则把每一项都取消选中item.values.forEach((item) => {item.selected = false})// 把自己选中val.selected = true}//   每次点击都更新禁用状态
+      updateDisabledStatus(pathMap, props.goodsData.specs)}

更新updateDisabledStatus函数

// 更新按钮的禁用状态
const updateDisabledStatus = (pathMap, specs) => {// 1. 当前用户的选择状态[undefined, "中国", undefined]const _selectedArr = getSelectedArr(specs)console.log(_selectedArr)specs.forEach((spec, i) => {const selectedArr = [..._selectedArr]//   对每一个按钮spec.values.forEach((val) => {// 2. 已经选中的按钮不需要判断if (val.selected) return false// 3. 假设他能选,更新选中之后的条件selectedArr[i] = val.nameconsole.log(selectedArr[i])// 4.过滤掉undefined得到keyconst key = selectedArr.filter(v => v).join(spliter)// 当前的状态 = 去路径字典里里找name 如果找到了就为说明存在true 取个反val.disabled = !pathMap[key]})})
}

(8)父传子-传入默认选中的sku

传入id时 子组件高亮并选中父组件传过来对应的规格

父组件

 <GoodsSku :goodsData="goodsData" skuId="1369155865461919746" ></GoodsSku>

子组件

  props: {skuId: {type: String,default: ''}},

定义initSelectedStatus函数

// 根据skuId还原用户选中的规格
const initSelectedStatus = (goodsData, skuId) => {// 1. 找到选中的具体的规格  sku对象const sku = goodsData.skus.find(sku => sku.id === skuId)// 2. 设置对应的按钮的selected为trueif (sku) {const selectArr = sku.specs.map(it => it.valueName)console.log('找到选中的具体的规格', selectArr, sku)// ["黑色", "中国", "10cm"]goodsData.specs.forEach((spec, idx) => {spec.values.forEach(value => {value.selected = (value.name === selectArr[idx])})})}
}
  setup (props, { emit }) {// 根据传入的skuId默认选中规格按钮
+    initSelectedStatus(props.goods, props.skuId)// 组件初始化的时候更新禁用状态

(9)数据通讯-子传父-传出sku信息

选中完整的规格信息(在规格列表中,所有的规格都选了,没有是undefined的情况)时,向父组件传递有效数据。具体结构如下

{skuId: sku的id,price: sku的价格(注意:每个sku价格可能是不同的) oldPrice: sku的原价格inventory: sku的库存,specsText: 商品的说明,例如:'颜色:黑色 产地:中国 尺度:20cm'//
}

如果选中不完整,也要向父组件传递数据,方便父组件进一步操作。这里约定传{}空对象。

const tryEmit = () => {// 触发change事件将sku数据传递出去const selectedArr = getSelectedArr(props.goodsData.specs).filter(v => v)// 当选中的length 和 一共的lengs相等就调用下面 否则传空对象过去if (selectedArr.length === props.goodsData.specs.length) {const skuIds = pathMap[selectedArr.join(spliter)]const sku = props.goodsData.skus.find(sku => sku.id === skuIds[0])// 传递emit('change', {skuId: sku.id,price: sku.price,oldPrice: sku.oldPrice,inventory: sku.inventory,specsText: sku.specs.reduce((p, n) => `${p} ${n.name}:${n.valueName}`, '').replace(' ', '')})} else {emit('change', {})}}

点击时调用

const clickSpecs = (item, val) => {// ...updateDisabledStatus(pathMap, props.goodsData.specs)// 抛出事件
+ tryEmit()
}

父组件接收

<GoodsSku :goods="goods" @change="skuChange"/>setup () {const goods = useGoods()// sku改变时候触发const skuChange = (sku) => {if (sku.skuId) {goods.value.price = sku.pricegoods.value.oldPrice = sku.oldPricegoods.value.inventory = sku.inventory}}return { goods, changeSku }}

总代码

父组件

  <GoodsSku :goodsData="goodsData" skuId="1369155865461919746"  @change="skuChange" ></GoodsSku>setup () {const goodsData = ref({})findGoods(route.params.id).then((data) => {goodsData.value = data.result})const skuChange = (sku) => {console.log(sku)if (sku.skuId) {goodsData.value.price = sku.pricegoodsData.value.oldPrice = sku.oldPricegoodsData.value.inventory = sku.inventory}}return { goodsData, skuChange }}

sku组件

<template><div class="goods-sku"><dl v-for="item in goodsData.specs" :key="item.id"><dt>{{ item.name }}</dt><dd><template v-for="val in item.values" :key="val.name"><img@click="clickSpecs(item, val)"v-if="val.picture":class="{ selected: val.selected, disabled: val.disabled }":src="val.picture"alt="val.picture":title="val.picture"/><span@click="clickSpecs(item, val)":class="{ selected: val.selected, disabled: val.disabled }"v-else>{{ val.name }}</span></template></dd></dl></div>
</template>
<script>
// 计算合集子集的方法
import getPowerSet from '@/vender/power-set'// 定义要分隔的字符串
const spliter = '*'// 根据sku数据得到的字典对象
const getPathMap = (skus) => {// 用来保存的路径字典const pathMap = {}//   遍历每一个sku规格skus.forEach((sku) => {// 1.过滤出有效的sku  因为库存会出有0的情况if (sku.inventory) {// 2. 拼接起来得到属性值数组例如 ['黑色','中国','10cm']const specs = sku.specs.map((spec) => spec.valueName)// 3.得到sku的子集 [[] ["蓝色"] ["中国"]["蓝色", "中国"] ["10cm"]...]const powerSet = getPowerSet(specs)// 将子集循环powerSet.forEach((set) => {const key = set.join(spliter)if (pathMap[key]) {// 已经有key往数组追加pathMap[key].push(sku.id)} else {// 没有key设置一个数组pathMap[key] = [sku.id]}})}})return pathMap
}// 更新按钮的禁用状态
const updateDisabledStatus = (pathMap, specs) => {// 1. 当前用户的选择状态[undefined, "中国", undefined]const _selectedArr = getSelectedArr(specs)console.log(_selectedArr)specs.forEach((spec, i) => {const selectedArr = [..._selectedArr]//   对每一个按钮spec.values.forEach((val) => {// 2. 已经选中的按钮不需要判断if (val.selected) return false// 3. 假设他能选,更新选中之后的条件selectedArr[i] = val.name// 4.过滤掉undefined得到keyconst key = selectedArr.filter(v => v).join(spliter)// 当前的状态 = 去路径字典里里找name 如果找到了就为说明存在true 取个反val.disabled = !pathMap[key]})})
}// 当前选中规格集合  [undefined, "中国", undefined]
const getSelectedArr = (specs) => {return specs.map((spec) => {const selectedVal = spec.values.find((val) => val.selected)// 找到了要里面的name 没有要 undefinedreturn selectedVal ? selectedVal.name : undefined})
}// 根据id 还原选中规格
const initSelectedStatus = (goodsData, skuId) => {// 1. 找到选中的具体的规格  sku对象const sku = goodsData.skus.find(sku => sku.id === skuId)if (sku) {const selectArr = sku.specs.map(spec => spec.valueName) //  ["黑色", "中国", "10cm"]goodsData.specs.forEach((spec, idx) => {spec.values.forEach(value => {// 给每一项都给当前的状态给value.selectedvalue.selected = (value.name === selectArr[idx])})})}
}
export default {name: 'GoodsSku',props: {goodsData: {type: Object,default: () => ({ specs: [], skus: [] }) // 防止第一次打开页面没数据报错},skuId: {type: String,default: ''}},setup (props, { emit }) {// 选中排他  item所有项  val当前项const clickSpecs = (item, val) => {if (val.disabled) return false// 如果当前选中if (val.selected) {// 就把当前取消选中val.selected = false} else {//  否则把每一项都取消选中item.values.forEach((item) => {item.selected = false})// 把自己选中val.selected = true}//   每次点击都更新禁用状态updateDisabledStatus(pathMap, props.goodsData.specs)// 抛出事件tryEmit()}const tryEmit = () => {// 触发change事件将sku数据传递出去const selectedArr = getSelectedArr(props.goodsData.specs).filter(v => v)if (selectedArr.length === props.goodsData.specs.length) {const skuIds = pathMap[selectedArr.join(spliter)]const sku = props.goodsData.skus.find(sku => sku.id === skuIds[0])// 传递emit('change', {skuId: sku.id,price: sku.price,oldPrice: sku.oldPrice,inventory: sku.inventory,specsText: sku.specs.reduce((p, n) => `${p} ${n.name}:${n.valueName}`, '').replace(' ', '')})} else {emit('change', {})}}// 路径地图const pathMap = getPathMap(props.goodsData.skus)// 组件初始化的更新禁用状态 参数路径地图,初始数据updateDisabledStatus(pathMap, props.goodsData.specs)initSelectedStatus(props.goodsData, props.skuId)return { clickSpecs }}
}
</script>
<style scoped lang="less">
.sku-state-mixin () {border: 1px solid #e4e4e4;margin-right: 10px;cursor: pointer;&.selected {border-color: @xtxColor;}&.disabled {opacity: 0.6;border-style: dashed;cursor: not-allowed;}
}
.goods-sku {padding-left: 10px;padding-top: 20px;dl {display: flex;padding-bottom: 20px;align-items: center;dt {width: 50px;color: #999;}dd {flex: 1;color: #666;> img {width: 50px;height: 50px;.sku-state-mixin ();}> span {display: inline-block;height: 30px;line-height: 28px;padding: 0 20px;.sku-state-mixin ();}}}
}
</style>

vue电商项目sku 规格 详细步骤相关推荐

  1. Vue 电商项目学习

    Vue 电商项目学习 vue_cli脚手架[^1]初始化项目 项目文件夹 项目的其他配置 项目路由的分析 路由组件 非路由组件 使用组件的步骤(非路由组件) 路由组件的搭建 路由的跳转 组件显示与隐藏 ...

  2. Vue电商项目—订单管理—订单列表模块-10

    Vue电商项目-订单管理-订单列表模块-10 1.1 订单管理概述 订单管理模块用于维护商品的订单信息, 可以查看订单的商品信息.物流信息, 并且可以根据实际的运营情况对订单做适当的调整. 1.2 订 ...

  3. Vue电商项目—数据统计—数据报表模块-11

    Vue电商项目-数据统计-数据报表模块-11 1.1 数据统计概述 数据统计模块主要用于统计电商平台运营过程的中的各种统计数据, 并通过直观的可视化方式展示出来, 方便相关运营和管理人员查看. 1.2 ...

  4. SpringBoot+SpringCloud+Mybatis+Vue 电商项目实战,附视频+源码+文档,包含所有主流技术栈。...

    大家好,我是树哥. 今天给大家分享一个电商项目--- 畅购商城.项目采用前后端分离的技术架构. 采用SpringBoot+SpringCloud+Mybatis+Vue为主要技术栈,包括了大型商城的主 ...

  5. SpringBoot+SpringCloud+Mybatis+Vue电商项目实战,附视频+源码+文档,包含所有主流技术栈...

    今天给大家分享一个电商项目--- 畅购商城.项目采用前后端分离的技术架构. 采用SpringBoot+SpringCloud+Mybatis+Vue为主要技术栈,包括了大型商城的主要功能.难点功能以及 ...

  6. 《菜狗商城》Springboot+Vue电商项目

    菜狗商城 一 介绍 菜狗商城 一款Springboot+Vue前后端分离架构的网络电商平台购物系统,包括用户登录,商品推荐,商品搜索,用户评价,购物车,添加订单,收货地址及微信支付等功能. 涉及技术: ...

  7. 电商项目——商品规格管理

    商品规格管理 商品规格数据结构 淘淘商城是一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别.为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU SPU和SKU SPU:S ...

  8. vue电商项目(二)——完成Home页面

    目录 一.项目开发逻辑 二.拆分搭建Home页面组件 1.注册使用三级联动TypeNav组件(全局组件) (1)注册全局组件 (2)使用全局组件 2.完成Home其余静态组件 (1)静态组件文件夹较少 ...

  9. vue电商项目(一)——项目搭建

    目录 一.项目初始化 1.脚手架目录介绍 2.项目的其他配置 (1)项目运行的时候,让浏览器自动打开 (2)关闭语法检查工具 (3)src文件夹的简写形式,配置别名,方便以后访问. 二.项目路由搭建 ...

最新文章

  1. 导出txt文件宏_利用solidwords二次开发导出三维曲面方阵点坐标方法
  2. vscode设置代码编辑时组合键代替方向键移动光标
  3. rhel6上使用udev配置oracle asm,Red Hat Enterprise Linux 6使用udev配置Oracle ASM总结文档
  4. 学习Java需要用到什么软件?
  5. python数组加入新元素_Python之list添加新元素、删除元素、替换元素
  6. Vue项目实战03 : vue中 meta 路由元信息
  7. 各地新闻客户端名称_广西新闻网《“益”起脱贫》节目首播!县长做主播,一天卖出5万多斤圣女果...
  8. 【AC自动机】单词(luogu 3966/ybtoj AC自动机-2)
  9. VS2015开发Android,自带模拟器无法调试、加载程序,算是坑吗
  10. std::string中的find_first_of()和find_last_of()函数
  11. 机器学习和人工智能的初学指南
  12. python爬取数据保存为csv时生成编号_将爬取到到数据以CSV格式存储
  13. 【图像分割】基于matlab改进的细菌觅食算法双阈值图像分割【含Matlab源码 069期】
  14. 网易2018校招内推编程题 小易喜欢的数列
  15. 圆柱螺旋压缩弹簧计算实例
  16. SVN 配置忽略文件
  17. 笔记︱盘点实验科学的三种实验模型(A/B实验、因果推断、强化学习)
  18. echart实现地球外环绕卫星效果
  19. AfxGetThreadState 与 _AFX_THREAD_STATE 剖析
  20. 第 01 章:开篇介绍,我要带你撸 Spring 啦!

热门文章

  1. 计算机网络——物理层(数字传输系统)
  2. 笔记本启动PE出现蓝屏的对策
  3. 使用JCS快速搭建缓存环境
  4. 牛逼的C/C++程序员是如何练成的?
  5. 基于python将txt文件数据导出至excel中
  6. 如何导出Axure原型设计中的图片?零基础入门教程
  7. 表情包 | 猛猪哭泣。
  8. Matlab车辆配送路径规划问题 各类vrp代码 带时间窗的路径规划问题
  9. 信息化,数字化,智能化是三种不同的概念吗?
  10. 8.轨迹预测,乱七八糟的预测方法