前端javascript去实现sku,简单易懂!

2,348 阅读3分钟

项目地址:https://github.com/hackftz/sku-implement/tree/master

前言:最近正好有sku商品的需求,看了一些思想后,自己实现了,如果路过的觉得写得还行的,麻烦点个赞哦,感谢!

注:代码原创,copy请标注来源哦!

效果如下:

tutieshi_640x368_7s.gif

  1. 代码部分
  • jsx文件
import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  // 所有行以及选项
  const rows = [
    ['红色', '黑色'],
    ['皮革', '纺织布'],
    ['中国', '美国', '俄罗斯'],
  ];

  // 所有可卖商品
  const products = [
    ['红色', '皮革', '中国'],
    ['红色', '纺织布', '美国'],
    ['黑色', '纺织布', '中国'],
    ['黑色', '纺织布', '俄罗斯'],
  ];

  // 用户选项
  let initialSelectTypes = [];
  for (let i = 0; i < rows.length; i++) {
    initialSelectTypes[i] = '';
  }

  // 用户已经选择的项
  const [selectTypes, setSelectTypes] = useState(initialSelectTypes);

  // 扁平化所有可选项
  const flatRows = rows.flat();

  // 所有可选项的长度
  const flagRowsLength = flatRows.length;

  // 获得矩阵
  const getMatrix = () => {
    // 创建矩阵 不能使用引用类型去创建矩阵 修改时会修改一列!!
    // const matrix = new Array(flagRowsLength).fill(new Array(flagRowsLength).fill(0));

    // 创建矩阵
    let matrix = [];
    for (let i = 0; i < flagRowsLength; i++) {
      matrix[i] = [];

      for (let j = 0; j < flagRowsLength; j++) {
        matrix[i][j] = 0;
      }
    }

    // 利用可卖商品去填充矩阵
    for (let i = 0; i < products.length; i++) {
      const product = products[i];

      for (let j = 0; j < product.length; j++) {
        // 首位商品名
        const prod = product[j];

        // 横纵方向的首位坐标
        const flatRowsIndexOfProd = flatRows.indexOf(prod);

        // 除去首位商品名 剩余的商品名
        const otherProds = product.filter((item) => item !== prod);

        for (let i = 0; i < otherProds.length; i++) {
          const otherProd = otherProds[i];

          // 这里是将横/纵向命中的交点都填充
          const flatRowsIndexOfOtherProd = flatRows.indexOf(otherProd);
          matrix[flatRowsIndexOfProd][flatRowsIndexOfOtherProd] = 1;
        }
      }
    }

    return matrix;
  };

  const matrix = getMatrix();
  console.table(matrix);

  // 获得可选项的type数组集合
  function getCanSelectTypes(selectTypes, products) {
    let canSelectTypes = [];

    // 初始值
    if (selectTypes.every((item) => item === '')) {
      // 将products扁平化 再过滤重复项
      const flatProducts = products.flat();
      canSelectTypes = [...new Set(flatProducts)];
    } else {
      // 重要!!!!
      // 首先把selectTypes挤挤 有值的往前推移 使用sort
      const forwardSelectTypes = selectTypes.slice().sort((a, b) => {
        if (a && b) {
          return 1;
        }

        if (a && !b) {
          return -1;
        }

        if (!a && b) {
          return 1;
        }

        if (!a && !b) {
          return 0;
        }
      });

      // forwardSelectTypes转成矩阵
      const forwardMatrix = forwardSelectTypes.filter(Boolean).map((item) => {
        const index = flatRows.indexOf(item);
        return matrix[index];
      });

      // 初始值用于累加计算
      let initialCanSelectTypes = [];
      for (let i = 0; i < flatRows.length; i++) {
        initialCanSelectTypes[i] = 1;
      }

      canSelectTypes = forwardMatrix
        .reduce((acc, cur) => {
          for (let i = 0; i < acc.length; i++) {
            acc[i] = acc[i] & cur[i];
          }

          return acc;
        }, initialCanSelectTypes)
        .map((item, index) => {
          if (item === 1) {
            return flatRows[index];
          }
        })
        .filter(Boolean);
    }

    return canSelectTypes;
  }

  // 计算出的可选项
  const canSelectTypes = getCanSelectTypes(selectTypes, products);

  // 点击选项
  const clickType = (index, type) => {
    // let newSelectTypes = selectTypes.slice();
    let newSelectTypes = JSON.parse(JSON.stringify(selectTypes));

    if (newSelectTypes[index] === '') {
      newSelectTypes[index] = type;
    } else {
      if (newSelectTypes[index] === type) {
        // 引用问题 直接移除index元素
        // newSelectTypes[index] === '';
        newSelectTypes.splice(index, 1, '');
      } else {
        newSelectTypes[index] = type;
      }
    }

    console.log('%c newSelectTypes after ⧭', 'color: #d0bfff', newSelectTypes);
    setSelectTypes(newSelectTypes);
  };

  return (
    <div className="App">
      {rows.map((types, index) => {
        return (
          <div className="row" key={types}>
            {types.map((type) => {
              // 是否可选
              const isSelect = selectTypes.includes(type);
              // 不用disabled的选项
              const noDisabled = canSelectTypes.includes(type);

              return (
                <button className={'type ' + `${isSelect ? 'select ' : ''}` + `${!isSelect ? (noDisabled ? '' : 'disabled') : ''}`} key={type} onClick={() => clickType(index, type)}>
                  {type}
                </button>
              );
            })}
          </div>
        );
      })}
    </div>
  );
}

export default App;
  • css
.App {
  padding: 40px;
}


.row {
  height: 50px;
  line-height: 50px;

  min-width: 400px;
}

.type {
  padding: 4px 10px;
  display: inline-block;

  margin-right: 15px;
  background-color:  white;
  border: 1px solid gray;;
  color: gray;

  cursor: pointer;
}

.type.select {

  color: red;
  border: 1px solid red;
  box-shadow:none;
}

.type.disabled {
  color: lightgray;
  border: 1px solid lightgray;
  box-shadow:none;
  pointer-events: none;
}
  1. 实现逻辑
  • 我们需要有这么几个变量

    用户已经选择的项——selectTypes
    计算出的可选项——canSelectTypes
    是否可选——isSelect
    不用disabled——noDisabled

  • 我们需要有这么几个常量
    扁平化所有可选项——flatRows
    所有可选项的长度——flagRowsLength

  • 我们需要有这么几个方法
    getMatrix——获得矩阵,用来比较
    获得可选项的type数组集合——getCanSelectTypes

  • 关键点!首先我们要利用初始数据生成矩阵,矩阵是网络结构,可以清晰可以看到哪些选项之间可以关联,形成最终的商品选项。

    这就是我要生成的根据可售卖的商品形成的矩阵。

image.png

  • 细节部分
    - 创建矩阵 不能使用引用类型去创建矩阵 修改时会修改一列!!
    - 横/纵向都要填充完整,虽然最后我们还是可以横向去判断
    - 将products扁平化 再过滤重复项
    - 每次用户点击的选项,会存在空选项,此时需要把selectTypes有值的部分往前挤挤,有值的往前推移,使用sort
    - 累加每个数组,使用对每个数组元素进行位运算
    - 数组引用问题,采用splice移除旧项