IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue3中 你不知道的那些事!!! -> 正文阅读

[JavaScript知识库]Vue3中 你不知道的那些事!!!

前言

今天在写项目中的一个小组件,感觉挺不错的,然后分享给大家,如果以后需要使用这个就,看一下这个博客就行

先说一下SKU和SPU的概念:

  • SPU(Standard Product Unit)标准化产品单元。是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
  • SKU(Stock Keeping Unit)库存量单位,即库存进出计量的单位, 可以是以件、盒、托盘等为单位。SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。
    在这里插入图片描述

SPU:代表一种商品,拥有很多相同的属性。
SKU:代表的是改商品可选择的规格的任意的组合,他是库存单位的唯一标识

比如在这个下图中出现的使用场景
在这里插入图片描述

思路

  • 从后端获取到所有带有SKU的组合的列表数据,找到有库存的SKU的列表,下面是有库存的SKU
[蓝色,中国,10cm]
[绿色,中国,20cm]
[蓝色,日本,30cm]
[黑色,日本,30cm]

然后计算SKU集合的笛卡尔集,得到后面的这个值,如下面的代码:

[蓝色,中国,10cm]--->[蓝色,中国,10cm,蓝色#中国,蓝色#10cm,中国#10cm,蓝色#中国#10cm]
[绿色,中国,20cm]--->[绿色,中国,20cm,绿色#中国,绿色#20cm,中国#20cm,绿色#中国#20cm]
[蓝色,日本,30cm]--->[蓝色,日本,30cm,蓝色#日本,蓝色#30cm,日本#30cm,蓝色#日本#30cm]
[黑色,日本,30cm]--->[黑色,日本,30cm,黑色#日本,黑色#30cm,日本#30cm,黑色#日本#30cm]

然后生成一个专用的路径的字典,如下代码

const dic = {
  //如果出现了两次的话,就写两个skuId
  '蓝色':[skuId,skuId], 
  '中国'[skuId],
  蓝色#中国#10cm:[skuId]
  蓝色#中国:[skuId]
}

开始啦

goods/components文件夹中新建一个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>

然后再组件中使用

<!-- 图片的信息 -->
        <div class="spec">
          <GoodsName :goods="detail" />
          <!-- 规格组件 -->
          <GoodsSku />
        </div>
<script>
import GoodsSku from './components/goods-sku'
components:{GoodsSku}
</script>

开始渲染组件

在商品规格组件把:specs="detail.specs"注入进去
代码如下

<div class="spec">
          <GoodsName :goods="detail" />
          <!-- 规格组件 -->
          <GoodsSku :specs="detail.specs" />
        </div>

在子组件中接一下数据

props:{
	specs:{
	type:Array,
	default:()=>[]
	}
}

然后渲染页面,代码如下

<template>
  <div class="goods-sku">
    <dl v-for='(item, index) in specs' :key='index'>
      <dt>{{item.name}}</dt>
      <dd>
        <template v-for='(tag, n) in item.values' :key='n'>
          <img :class='{selected: tag.selected}' v-if='tag.picture' :src="tag.picture" alt="" >
          <span :class='{selected: tag.selected}' v-else >{{tag.name}}</span>
        </template>
      </dd>
    </dl>
  </div>
</template>

绑定按钮点击事件,完成选中和取消选中

  • 当前点击的是选中,取消即可
  • 当前点击的是未选中,先当前规格按钮全部取消,当前按钮选中
    代码如下
<template>
  <div class="goods-sku">
    <dl v-for='(item, i) in specs' :key='i'>
      <dt>{{item.name}}</dt>
      <dd>
        <template v-for='(tag, n) in item.values' :key='n'>
          <img :class='{selected: tag.selected}' v-if='tag.picture' :src="tag.picture" alt="" @click='toggle(tag, item.values)'>
          <span :class='{selected: tag.selected}' v-else @click='toggle(tag, item.values)'>{{tag.name}}</span>
        </template>
      </dd>
    </dl>
  </div>
</template>
<script>
export default {
  name: 'GoodsSku',
  props: {
    // 商品的规格参数
    specs: {
      type: Array,
      default: () => []
    }
  },
  setup () {
      // 控制标签的选中和反选(保证仅仅可以选中一个标签)
      const toggle = (tag, list) => {
        // 处理当前点击的标签是否选中
        tag.selected = !tag.selected
        // 处理点击当前标签之外的其他标签的情况
        list.forEach(item => {
          if (item.name !== tag.name) {
            // 其他标签,都编程不选中的状态
            item.selected = false
          }
        })
      }
      return { toggle }
  }
}
</script>

禁用效果的思路分析
在这里插入图片描述
大致步骤:

  1. 根据后台返回的skus数据得到有效(有库存)sku组合在字典中新建一个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;
}
  1. 根据有效的sku组合得到所有的子集集合(笛卡尔集)
  2. 根据子集集合组合成一个路径字典,也就是对象。
import powerSet from '@/vendor/power-set.js'
// 生成路径字典
const usePathMap = (skus) => {
  // 路径字典结果
  const result = {}
  // 规格分隔符
  const spliter = '※'

  skus.forEach(sku => {
    // 过滤掉无效sku数据
    console.log(sku.inventory)
    if (sku.inventory === 0) return
    // 获取规格的集合数据:[蓝色,中国,10cm]
    const spec = sku.specs.map(item => item.valueName)
    // 计算当个sku规格的笛卡尔集
    const specSet = powerSet(spec)
    specSet.forEach(item => {
      // 排除空数组
      if (item.length === 0) return
      // 生成字典的key
      const key = item.join(spliter)
      // 把key添加到字典中
      if (result[key]) {
        // 字典中已经存在当前的key
        result[key].push(sku.id)
      } else {
        // 字典中不存在当前的key
        result[key] = [sku.id]
      }
    })
  })

  return result
}

参照示例
在这里插入图片描述

禁用效果-设置状态

目的:在组件初始化的时候,点击规格的时候,去更新其他按钮的禁用状态。
在这里插入图片描述
代码如下

// 获取选中的所有规格的值
const getSelectedValues = (specs) => {
  // 选中的所有的规格数据
  const result = []
  specs.forEach((item, index) => {
    // 获取规格的选中的信息
    const spec = item.values.find(tag => tag.selected)
    if (spec) {
      // 该规格被选中了
      result[index] = spec.name
    } else {
      // 该规格没有选中
      result[index] = undefined
    }
  })
  return result
}

// 控制规格标签是否被禁用
const updateDisabledStatus = (specs, pathMap) => {
  // seletedValues = [undefined, undefined, undefined]
  specs.forEach((spec, i) => {
    // 每次规格的遍历,选中的值需要重新初始化
    const seletedValues = getSelectedValue(specs)
    spec.values.forEach(tag => {
      if (tag.selected) {
        // 标签本身就是选中状态,不需要处理
        return
      } else {
        // 没有选中(初始化时,需要判断单个规格的禁用状态)
        seletedValues[i] = tag.name
      }
      // 此时,需要判断当前的按钮是否应该被禁用
      // 基于当前选中的值,组合一个路径
      // 过滤掉undefined值,基于剩余的值组合一个路径
      let currentPath = seletedValues.filter(item => item)
      if (currentPath.length > 0) {
        // 拼接路径字符串 currentPath = 黑色★10cm
        currentPath = currentPath.join(spliter)
        // 判断当前的路径是否在路径字典中(如果在字典中没有找到该路径,证明当前的标签应该禁用)
        tag.disabled = !pathMap[currentPath]
      }
      // 单独判断单个按钮是否应该禁用
      // tag.disabled = !pathMap[tag.name]
    })
  })
}
  setup (props) {
    const pathMap = getPathMap(props.goods.skus)
    // 组件初始化的时候更新禁用状态
+    updateDisabledStatus(props.specs, pathMap)
    const clickSpecs = (item, val) => {
      // 如果是禁用状态不作为
+      if (val.disabled) return
      // 1. 选中与取消选中逻辑
      if (val.selected) {
        val.selected = false
      } else {
        item.values.find(bv => { bv.selected = false })
        val.selected = true
      }
      // 点击的时候更新禁用状态
+      updateDisabledStatus(props.specs, pathMap)
    }
    return { clickSpecs }
  }

总结

这个知识也是前端工程师必会的,加油

  • spu代表一种商品,拥有很多相同的属性。
  • sku代表该商品可选规格的任意组合,他是库存单位的唯一标识。
    下面是我的个人理解,好了,今天就到这里了,明天见
    在这里插入图片描述
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-29 10:10:41  更:2021-09-29 10:11:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 13:50:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计