Web前端之路

SKU组件(React版)

2020-03-16  本文已影响0人  我是皮蛋

SKU组件(React版)

这里的一些逻辑还是需要自己再优化一下的

起因

今天看掘金的时候看到前端SKU算法实现,因为公司也有涉及到SKU的业务,记录一下自己写SKU的一个例子吧,刚好他有提供后端的API接口数据,mock一下干起来,但是在做的时候还是有很多问题的,这里做一下记录

实现效果

Peek 2020-03-16 12-08.gif

mock数据

export const simulatedSku = {
  id: 2,
  title: "林间有风自营针织衫",
  subtitle: "瓜瓜设计,3件包邮",
  category_id: 12,
  root_category_id: 2,
  price: "77.00",
  img: "",
  for_theme_img: "",
  description: null,
  discount_price: "62.00",
  tags: "包邮$热门",
  is_test: true,
  online: true,
  sku_list: [
    {
      id: 2,
      price: 77.76,
      discount_price: null,
      online: true,
      img: "",
      title: "金属灰·七龙珠",
      spu_id: 2,
      category_id: 17,
      root_category_id: 3,
      specs: [
        {
          key_id: 1,
          key: "颜色",
          value_id: 45,
          value: "金属灰"
        },
        {
          key_id: 3,
          key: "图案",
          value_id: 9,
          value: "七龙珠"
        },
        {
          key_id: 4,
          key: "尺码",
          value_id: 14,
          value: "小号 S"
        }
      ],
      code: "2$1-45#3-9#4-14",
      stock: 5
    },
    {
      id: 3,
      price: 66,
      discount_price: 59,
      online: true,
      img: "",
      title: "青芒色·灌篮高手",
      spu_id: 2,
      category_id: 17,
      root_category_id: 3,
      specs: [
        {
          key_id: 1,
          key: "颜色",
          value_id: 42,
          value: "青芒色"
        },
        {
          key_id: 3,
          key: "图案",
          value_id: 10,
          value: "灌篮高手"
        },
        {
          key_id: 4,
          key: "尺码",
          value_id: 15,
          value: "中号 M"
        }
      ],
      code: "2$1-42#3-10#4-15",
      stock: 999
    },
    {
      id: 3,
      price: 66,
      discount_price: 59,
      online: true,
      img: "",
      title: "橘黄色·灌篮高手",
      spu_id: 2,
      category_id: 17,
      root_category_id: 3,
      specs: [
        {
          key_id: 1,
          key: "颜色",
          value_id: 44,
          value: "橘黄色"
        },
        {
          key_id: 3,
          key: "图案",
          value_id: 10,
          value: "灌篮高手"
        },
        {
          key_id: 4,
          key: "尺码",
          value_id: 15,
          value: "中号 M"
        }
      ],
      code: "2$1-42#3-10#4-15",
      stock: 999
    },
    {
      id: 4,
      price: 88,
      discount_price: null,
      online: true,
      img: "",
      title: "青芒色·圣斗士",
      spu_id: 2,
      category_id: 17,
      root_category_id: 3,
      specs: [
        {
          key_id: 1,
          key: "颜色",
          value_id: 42,
          value: "青芒色"
        },
        {
          key_id: 3,
          key: "图案",
          value_id: 11,
          value: "圣斗士"
        },
        {
          key_id: 4,
          key: "尺码",
          value_id: 16,
          value: "大号  L"
        }
      ],
      code: "2$1-42#3-11#4-16",
      stock: 8
    },
    {
      id: 5,
      price: 77,
      discount_price: 59,
      online: true,
      img:
        "http://i1.sleeve.7yue.pro/assets/09f32ac8-1af4-4424-b221-44b10bd0986e.png",
      title: "橘黄色·七龙珠",
      spu_id: 2,
      category_id: 17,
      root_category_id: 3,
      specs: [
        {
          key_id: 1,
          key: "颜色",
          value_id: 44,
          value: "橘黄色"
        },
        {
          key_id: 3,
          key: "图案",
          value_id: 9,
          value: "七龙珠"
        },
        {
          key_id: 4,
          key: "尺码",
          value_id: 14,
          value: "小号 S"
        }
      ],
      code: "2$1-44#3-9#4-14",
      stock: 7
    }
  ],
  spu_img_list: [
    {
      id: 165,
      img:
        "http://i1.sleeve.7yue.pro/assets/5605cd6c-f869-46db-afe6-755b61a0122a.png",
      spu_id: 2
    }
  ],
  spu_detail_img_list: [
    {
      id: 24,
      img: "http://i2.sleeve.7yue.pro/n4.png",
      spu_id: 2,
      index: 1
    }
  ],
  sketch_spec_id: 1,
  default_sku_id: 2
};

简单的封装一个SKUCard和SKUGroup

类似于RadioGroup和Radio,我们先封装一个简单的SKU Group和SKU组件,便于状态的统一管理

export const SkuCard = props => {
  const { value, label, onChange, disabled, activate, style } = props;
  const [innerActive, setInnerActive] = useState(activate ?? false);

  const handleChange = value => () => {
    if (!disabled) {
      onChange?.(value, !innerActive);
      setInnerActive(!innerActive);
    }
  };

  return (
    <div
      className={
        disabled ?? false
          ? "disabled"
          : activate ?? innerActive
          ? "activate"
          : "normal"
      }
      onClick={handleChange(value)}
      style={{ ...(style ?? {}) }}
    >
      {label}
    </div>
  );
};
// 定义了Empty,这个Empty对空的时候进行设置
export const Empty = Symbol("empty");

export const SkuGroup = props => {
  const { value, onChange, skuName } = props;

  const [selected, setSelected] = useState(value);
  const { children } = props;

  const _onChange = (value, activate) => {
    const _value = !activate && selected === value ? Empty : value;
    setSelected(_value);
    onChange?.(_value);
  };

  const renderGroupChild = (child, index) => {
    const { props: childProps } = child;

    return React.cloneElement(child, {
      ...childProps,
      onChange: _onChange,
      activate: childProps.value === selected,
      key: `create-${index}`,
      style: {
        ...(childProps?.style ?? {}),
        marginLeft: index === 0 ? 0 : "20px"
      }
    });
  };

  return (
    <div className="skuGroup">
      {skuName && <div className="labelName">{skuName}</div>}
      {children.map((child, index) => {
        return child?.type === SkuCard ? renderGroupChild(child, index) : child;
      })}
    </div>
  );
};

SKU组件实现的思路分析

选区_059.png
// 代码中的几个关键变量
// skuList: 商品拥有的所有sku组合的型号(SPU中的所有商品类型)
// sku: 需要显示的sku card
// selectSku: radio显示选中值的[1, 2, 3]

// 初始化的时候aviableSku就是所有的商品类目
const _getSku = (aviableSku = []) => {
    const _sku = {};
    const _aviableSku = {};

    // 得到目前可以选择的所有商品的sku
    aviableSku.forEach(item => {
        item.forEach(x => {
            const key = JSON.stringify({ key_id: x.key_id, key: x.key });

            const value = {
                value_id: x.value_id,
                value: x.value,
                disabled: false
            };

            _aviableSku[key]
                ? _aviableSku[key].some(z => z.value_id === x.value_id)
                ? null
            : _aviableSku[key].push(value)
            : (_aviableSku[key] = [value]);
        });
    });

    // 将SKU中所有不满足aviableSku的东西diabled掉
    skuList.forEach(item => {
        // 每个商品
        item.forEach((x, i) => {
            // 商品下的每个sku
            const key = JSON.stringify({ key_id: x.key_id, key: x.key });
            const value = {
                value_id: x.value_id,
                value: x.value,
                disabled: !_aviableSku[key].some(item => item.value_id === x.value_id)
            };

            _sku[key]
                ? _sku[key].some(z => z.value_id === x.value_id)
                ? null
            : _sku[key].push(value)
            : (_sku[key] = [value]);
        });
    });

    setMySku(_sku);
};
useEffect(() => {
    // 利用useRef记录上一次选择sku的状态
    if (prevSku.current) {
        // 找到哪一个SKU的值发生了变化
        const cIndex = findChangeIndex(prevSku.current, selectSku);
        if (cIndex !== -1) {
            const changeValue = selectSku[cIndex];
            let otherCondition = {};

            const keys = Object.keys(sku);
            selectSku.forEach((item, index) => {
                if (
                    changeValue === Empty
                    // 改变值为Empty,说明原来选中,现在取消选中场景
                    ? index !== cIndex && item !== Empty
                    // 说明Item是有限定值的
                    : item !== Empty
                ) {
                    // 将限定值保存在otherCondition中
                    // 记录现在的限定状态
                    const key_id = JSON.parse(keys[index])["key_id"];
                    otherCondition[key_id]?.push(item) ??
                        (otherCondition[key_id] = [item]);
                }
            });

            // 通过限定矩阵的值挑选出满足条件的商品类别
            const aviableSku = skuList.filter(good => {
                const aviableGood = good.map(sku => {
                    const isInOther = otherCondition[sku.key_id];
                    return isInOther !== undefined
                        ? isInOther.includes(sku.value_id)
                    : true;
                });

                return aviableGood.every(item => item);
            });

            _getSku(aviableSku);
        }
    } else {
        _getSku(skuList);
    }

    prevSku.current = selectSku;
}, [selectSku]);
上一篇下一篇

猜你喜欢

热点阅读