JS 中使用笛卡尔积算法实现 SKU
什么是笛卡尔积
笛卡尔积指在数学中,两个集合 X 和 Y 的笛卡尔积,又称之为直积。表示为 X x Y,X 为第一个合集的成员,Y 为第二个合集的所有可能有序对中的一个成员。 假设合集 A=a,b,合集 B=0,1,则两个合集的笛卡尔积为(a,0), (a,1), (b,0), (b,1)。 数据库中左连接或右连接中会使用到笛卡尔积。
什么是 SKU
SKU 的全称是Stock Keeping Units,我们可以理解为是商家用于管理商品库存和销售的一种形式。 每个 SKU 都对应着若干个属性的集合。例如:一个商品存在颜色、尺寸等属性,商家就可以根据不同的属性设置不同的 SKU。
假如我们现在有一个商品,这个商品有红色、蓝色两种颜色,1、2 两种尺寸,儿童票、成人票两种规格,那么我们根据现有的规格,可以得到所有的 SKU 为:
JavaScript
const arr = [
["红色", "1", "儿童票"],
["红色", "1", "成人票"],
["红色", "2", "儿童票"],
["红色", "2", "成人票"],
["蓝色", "1", "儿童票"],
["蓝色", "1", "成人票"],
["蓝色", "2", "儿童票"],
["蓝色", "2", "成人票"],
]
具体算法
首先我们需要构建我们商品的数据,数据结构为下:
JavaScript
const skuTopData=[
{
key:0
attr_name: '颜色',
attr_value:['红色','蓝色']
},
{
key:1,
attr_name: '尺寸',
attr_value:['1cm','8cm']
}
]
基于上面的属性结构,我们可以获得的 SKU 结构为:
JavaScript
const skuBottomData = [
{
key0: 'red',
key1: '1cm',
original_price: '',
price: '',
quantity: '',
sku: ''
},
{
key0: 'red',
key1: '8cm',
original_price: '',
price: '',
quantity: '',
sku: ''
},
{
key0: 'green',
key1: '1cm',
original_price: '',
price: '',
quantity: '',
sku: ''
},
{
key0: 'green',
key1: '8cm',
original_price: '',
price: '',
quantity: '',
sku: ''
}
]
我们可以使用笛卡尔积来构建我们的 SKU 数据,SKU 的生成是实时的,也就是销售属性的增加或者删除都会引起 SKU 的变化,所以我们需要通过 watch 监听销售属性的变化:
JavaScript
// 构建销售属性data
const skuTopData = ref([])
// 监听销售属性的变化
watch(
() => skuTopData.value,
newV => {
// 只有当属性和值都存在的话再去做处理
// 只添加输入框而没有输入值的话不触发
const data = newV.filter(ee => {
return ee.attr_name && ee.attr_value.length && ee.attr_value.every(ee => ee)
})
if (data.length) {
transformColumn(data)
}
},
{
deep: true
}
)
// 通过属性构建SKU
function transformColumn(data: any) {
// 先把下面列表的列提取出来
const arr = data.map((ee: any, vv: number) => {
return {
key: ee.key,
width: '100px',
align: 'center',
title: ee.attr_name
}
})
// 先构建出来列
skuBottomColumns.value = cloneDeep([...arr, ...initSkuBottomColumns])
// 再处理sku数据
transformSkuData(data)
}
function transformSkuData(data) {
// 获取到所有的属性名 [['red','green'],['1cm','8cm']]
const cartesianData = data.map((ee: any) => ee.attr_value)
if (cartesianData.length === 0) {
addAttr([])
// 一条数据也要展示在列表上
} else if (cartesianData.length === 1) {
// 如果只填了一条数据 [['red']]
const array: any[] = []
cartesianData[0].forEach((ee: any) => {
const obj = {
key0: ee
}
array.push(obj)
})
addAttr(array)
} else {
const res: any[] = cartesianData.reduce((pre: any, cur: any, index: number) => {
const array: any[] = []
pre.forEach((ee: any) => {
cur.forEach((eee: any) => {
if (index === 1) {
const obj = {
key0: ee,
key1: eee,
sku: getSkuNum()
}
array.push(obj)
} else {
const sku = getSkuNum()
const item = JSON.parse(JSON.stringify(ee))
item[`key${index}`] = eee
item.sku = sku
array.push(item)
}
})
})
return array
})
addAttr(res)
}
}
// 添加其他属性 库存 价格 会员价
function addAttr(data: any) {
let res = data.map(ee => {
ee.quantity = ''
Ï ee.price = ''
ee.original_price = ''
ee.sku = getSkuNum() // 生成一个唯一值
return ee
})
skuBottomData.value = res
}
经过上面的计算,我们的销售属性的变化,就会同时计算 SKU 数据的值。
但是当我们修改某一个值的时候,就会引起 SKU 属性的重新计算,而以前填写的数据也全部会被重置掉,所以我们需要在每次重新计算 SKU 数据的时候,获取到上一次的 SKU 数据和新的对比,如果 key 一样则用旧的数据,如果 ky 不一样,则重新赋值。
JavaScript
// 添加其他属性 库存 价格 会员价
function addAttr(data: any) {
let res = data.map((ee, index) => {
// 如果下面的列表不需要动态输入值的话,可以使用watch监听skuBottomData变化对afterData进行赋值,我这个项目可能会动态的赋值,所以在改变skuBottomData之前获取下现在的最新的值 然后经过计算再赋新值
const afterData=cloneDeep(skuBottomData.value)
let old = afterData.find(item => item.key === index)
ee.quantity = old === undefined ? '' : old.quantity
ee.price = old === undefined ? '' : old.price
ee.original_price = old === undefined ? '' : old.original_price
ee.sku = old === undefined ? getSkuNum() : old.sku
ee.key = old === undefined ? index : old.key
return ee
})
skuBottomData.value = res
}
以上代码为现在的业务需求,因为需要动态输入值,所以比较复杂一点,简单点的只需要通过 reduce 里面的双重 forEach 构建好 SKU 数据即可使用。