做电商,绕不开一个叫做 SKU 的概念。SKU 全称为Stock Keeping Unit(库存量单位),在国内有时被称为单品。对一种商品(SPU)而言,当其品牌、型号、配置、等级、花色、包装容量、单位、生产日期、保质期、用途、价格、产地等属性中任一属性与其他商品存在不同时,可称为一个单品(SKU)。基于这样的概念,延伸出许多前后端需求。
在前端代码中,用户进行购买操作时,预测一个商品在不同 SKU 规格下的可选路径,是比较常见的做法。
需求
用户下单时,对商品规格作出选择,有时一个商品会有多种规格组合的属性,比如颜色可选黑、白、灰,尺寸可选大、中、小,型号可选A、B、C,每一种选择组合,对应的 SKU 的价格、库存情况可能有所不同。它们看起来像下图这样一个 3*3 的阵列,用户实际可能选择的组合多达 27 种。
此时,前端要跟据实际情况作一些处理,主要有四个方面:
- 27 个组合中,某些组合可能并没有库存,在用户选中一个规格后,需要将接下来不可选的路径置灰并不可选择;
- 跟据用户已选规格,预测出其他可选规格,将可选规格路径高亮出来;
- 一些电商应用会要求,进入商品页面时,将最优规格组合(如价格最优、性能最优)优先选中;
- 用户每选中一组规格组合(SKU),页面应显示出该 SKU 对应的库存、售价情况。
笛卡尔积
后端 api 接口在开发中,通常只能给出该商品已有库存的 sku ,它们看起来像这样:
const goodsData = [
{
skuId: "3158054",
price: 999,
stock: 99,
sale_attrs: [
{ name: "颜色", value: "黑" },
{ name: "尺码", value: "大" },
{ name: "型号", value: "A" }
]
},
{
skuId: "3158055",
price: 99,
stock: 9,
sale_attrs: [
{ name: "颜色", value: "白" },
{ name: "尺码", value: "中" },
{ name: "型号", value: "B" }
]
},
{
skuId: "3158056",
price: 1000,
stock: 7,
sale_attrs: [
{ name: "颜色", value: "灰" },
{ name: "尺码", value: "小" },
{ name: "型号", value: "C" }
]
}
];
跟据上面的数据,我们通常需要将用户的可选规格 sale_attrs
归类、梳理出来,并作去重处理,得到一个所有备选规格的矩阵:
const attributes = {
颜色: ["黑", "白", "灰"],
尺码: ["大", "中", "小"],
型号: ["A", "B", "C"]
};
此时,如果我们对矩阵中的值穷尽组合可能的 sku ,那么结果将远远大于 api 接口给我们的已有库存商品,这些所有可能的 sku ,是一个笛卡尔积(Cartesian product)。穷举后结果如下:
const resultArr = [
['黑', '大', 'A'],
['黑', '大', 'B'],
['黑', '大', 'C'],
['黑', '中', 'A'],
['黑', '中', 'B'],
['黑', '中', 'C'],
['黑', '小', 'A'],
['黑', '小', 'B'],
['黑', '小', 'C'],
['白', '大', 'A'],
['白', '大', 'B'],
['白', '大', 'C'],
['白', '中', 'A'],
['白', '中', 'B'],
['白', '中', 'C'],
['白', '小', 'A'],
['白', '小', 'B'],
['白', '小', 'C'],
['灰', '大', 'A'],
['灰', '大', 'B'],
['灰', '大', 'C'],
['灰', '中', 'A'],
['灰', '中', 'B'],
['灰', '中', 'C'],
['灰', '小', 'A'],
['灰', '小', 'B'],
['灰', '小', 'C']
]
对于一个 3*3 的阵列来说,结果多达 27 种,当这样的幂运算任何一项有增加时,结果都将大大增加。
核心方法
我们封装一种方法,专门用来推导出所有可能的组合。
function cartesianProductOf() {
return Array.prototype.reduce.call(
arguments,
function (a, b) {
var ret = [];
a.forEach(function (a) {
b.forEach(function (b) {
ret.push(a.concat([b]));
});
});
return ret;
},
[[]]
);
}
此时将计算素材放进去,就能计算出所有组合:
let arr = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]
];
let allArr = cartesianProductOf(...arr);
处理思路和步骤与 Demo
根据前文中的需求,拆分处理步骤,主要是加工和变造数据,计算集合,筛选数据这几步,页面根据筛选出来的数据进行判断和渲染。
由于核心原理已详细阐述,以下仅演示 Demo(基于 Vue3,iframe 嵌入):