业务中的算法

问题背景

电商商品选择常用以下场景:

  • 衣服、手机的选择
  • 奶茶的定制选择

UI 层处理要求

  • 当用户选择某些属性后,怎么计算每个(自身+其他)属性的可选/可替换的项
  • 属性都选中后 → 自动锁定 SKU,显示库存、价格等

你觉得这两种场景是一样的吗?后台能用一个结构返回选项关系吗?如果一个电商平台既要支持衣服、手机等商品、也要支持奶茶等商品,请问应该怎么做?

在解答这些问题前,我们需要先了解他们之前的场景需求。

衣服、手机的场景:可能出现某种尺寸或者型号的商品没有库存或没有生产,从而不能选了。

奶茶的场景:每个选项都可以选择

一、电商 App 的服装选择

1、需求描述

在电商 App(如淘宝、京东、Uniqlo、ZARA)中,用户在商品详情页选择衣服时,常常需要选择:

  • 颜色(color):红色、蓝色、黑色
  • 尺码(size):S、M、L、XL
  • 性别(gender):男款、女款
  • 款式(style):连帽、无帽
  • 厚度(thickness):常规、加厚

但并不是所有组合都可用,比如

  • 有些颜色根本没有男款,eg: 红色没有男款
  • 有些款式只支持 M/L 没有 S,eg:男款没有S
  • 有些组合明明理论上能配,但没生产

🌟 所以,你无法通过规则自动推导哪些选项合法,只能靠后台上架商品时的 SKU 列表 明确枚举出所有合法组合。

2、推荐的数据结构

枚举 SKU(库存单位)仍然是电商领域最主流、最稳定、最易维护的方式,即便维度变多(颜色、尺码、款式、性别、厚薄、材质等),只要是商品销售中的“最终可买组合”,都建议通过 SKU 列表来做过滤。

原因:电商的核心业务单位就是 SKU

每个实际的商品组合(即将被购买的那个具体“衣服”)就是一个 SKU:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"spu_id": "xxx",
"name": "xxx商品",
"base_price": 12.0,
"sku_list": [
{
"skuId": "sku_abc123", // SKU(库存单位)ID,唯一标识
"spuId": "spu_abc", // 关联的 SPU ID,表示这个 SKU 是哪个 SPU(商品)的一部分。
"attributes": { // 商品属性
"color": "黑色", // 颜色(红 / 黑 / 白)
"size": "L", // 尺码(S / M / L / XL)
"gender": "女款", // 性别(适合男款/女款)
"style": "连帽", // 款式(如:连帽、无帽)
"thickness": "加厚" // 厚度(如:常规、加厚)
},
"stock": 5, // 库存数量
"price": 199 // 商品价格
}
// 继续组合...
]
}

所以上诉电商的问题本质:多属性组合的有效性筛选

这正是一个典型的 多维交叉过滤(multi-dimensional filtering) 或称 SKU(库存单位)选择问题

如何管理 SPU 和 SKU 的关系

在电商领域,SPU(Standard Product Unit,标准产品单元)SKU(Stock Keeping Unit,库存单位) 是两个非常重要的概念。它们的关系是 SKU 包含在 SPU 中,而 SPU 是用于描述一个商品的通用属性,SKU 则是根据具体的属性组合(如颜色、尺码等)生成的具体库存项。

  1. SPU 表:主要存储商品的核心信息和基础属性。
  2. SKU 表:存储每个商品的具体变体,包括颜色、尺码、价格、库存等详细信息。

数据库关系图:

  • SPU 表

    spuId name brand category material style description
    spu_001 T恤 Nike 运动服 连帽 舒适的运动款
  • SKU 表

    skuId spuId color size gender style price stock weight
    sku_001 spu_001 红色 S 女款 连帽 199.0 10 0.5
    sku_002 spu_001 蓝色 M 女款 无帽 209.0 20 0.6

3、操作逻辑

3.1、入参:选择时候的变量定义

为方便演示,我们假设结构如下

1
2
3
4
5
6
7
8
const skuList = [
{ id: 'sku1', attributes: { color: '红', size: 'S', gender: '女款', thickness: '常规' }},
{ id: 'sku2', attributes: { color: '红', size: 'M', gender: '男款', thickness: '常规' }},
{ id: 'sku3', attributes: { color: '红', size: 'M', gender: '女款', thickness: '加厚' }},
{ id: 'sku4', attributes: { color: '黑', size: 'M', gender: '男款', thickness: '加厚' }},
{ id: 'sku5', attributes: { color: '黑', size: 'L', gender: '男款', thickness: '加厚' }},
];
// 红色只有 S、M,没有L; 红色S只有女款,没有男款

场景举例:

  1. 用户原本选择:

    1
    oldSelected = { color: "黑", size: "M" }
  2. 用户进行操作:

    1
    2
    3
    change = { category: "color", value: "红" } // 用户选择颜色 `"红"`
    // 如果用户取消某项(如取消颜色选择)怎么办?
    change = { category: "color", value: null } // 用户取消颜色 `"红"`
  3. 合并出新选项:

    1
    newSelected = { color: "红", size: "M" }

3.2、核心逻辑:当用户选择某些属性后,怎么计算每个(自身+其他)属性的可选/可替换的项

这些就可以用来禁用/置灰 UI 上不可选的项。

3.2.1、核心逻辑思路

我们以下面操作为例

1
2
oldSelected = { color: "红", size: "S", gender: "女款" }
change = { category: "thickness", value: "常规" }

得到

1
newSelected = { color: "红", size: "S", gender: "女款", thickness:"常规" };

颜色的可替换/选择项计算过程如下,

①去掉 newSelected 中的 color,得到 { size: “S”, gender: “女款”, thickness:”常规” }

②在 skuList 中过滤出符合条件的所有 sku

③从过滤出来的这些SKU中提取 color 的所有可能值,去重后即为color分类的 validOption

3.2.2、核心逻辑示例

场景: 初始无选择 -> 选择”红” -> 选择”S” -> 选择”女款” -> 切为”常规”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 场景1:
oldSelected = {} // 初始无选择
change = { category: "color", value: "红" }, // 用户选择 "红"
得到
{
selected: { color: "红" },
selectedSKU: null, // 未全选,不匹配具体 SKU
validOptions: {
color: ["红", "黑"], // 可以切换颜色
size: ["S", "M"], // 红色只有 S、M,没有L
gender: ["男款", "女款"],
thickness: ["常规", "加厚"]
}
}

// 场景2:
oldSelected = { color: "红" }
change = { category: "size", value: "S" }
得到
{
selected: { color: "红", size: "S" }, // 当前选择状态
selectedSKU: null, // 未全选,无匹配 SKU
validOptions: {
color: ["红", "黑"], // 可以切换颜色
size: ["S", "M"], // 红色只有 S、M,没有L
gender: ["女款"], // 红S 下只有 女款
thickness: ["常规", "加厚"]
}
}

// 场景34:
oldSelected = { color: "红", size: "S", gender: "女款" }
change = { category: "thickness", value: "常规" }
得到
{
selected: { color: "红", size: "S", gender: "女款", thickness:"常规" }, // 当前选择状态
selectedSKU: { id: 'sku1', attributes: { ... }}, // 匹配的唯一 SKU
validOptions: {
color: ["红", "黑"], // 可以切换颜色
size: ["S", "M"], // 红色只有 S、M,没有L
gender: ["女款"], // 红S 下只有 女款
thickness: ["常规", "加厚"]
}
}

3.3、结果:若用户所有属性都选了,就直接定位 SKU

1
2
3
4
const selected = { color: "黑", size: "M" };
const sku = validSKUs.find(sku => sku.color === selected.color && sku.size === selected.size);

// sku.skuId === "sku6"

4、操作逻辑的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 更新 SKU 选择状态,并返回可选项和匹配的 SKU
* @param {Object} oldSelected - 当前已选的属性(如 `{ cupSize: "大杯", temperature: "冰" }`)
* @param {Object} change - 用户新选择的属性(如 `{ category: "sweetness", value: "无糖" }`)
* @param {Array} skuList - 所有 SKU 数据(如 `[{ id: "sku1", attributes: { ... } }, ...]`)
* @returns {Object} - 返回新的选择状态、可选项和匹配的 SKU
*/
function updateSKUState(oldSelected, change, skuList) {
if (!skuList || skuList.length === 0) {
return { selected: oldSelected, selectedSKU: null, validOptions: {} };
}

// 1. 更新选择
const newSelected = { ...oldSelected, [change.category]: change.value };

// 2. 计算所有可能的属性分类
const attributeKeys = Object.keys(skuList[0].attributes);

// 3. 核新逻辑:计算每个分类在其他分类选择或未选择情况下的可选/可替换值(动态计算,不固定已选属性)
// ①、对每个属性分类 attrKey,临时排除它自身(避免自我约束)。
// ②、根据其他已选属性过滤 skuList,得到符合条件的SKU。
// ③、从这些SKU中提取 attrKey 的所有可能值,去重后存入 validOptions。
const validOptions = {};
for (let attrKey of attributeKeys) {
// 3.1 临时忽略当前属性,计算剩余约束下的可选值
const tempSelected = { ...newSelected };
delete tempSelected[attrKey]; // 排除当前属性,避免自我约束

// 3.2 计算当前属性的可选值(基于其他已选属性的约束)
const availableValues = [
...new Set(
skuList
.filter(sku =>
Object.entries(tempSelected).every(([k, v]) => !v || sku.attributes[k] === v)
)
.map(sku => sku.attributes[attrKey]) // 从每个 SKU 中提取指定值,生成一个新数组。
// .map() 数组高阶函数,对数组中的每个元素执行回调函数,并返回一个新数组。

)
];
validOptions[attrKey] = availableValues;
}

// 4. 检查是否匹配到唯一 SKU
const isAllSelected = attributeKeys.every(key => newSelected[key]);
const selectedSKU = isAllSelected
? skuList.find(sku =>
attributeKeys.every(attrKey => sku.attributes[attrKey] === newSelected[attrKey])
)
: null;

return {
selected: newSelected,
selectedSKU,
validOptions, // 返回所有属性的动态可选值(包括已选属性)
};
}

二、普通奶茶下单

我们将电商衣服的选择扩展到奶茶下单中,

杯型(单选):大杯、超大杯
温度(单选):冰
糖度(单选):标准甜、少糖、无糖

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
const skuList = [
{ id: 'sku11', attributes: { cupSize: '大杯', temperature: '冰', sweetness: '标准甜' }},
{ id: 'sku12', attributes: { cupSize: '大杯', temperature: '冰', sweetness: '少糖' }},
{ id: 'sku13', attributes: { cupSize: '大杯', temperature: '冰', sweetness: '无糖' }},
{ id: 'sku21', attributes: { cupSize: '超大杯',temperature: '冰', sweetness: '标准甜' }},
{ id: 'sku22', attributes: { cupSize: '超大杯', temperature: '冰', sweetness: '少糖' }},
{ id: 'sku23', attributes: { cupSize: '超大杯', temperature: '冰', sweetness: '无糖' }}
];


// 场景: 初始无选择 -> 选择"大杯" -> 选择"冰" -> 选择"标准甜" -> 切为"无糖"
// 场景1: 初始无选择 -> 选择"大杯"
oldSelected = {} // 初始无选择
change = { category: "cupSize", value: "大杯" }, // 用户选择 "大杯"
得到
{
selected: { cupSize: "大杯" },
selectedSKU: null, // 未全选,不匹配具体 SKU
validOptions: {
cupSize: ["大杯", "超大杯"], // 可以切换杯型
temperature: ["冰"], // 只有 "冰" 可选(因为 "大杯" 只有 "冰")
sweetness: ["标准甜", "少糖", "无糖"] // 所有甜度可选
}
}

// 场景2:
oldSelected = { cupSize: "大杯" }
change = { category: "temperature", value: "冰" }
得到
{
selected: { cupSize: "大杯", temperature: "冰" }, // 当前选择状态
selectedSKU: null, // 未全选,无匹配 SKU
validOptions: {
cupSize: ["大杯", "超大杯"],
temperature: ["冰"],
sweetness: ["标准甜", "少糖", "无糖"]
}
}

// 场景3:
oldSelected = { cupSize: "大杯", temperature: "冰" }
change = { category: "sweetness", value: "标准甜" }
得到
{
selected: { cupSize: "大杯", temperature: "冰", sweetness: "标准甜" },
selectedSKU: { id: "sku13", attributes: { ... } }, // 匹配到唯一 SKU
validOptions: {
cupSize: ["大杯", "超大杯"], // 仍可切换杯型
temperature: ["冰"], // 温度固定(只有 "冰")
sweetness: ["标准甜", "少糖", "无糖"] // 仍可切换甜度
}
}

// 场景4:
oldSelected = { cupSize: "大杯", temperature: "冰", sweetness: "标准甜" };
change = { category: "sweetness", value: "无糖" };
得到
{
selected: { cupSize: "大杯", temperature: "冰", sweetness: "无糖" }, // 当前选择状态
selectedSKU: { id: 'sku13', attributes: { ... }}, // 匹配的唯一 SKU
validOptions: {
cupSize: ["大杯", "超大杯"],
temperature: ["冰"],
sweetness: ["标准甜", "少糖", "无糖"]
}
}

可以看出当奶茶的所有属性都可以选择的时候,我们得到的 validOptions 为

1
2
3
4
5
{
cupSize: ["大杯", "超大杯"],
temperature: ["冰"],
sweetness: ["标准甜", "少糖", "无糖"]
}

这也相当于是验证了我们不需要做此计算。

所以理论上可简化为

1
2
3
4
5
6
7
8
9
10
11
12
{
"spu_id": "xxx",
"name": "xxx商品",
"sku_list": [

],
"categories": {
cupSize: ["大杯", "超大杯"],
temperature: ["冰"],
sweetness: ["标准甜", "少糖", "无糖"]
}
}

1、推荐结构

但不同杯型价格不同,所以更合适的结构是下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
{
"spu_id": "xxx",
"name": "xxx商品",
"sku_list": [
{ id: 'sku11', attributes: { cupSize: '大杯'}, "stock": 10, "price": 990},
{ id: 'sku21', attributes: { cupSize:'超大杯'}, "stock": 8, "price": 1199}
],
"categories": {
temperature: ["冰"],
sweetness: ["标准甜", "少糖", "无糖"]
}
}

即可以任意切换且不影响价格的放在categories中,其他放在sku_list中。

2、其他可能结构

也有些会设计成下面这样子,完全不用sku。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"spu_id": "xxx",
"name": "xxx商品",
"base_price": 990,
"sku_list": [

],
"categories": [
{
"name": "杯型",
"choices": [
{ "label": "大杯", "extra_price": 0 },
{ "label": "超大杯", "extra_price": 200 }
]
},
{
"name": "温度",
"choices": [
{ "label": "冰", "extra_price": 0 }
]
},
{
"name": "甜度",
"choices": [
{ "label": "标准甜", "extra_price": 0 },
{ "label": "少糖", "extra_price": 0 },
{ "label": "无糖", "extra_price": 0 },
{ "label": "香草+3元", "extra_price": 300 }
]
}
],
}

3、其他思考

问:如果选择糖的时候,有的需要增加费用,结果怎么提供?

答:方法①用sku列表时,将糖添加到sku中 或者 方法②不用sku列表时,为糖增加 extra_price。

4、小结

推荐结构:可以任意切换且不影响价格的单选放在categories中,其他放在sku_list中。

三、扩展奶茶下单

杯型(单选):超大杯、大杯、中杯
温度(单选):正常冰、少冰、热
糖度(单选):标准甜、少甜、微甜、无糖
加料(多选):珍珠、葡萄、芋圆、椰果、不另外加

1、需求分析

1.1、功能要求分析

  1. 分类与选项定义:
    • 杯型、温度、糖度:单选
    • 加料:多选
    • 不同杯型,价格不一样
  2. 规则限制:
    • 选择“无糖”时,加料只能选择“不另外加”,其他全部置灰禁用
    • 选择“热”时,加料不能选“葡萄”

请问应该怎么设计数据结构和实现”选择某个分类后自动刷新其他分类可选项并置灰不可选项目”的功能。

1.2、奶茶系统推荐结构

核心思路:

  • SKU 负责枚举“杯型 + 温度 + 糖度”这种固定组合(一般对应定价逻辑)
  • 加料是用户行为的动态选项,作为 “附加项”附加到 SKU 上
    • 不影响 SKU 的价格/库存
    • 有自己的校验规则(如「无糖」限制加料)

实际电商和点单系统中常用的做法 ——
将「确定的可枚举组合」用 SKU 管理,
将「不确定的动态选项」如加料、附加服务用“附加项”来处理。

加料不放在SKU中,是因为组合数量会爆炸且冗余

  • 假设每类属性都有 4 个值,加料 5 个(多选),组合数可能是:

    3(杯型) × 3(温度) × 4(糖度) × 2⁵(加料多选组合) = 3 × 3 × 4 × 32 = 1,152 种

2、结构🧱设计拆分

2.1、SKU 定义:仅含主选项

1
2
3
4
5
6
7
8
9
10
11
12
{
"spu_id": "xxx",
"name": "xxx商品",
"sku_list": [
{ id: 'sku11', attributes: { cupSize: '大杯'}, "stock": 10, "price": 990},
{ id: 'sku21', attributes: { cupSize:'超大杯'}, "stock": 8, "price": 1199}
],
"categories": {
temperature: ["冰"],
sweetness: ["标准甜", "少糖", "无糖"]
}
}

2.2、可多选的附加项(加料)定义

1
2
3
4
5
6
7
8
9
10
11
12
const categories = {
"toppings": {
"type": "multi", // 多选
"options": [
{ "label": "珍珠", "value": "pearl", "price": 1 },
{ "label": "葡萄", "value": "grape", "price": 2 },
{ "label": "芋圆", "value": "taro", "price": 1.5 },
{ "label": "椰果", "value": "coconut", "price": 1 },
{ "label": "不另外加", "value": "none", "price": 0 }
]
}
}

2.3、规则系统(限制附加项选择)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const rules = [
{
if: { sweetness: "无糖" },
thenEnableOnly: {
toppings: ["none"]
}
},
{
if: { temperature: "热" },
thenDisable: {
toppings: ["grape"]
}
}
];

2.4、总结设计优势与场景类似

部分 用法 优点
SKU 负责主商品的组合(杯型、温度、糖度) ✅ 易管理,价格统一,库存精准
附加项 处理加料等用户增值选项 ✅ 动态扩展,多选灵活,价格单独计算
规则系统 控制选择互斥/可选性等逻辑 ✅ 易维护、可配置、逻辑分离

使用场景类似

场景 SKU 管控 附加项管理
衣服 颜色、尺码、版型 印花、刺绣、包装
手机下单 颜色、存储、处理器 贴膜、延保、耳机
奶茶/披萨点单 杯型、糖度、温度 加料、备注、打包
机票/酒店预订 舱位、房型、套餐 行李、早餐、保险、接送

如你所见:“SKU + 附加项 + 规则” 是一种高扩展、高灵活的标准模型 ✅

3、代码实现

加料 从 SKU 属性中拿出来,变成了“附加项”:

拆成两个更新模块更合理:

1
2
3
4
5
6
7
8
function updateSKUState(oldSelectedAttrs, change, skuList) {
// 👇 仅用于处理单选项(杯型、温度、糖度),适合“所有选项都是 SKU 属性(单选)”的场景
}

function updateAddOnsState(newSelectedAttrs, oldSelectedAddOns, rules) {
// 👇 根据 SKU 选项 + 附加项 + 规则动态返回:
// 可选加料、禁用项、自动取消逻辑等
}

分别用于:

  1. 处理 SKU 属性变化(杯型、温度、糖度)
  2. 处理加料(附加项)的可选项、禁用项和自动取消逻辑
方法名 功能
updateSKUState 处理 SKU 类单选属性变化,筛选可选项,找出精确 SKU
updateAddOnsState 根据规则动态控制加料的禁用项,并移除当前选择中无效的加料选项

3.1、updateSKUState — 处理 SKU 单选属性变化

步骤1:用户选择/替换基础属性(调用 updateSKUState

1
2
3
4
5
6
7
8
9
10
11
12
let oldSelected = { cupSize: "大杯", temperature: "冰", sweetness: "标准甜" };
const change = { category: "sweetness", value: "无糖" }; // 切为"无糖"
得到
{
selectedAttrs: { cupSize: "大杯", temperature: "冰", sweetness: "无糖" }, // 当前选择状态
selectedSKU: { id: 'sku13', attributes: { ... }}, // 匹配的唯一 SKU
validOptions: {
cupSize: ["大杯", "超大杯"],
temperature: ["冰"],
sweetness: ["标准甜", "少糖", "无糖"]
}
}

3.2、 updateAddOnsState — 处理加料限制规则(多选)

步骤2:根据当前属性更新附加选项(调用 updateAddOnsState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const selectedAttrs = {
cupSize: "大杯",
temperature: "冰",
sweetness: "无糖"
};
const selectedAddOns = ["椰果"];
const rules = [
{
if: { sweetness: "无糖" }, // 条件:选择"无糖"
thenDisable: { toppings: ["珍珠"] } // 则禁用"珍珠"(无糖奶茶不能加珍珠)
},
{
if: { temperature: "热" }, // 条件:选择"热饮"
thenEnableOnly: { toppings: ["红豆", "布丁"] } // 则只能加"红豆"或"布丁"
}
];


// 计算可用的附加项
const enabledAddOns = updateAddOnsState(selectedAttrs, selectedAddOns, rules);
// 结果:
// enabledAddOns = ["红豆", "布丁", "椰果"] // "珍珠" 不能点

方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function updateAddOnsState(selectedSKUAttributes, selectedAddOns, rules) {
const allToppings = categories.toppings.options.map(opt => opt.value || opt);
let enabledAddOns = new Set(allToppings); // 默认全部可用
let allowOnlySet = null;

for (let rule of rules) {
// 判断该规则是否符合当前选择状态,比如:`if: { sweetness: "无糖" }`,当前选择的 `sweetness` 也是 `"无糖"`,则满足。
const isMatched = Object.entries(rule.if).every(
([key, value]) => selectedSKUAttributes[key] === value
);

if (isMatched) {
// 如果命中了 thenEnableOnly,记录允许的列表(唯一优先)
if (rule.thenEnableOnly && rule.thenEnableOnly.toppings) {
allowOnlySet = new Set(rule.thenEnableOnly.toppings);
}

// 在没有 thenEnableOnly 优先时候,如果命中了 thenDisable,则从默认的全部中删掉禁用的
if (!allowOnlySet && rule.thenDisable && rule.thenDisable.toppings) {
rule.thenDisable.toppings.forEach(opt => enabledAddOns.delete(opt));
}
}
}

// 如果有 thenEnableOnly,就直接使用它来作为最终可用选项
if (allowOnlySet) {
enabledAddOns = allowOnlySet;
}

return Array.from(enabledAddOns);
}

总结

从普通奶茶下单示例中,我们可以看出当奶茶的所有属性都可以选择的时候,我们得到的 validOptions 为

1
2
3
4
5
{
cupSize: ["大杯", "超大杯"],
temperature: ["冰"],
sweetness: ["标准甜", "少糖", "无糖"]
}

这也相当于是验证了我们不需要做此计算。

所以,理论上 cupSize 、 temperature 、 sweetness 等就没必要通过sku方式获得。但当这其中有属性会应影响价格时候(如cupSize),就还是得放在sku中。所以最后推荐的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"spu_id": "xxx",
"name": "xxx商品",
"sku_list": [
{ id: 'sku11', attributes: { cupSize: '大杯'}, "stock": 10, "price": 990},
{ id: 'sku21', attributes: { cupSize:'超大杯'}, "stock": 8, "price": 1199}
],
"categories": {
temperature: ["冰"],
sweetness: ["标准甜", "少糖", "无糖"]
},
"toppings": {
"type": "multi", // 多选
"options": [
{ "label": "珍珠", "value": "pearl", "price": 1 },
{ "label": "葡萄", "value": "grape", "price": 2 },
{ "label": "芋圆", "value": "taro", "price": 1.5 },
{ "label": "椰果", "value": "coconut", "price": 1 },
{ "label": "不另外加", "value": "none", "price": 0 }
]
},
"toppings_rules": [
{
if: { sweetness: "无糖" }, // 条件:选择"无糖"
thenDisable: { toppings: ["珍珠"] } // 则禁用"珍珠"(无糖奶茶不能加珍珠)
},
{
if: { temperature: "热" }, // 条件:选择"热饮"
thenEnableOnly: { toppings: ["红豆", "布丁"] } // 则只能加"红豆"或"布丁"
}
]
}

使用此结构,在衣服、手机选择时候也仍然使用。

使用的计算详见上文。

使用有效 SKU 列表:电商场景主流方案,直接枚举合法组合,性能好,扩展性强

使用 rules + if/thenDisable:比较适合你之前描述的“条件式限制”,用于逻辑明确的选项限制

END