Open Design

Open Design:Claude Design 的开源平替代,让任何 AI 模型都能为你设计

引言:11天,一场开源对商业的惊艳“逆袭”

2026年4月,Anthropic 推出了 Claude Design,让 AI 能够直接产出精美的设计稿,迅速在开发者社群中引发了轰动。然而,它的封闭源代码、仅限云端付费使用的模式,也让许多人望而却步。

短短11天后,一位名为Tom Huang的开发者便向世界展示了开源的力量。他与其团队耗時72小时、编写超过18,700行代码,成功逆向工程推出了 Open Design(曾用名 Open Claude Design),在 X 平台上24小时内就获得了超过38万次观看与1600多个赞。项目上线后迅速在 GitHub 上累积了超过5000颗星,并持续增长。

这不仅是“复刻”,更是一次超越。Open Design 不仅重现了 Claude Design 超过95%的核心体验,更遵循“本地优先、开源可部署”的理念,把设计主权交还给了用户。

一、 什么是 Open Design?

Open Design 是一个“AI 设计工作流编排器”。

它不是一个简单的“文生图”工具,也不是一个封闭的 SaaS 产品。其核心价值在于:你不必再被特定的 AI 模型或平台绑定。Open Design 本身不内置“设计师”,而是利用你电脑上已有的编程 Agent(如 Claude Code, Cursor, Gemini CLI 等)作为设计引擎。

简单来说,它做了一个非常聪明的转换:把你手头只会写代码的 Agent,瞬间变成一个遵循严格设计规范、拥有海量品牌风格的高级设计师。

核心架构:Skills × Design Systems

Open Design 的强大源于其巧妙的架构设计,即“技能”与“设计系统”的笛卡尔积效应。

  • 19个核心技能:它将设计任务模块化,内置了网页原型、PPT演示、仪表盘、营销邮件等19种具体的设计技能。每个技能都是一个包含详细指令和参考资料的文件夹。
  • 71+套品牌设计系统:这是 Open Design 的杀手锏之一。它内置了包括 Stripe、Linear、Vercel、Apple、Notion、Spotify 等在内的71+套知名品牌的设计规范,涵盖色彩、字体、间距、组件等所有细节。

二、 为什么选择 Open Design?(对比 Claude Design)

为了让你更直观地了解它的优势,这里将它与原版的 Claude Design 进行一次详细对比:

对比维度 Open Design (开源版) Claude Design (官方版)
开源协议 ✅ Apache 2.0 开源 ❌ 闭源商业产品
模型兼容 ✅ 16种 CLI + BYOK API(可用任何模型) ❌ 仅限 Anthropic 模型
部署方式 ✅ 本地 / Vercel / Docker / 桌面App ❌ 仅限云端
数据隐私 ✅ 本地 SQLite 存储,数据完全自主 ❌ 数据存储在云端
设计系统 ✅ 71+套可切换,且可自行扩展 ⚠️ 固定且不公开
费用 ✅ 完全免费(自带 API Key 或 CLI) ❌ 最低 $20/月起
核心机制 ✅ 反AI-slop,通过问卷和自检确保质量 ⚠️ 较为依赖模型本身

关键优势: Open Design 内置了一套 “反AI-slop”机制,防止 AI 生成那些千篇一律的“廉价设计”(例如泛滥的紫色渐变、圆角卡片配侧边色块等)。它通过前置问卷、品牌色提取协议和五维自我批判机制,强迫 AI 按专业设计师的工作流产出内容,从而显著提升质量。

三、 快速上手:3步完成本地部署

只要你的电脑上准备好了必要的工具,整个安装过程不会超过10分钟。

1. 环境准备

在开始之前,请确认你的电脑已经安装了以下工具:

依赖项 版本要求 检查命令 安装 说明
Node.js v20 或更高 node -v 推荐使用 nvm 管理版本
pnpm 10.33.x 或更高 pnpm -v brew install corepack 通过 corepack enable 启用
Git 任意版本 git --version 用于克隆仓库
AI CLI(可选,但推荐) 任一即可 which claude Claude Code / Codex / Cursor 等

2. 安装和配置

打开你的终端(Terminal),一次执行以下命令:

第一步:安装项目

1
2
3
4
5
6
7
# 克隆项目
git clone https://github.com/nexu-io/open-design.git
cd open-design

# 安装依赖
corepack enable
pnpm install

第二步:配置 API(关键步骤)

Open Design 支持两种模式,你可以根据自己的情况选择:

方式一:CLI 模式

如果你已经安装了 Claude Code 等 CLI 工具,Open Design 会自动扫描并优先使用它们,无需额外配置。

方式二:BYOK 模式(推荐国内用户)

如果没安装,你需要自己配置。此方式也适合你用国内模型来配置。

1
2
3
# macOS / Linux
export ANTHROPIC_BASE_URL="https://你的中转服务地址"
export ANTHROPIC_API_KEY="sk-你的API密钥"

之后如果要修改配置也可以在启动服务里修改:

Open Design_2setting_1

3、启动

配置完成后,运行如下命令即可生效。

1
pnpm tools-dev run web

Open Design_1run

启动成功后,在浏览器中打开给出的Web地址,即可。

如果要关闭关闭服务

1
2
# 在终端窗口中,按下:
Ctrl + C

四、 在浏览器中的使用示例:让你的第一个设计诞生

  1. 打开浏览器,访问给出的web地址 http://127.0.0.1:57063/

  2. 在左侧选择一个 Skill(建议新手从 web-prototype 开始)。

  3. 在右侧选择一个 Design System(试试 stripelinear,感受一下顶级品牌的视觉风格)。

  4. 在中央的输入框中,用自然语言描述你的需求,例如:

    “为一个冥想App设计一个引导页,要求色调让人感到平静。”

  5. 点击生成。你会先看到一个需求表单,这是 Open Design 的关键一步,它会引导你明确“为谁设计”、“什么风格”、“尺寸如何”等细节。

  6. 填写完表单,点击确认,然后你就可以在时间线上看到 Agent 的实时计划进度,并最终获得一个可直接运行的 HTML 网页或设计稿。

Open Design_3demo_1

阅读全文 →

新电脑(UOS)初始安装

华为 UOS 统信电脑初始安装

一、电脑认识

1、系统信息

  • 设备:华为擎云 L540(HUAWEI QingYun L540)
  • CPU 架构:ARM64 (aarch64)
  • 系统:UOS Desktop 20 专业版
  • 输入法框架:Fcitx 4.2.9
  • 搜狗拼音版本:4.2.1.305 (UOS 版)
  • 搜狗语音数据包:1.0.0

2、重要结论:华为鲲擎 L540 GPU 不支持硬件 OpenGL 加速

3、相关影响

3.1 终端

Ghostty / Kitty / WezTerm 等 GPU 加速终端均无法正常运行
(因鲲鹏显卡只有 HISI DRM 显示控制器,无硬件 OpenGL)

✅ 推荐方案:Deepin 终端(自带)+ tmux = 最佳体验
轻量、稳定、不需要折腾

3.2 Markdown 查看/编辑器

meditor 不支持

原因:Flutter 应用,华为鲲鹏无 OpenGL 硬件加速,无法运行

二、OpenCode

1、安装

官网地址: https://opencode.ai/zh/download

1.1 下载的桌面版 .deb 无法使用,提示提示软件包架构不匹配/点击打开OpenCode 提示无可渲染的OpenGL

1.2 使用命令安装

1
curl -fsSL https://opencode.ai/install | bash

2、opencode 内滚动正常,鼠标选中提示了 copy to clipboard,但实际无法粘贴

根源:复制被拦截,是opencode TUI 鼠标设置问题

解决:
1、添加配置文件:~/.config/opencode/tui.json

完整配置示例:

1
2
3
4
{
"$schema": "https://opencode.ai/tui.json",
"mouse": true
}

2、mouse 选项说明:

现象
true(默认) opencode 内滚动正常,但鼠标选中复制被拦截
false 终端原生选中复制正常,但滚动交给终端处理

当前设置:mouse: true(优先保证滑动)

3、继续opencode上次的会话

1、退出 opencode

在OpenCode会话窗口中,输入 /exit 回车

opencode_ses_1exit

2、退出后,看到他给我们的继续刚才会话的命令,然后拷贝粘贴

opencode_ses_2continue

4、opencode 会话管理(快速恢复历史会话)

用途:分页浏览历史会话并选择恢复

安装

让 AI 通过 opencode-sessions-manager skill 安装即可,自动处理脚本部署和 shell 配置。

依赖

  • sqlite3sudo apt install sqlite3

使用

1
opencode_list
操作 说明
输入序号 恢复对应会话
n / p 下一页/上一页
直接回车 开新会话
q 退出

三、终端

1、终端设置

1.1、默认工作目录设置

作用:新开终端自动进入 ~/Downloads 目录

在 ~/.bashrc 末尾添加:

1
cd ~/Downloads  # 终端默认进入 Downloads 目录

立即生效命令:source ~/.bashrc

2、终端工具

2.1、Deepin 终端(自带)

使用:Deepin 终端(自带)+ tmux = 最佳体验

2、tmux(终端复用器,分屏+会话保持,不依赖 GPU)

1
2
3
4
5
# 安装:
sudo apt install tmux

# 启动:
tmux

常用快捷键:

  • Ctrl+B % — 垂直分屏

  • Ctrl+B “ — 水平分屏

  • Ctrl+B D — 断开会话(后台继续跑)

  • tmux attach — 重连回会话

四、Markdown 查看/编辑器

1、ReText(Markdown 编辑器,PyQt 轻量,在华为鲲鹏上实测可用)

1、安装:

1
sudo apt install retext

2、启动

方式一:启动菜单找 “ReText”

方式二:在终端运行:

1
retext

2、Zettlr(Markdown 编辑器,免费开源,有 ARM64 版,Github需科学上网,暂未使用)

DEB 包下载地址:
https://github.com/Zettlr/Zettlr/releases/download/v3.6.0/Zettlr-3.6.0-arm64.deb
AppImage 下载地址:
https://github.com/Zettlr/Zettlr/releases/download/v3.6.0/Zettlr-3.6.0-arm64.AppImage

安装 DEB:
sudo dpkg -i Zettlr-3.6.0-arm64.deb

或运行 AppImage:
chmod +x Zettlr-3.6.0-arm64.AppImage
./Zettlr-3.6.0-arm64.AppImage

五、Sublime Text

让AI给我 Sublime Text 的安装包下载地址,我等下自己下载安装

附:之前用的 Sublime Text ARM64 直接下载地址:

https://download.sublimetext.com/sublime-text_build-4200_arm64.deb

六、Git 命令安装

七、opencode Skill 安装

1、superpowers

2、find-skills — 帮助发现和安装 agent skills

用途:当用户问 “how do I do X”、”find a skill for X” 时触发

方式:正常安装

3、business-env-quarter — 营商环境体验官项目方案撰写

方式:通过软链接指向 ~/Downloads/Family/Juan/business-env-quarter/

结果:~/.config/opencode/skills/Family/Juan/business-env-quarter/SKILL.md

4、opencode-skill-creator 插件(antongulin 版)

说明:专为 opencode 适配的插件+skill,npm 包名 opencode-skill-creator

GitHub:https://github.com/antongulin/opencode-skill-creator

5、【后续待办】opencode skill 安装(需科学上网连接)

5.1 anthropics/skills 含 anthropics/skill-creator 、anthropics/frontend-design

https://github.com/anthropics/skills.git

会涉及到 Node.js 和 npm 的安装

八、语音输入法

1、结论

搜狗语音不可用 ❌

考虑替代方案

方案 说明 状态
Deepin AI 助手(科大讯飞) 系统预装,iFLYTEK 语音识别 ✅ 已安装
讯飞输入法 UOS 商店可能提供 ARM64 版 ❓ 待调研
搜狗语音 UI Compositor 兼容性 bug ❌ 放弃

2、搜狗语音输入法问题排查完整记录

2.1 安装流程(日后参考)

1
2
3
4
5
6
7
8
9
10
11
# 1. 安装主输入法
sudo apt install com.sogou.sogoupinyin-uos
# 2. 安装语音数据包
sudo apt install com.sogou.sogoupinyin-uos-sogouvoice
# 3. 禁用旧版手写模块
mkdir -p ~/.config/fcitx/addon
echo "[Addon]\nEnabled=False" > ~/.config/fcitx/addon/fcitx-sogoupinyinhxm-disable.conf
# 4. 配置 OpenAL 走 PulseAudio(安全方案,不加 audio 组)
echo -e "[general]\ndrivers = pulse,alsa" > ~/.alsoftrc
# 5. 重启输入法
fcitx-remote -r

2.2 使用中发现的问题/实测结果:搜狗语音不可用 ❌

2.2.1 表现
  • 麦克风按钮:✅ 能弹出语音控制窗口
  • 录音功能:✅ 能识别语音(麦克风正常工作)
  • “完成”按钮:❌ 点击无响应,窗口卡死,无法关闭
  • 文字输出:❌ 语音识别后未输出文字
2.2.2 根因
  • 日志记录:TimerEnsureGetCompositingStatus stop m_nCheckedCount:20
  • 搜狗语音 UI 窗口无法检测系统 Compositor 状态,事件循环异常
  • 这是 SogouVoiceInput-uos v1.0.0 在 UOS ARM64 上的兼容性 bug,无法通过配置修复

End

智能家居_涂鸦Demo

一、OEM App

1、定价

更多App详细权限介绍及定价可查看:Tuya 开发者平台 OEM App 计划

OEM App 基础版 OEM App 高级版
定价 ¥33500/年 ¥67000/年

2、产品创建

涂鸦 oem app 创建

二、SDK App

iOS swift https://github.com/tuya/tuya-home-ios-sdk-sample-swift

必备

1、Multicast 权限

Multicast 权限 申请页面 中按照 Multicast 权限 申请教程 进行申请。

填写项 填写示例 注意事项
App Name 为 Wi-Fi 快连配网(EZ 配网)功能申请应用权限 -
Apple Store URL 例如,智能生活的 Apple ID of App 为 1115101477,所以链接为 https://apps.apple.com/app/id1115101477 该 URL 需要您拼接,格式为 https://apps.apple.com/app/id[Apple ID of App]。
Apple ID of App(非必填) 为 Wi-Fi 快连配网(EZ 配网)功能申请应用权限 -
App Category 为 Wi-Fi 快连配网(EZ 配网)功能申请应用权限 -
Describe the main purpose of your app 用于家居场景,提供设备配网、设备控制、固件升级、自动化等。 请勿直接使用右侧文案,根据您 App 的应用场景做一定调整。
Explain why your app needs to send multicast or broadcast traffic, or browse for all Bonjour service types. 我们的 App 需要和我们的硬件设备进行通信,由于 IP 地址和端口不固定,使用自定义的广播组播协议进行通信,使用 UDP 6666、6667,TCP 6668 端口。例如,需要将设备信息注册到云端的场景。App 发送包含经过加密的 UDP 广播包或者组播包。设备 Wi-Fi 芯片在接收到该 UDP 包后,通过特定的 UDP 组织形式就可以解密出信息,接着设备进行 Wi-Fi 配置后即可上网连接云端。 请勿直接使用右侧文案,建议在描述上做一些调整,但必须包含 UDP 6666、6667,TCP 6668 端口 这些内容。

Thank you for your submission.

We’ll review your request and contact you soon with a status update.

Request ID: HZ5RT74G99

三、数据格式

创建新家

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
"result" : {
"id" : 164328460,
"role" : 2,
"lon" : 0,
"rooms" : [
{
"displayOrder" : 1,
"id" : 77716018,
"ownerId" : "240039697",
"status" : true,
"name" : "",
"uid" : "ay1776863768994ELVYe"
}
],
"gid" : 240039697,
"background" : "",
"admin" : true,
"displayOrder" : 1,
"groupUserId" : 176321183,
"lat" : 0,
"geoName" : "Xiamen",
"homeMode" : 0,
"dealStatus" : 2,
"groupId" : 240039697,
"managementStatus" : true,
"name" : "Xiamen"
},
"success" : true,
"status" : "ok",
"t" : 1776864297431
}

常见问题

1
2
3
4
5
https://github.com/tuya/tuya-home-ios-sdk-sample-swift 最新的 feat: Release SDK 7.5 demo 无法编译

缺少 thing_custom_config.json
且出现如下错误
Bridging header dependency scanning failure: /Users/qian/Project/Tuya/tuya-home-ios-sdk-sample-swift/TuyaAppSDKSample-iOS-Swift/TuyaAppSDKSample-iOS-Swift-Bridging-Header.h:13:9: fatal error: 'ThingSmartBusinessExtensionKit/ThingSmartBusinessExtensionKit.h' file not found

AI答:

  1. 确保 thing_custom_config.json 文件已正确配置并放置在工程中,参考 框架接入

  2. 确认 Podfile 中包含 ThingSmartBusinessExtensionKit 依赖项,执行 pod install 并检查模块安装是否成功 。

  3. 验证 Bridging-Header.h 文件中是否正确引入头文件,参考:

    1
    2
    #import <ThingSmartBusinessExtensionKit/ThingSmartBusinessExtensionKit.h>
    ``` 。
  4. 检查 Xcode 项目设置中的 Framework Search PathsHeader Search Paths 是否包含 SDK 安装路径 。

  5. 清理项目并重新编译,必要时清除 Xcode 和 CocoaPods 缓存 。

【提示:AI自动生成,仅供参考】

答案来源请参考:迁移指南

答案来源请参考:快速集成

答案来源请参考:iOS SDK 使用过程中遇到模块无法找到的问题如何解决?

设备的尝试

1、我们先用一台可以接入涂鸦app的设备来运行demo,如”涂鸦热水器计量插座”

2、在 https://platform.tuya.com/oem/sdkList 上新建使用App SDK 方式开发的app信息,之后将该信息填入 https://github.com/tuya/tuya-home-ios-sdk-sample-swift Demo中。

所创建的app需要以下 Capabilities

如果缺少都会有问题。

常见的缺少情况:

NEHotspotNetwork nehelper sent invalid result code [1] for Wi-Fi information request

必须拥有 Access Wi-Fi Information 授权
这是 Xcode 中的一项能力 (Entitlement),是获取 Wi-Fi 信息的“钥匙”。

配置方法:在 Xcode 中,点击你的项目 Target → Signing & Capabilities → 点击 + Capability → 搜索并添加 Access Wi-Fi Information。

3、运行项目,将设备置于配网状态,确保涂鸦app可以扫描得到。之后我们通过我们的app扫描,依次使用EZ mode等方式,如下是使用EZ mode 的方式。

注意:不要觉得扫描不出来就是有问题,有可能是你配置不到位。如下。

从代码中可以看出我们需要先创建家庭homeid,才可执行下去。

End

智能家居_语音模组

一、语音模组

涂鸦语音平台

二、离线语音控制模组

1、离线语音控制模组,是什么,有什么用,有哪些?

1.1、它到底是什么?

在行业中通常被称为离线语音识别模组语音控制模组。即不透过APP,直接在机器上加语音模组来接收指令

它是一个软硬件结合的解决方案,通常是一块小小的电路板,上面集成了:

  • 核心芯片:负责处理复杂的语音识别算法。
  • 麦克风接口:用来连接麦克风,采集声音。
  • 控制接口:比如UART(串口)、I2C、PWM等,用于和产品的主控板通信,或者直接控制电机、开关等。

你可以把它想象成一个“即插即用”的耳朵和大脑。你在产品开发时,只需将这个模组加上麦克风和喇叭(如果需要语音回复),它就能为你的传统产品(如灯具、风扇、插座)赋予“听懂”语音指令的能力。

.2、它有哪些核心特点?

这种模组之所以越来越受欢迎,主要在于它解决了传统智能家居的几个核心痛点:

  • 摆脱手机APP:用户不再需要下载APP、连接Wi-Fi、配对设备,开机就能用语音控制,对老人和小孩特别友好,真正实现了“所说即所得”。
  • 无需网络,响应快:所有的语音识别都在模组本地完成,完全不依赖外部网络。这带来了两个好处:一是响应速度极快(通常小于300毫秒),没有网络延迟;二是在没有Wi-Fi的地下室、工厂等环境中也能正常使用。
  • 保护用户隐私:因为语音数据不需要上传到云端识别,所以用户的谈话内容不会被上传,大大降低了隐私泄露的风险,这对医疗、办公等敏感场景尤为重要。
  • 成本低,开发快:市面上有很多成熟的模组可供选择,比如搜索结果中提到的SU-03T、涂鸦智能的VCT2、安信可的VC系列、启英泰伦的CI系列等。它们通常都提供配套的开发工具,可以让你很方便地自定义唤醒词(如“你好,小智”)和指令词(如“打开风扇”、“调到三档”),并烧录到模组中,大大缩短了产品开发周期。

1.3、几款主流的离线语音模组

为了方便你评估和选型,我把市面上几款主流的离线语音模组整理成了一个简单的对比表格:

模组型号 核心特点 / 适用场景 关键参数
SU-03T 通用性强,资料丰富,适合快速开发智能家居产品。 支持100+条指令,响应<300ms,待机功耗<50mW。
涂鸦 VCT2 基于CI1102芯片,生态完善,可方便地扩展Wi-Fi/Zigbee功能。 运行功耗≤150mW,提供UART、PWM、ADC等丰富接口。
安信可 VC系列 支持唤醒词自学习(无需编译固件),识别率高达98%,支持中英文。 支持150条本地指令,识别时间<100ms。
启英泰伦 CI系列 方案成熟,提供不同词条数量(200-300条)的选型,工业级稳定性。 支持10米远场识别,响应最快0.2S,工作温度-40℃~85℃。

2、是不是完全脱离后台服务器和前端app?以及它为什么不需要网络?

2.1、是不是就完全脱离了后台服务器和前端APP?

是的,完全正确。这正是离线语音模组的核心价值所在。

在你设想的这个使用场景下,它可以完全脱离后台服务器和前端APP独立运行

  • 无需求APP:用户拿到内置该模组的产品(如一个风扇),插上电,说出预设的唤醒词(如“你好,小智”),设备被唤醒,然后直接说出指令(如“风速调到二档”),设备执行。整个过程,手机是不参与的。
  • 无需后台服务器:所有的语音识别过程,都在模组本地(即芯片内部)完成,不需要将录音上传到任何云端服务器去解析。这意味着即使产品处于一个完全没有互联网信号的环境(如地下室、偏远山区),它依然能正常工作。

2.2、它为什么不需要网络?

这是一个非常好的问题,核心在于运算发生的位置

你可以把联网的智能音箱理解为一个 “瘦客户端” ,而把离线语音模组理解为一个 “胖客户端”

  • 联网设备(如智能音箱)
    1. 麦克风:采集你的声音。
    2. 上传:将声音文件通过Wi-Fi发送到云端服务器。
    3. 云端运算:服务器上庞大的语音识别模型(需要海量计算资源和存储空间)进行解码,识别出你说的是“打开风扇”。
    4. 下发指令:服务器将识别结果“打开风扇”通过网络发回给音箱。
    5. 执行:音箱控制风扇打开。
  • 离线语音模组
    1. 麦克风:采集你的声音。
    2. 本地运算:声音直接被送入模组内部的芯片。芯片里已经烧录了一个精简但高效的语音识别模型。这个模型虽然只有几百KB或几MB,但它包含了你们预设的所有指令词的声音特征。芯片内部的处理器会独立完成声音比对和识别的全过程。
    3. 直接执行:一旦识别出“打开风扇”这条指令,芯片立即通过引脚输出一个高/低电平信号,或者通过UART(串口)发送一条指令给产品的主控板,控制风扇电机启动。

总结一下:

你正在开发的这款模组,代表了终端智能化的趋势。它将原本需要云端完成的运算能力,压缩进了小小的本地芯片中。这使得产品本身就成了一个独立的、智能的节点,不依附于任何外部生态(如米家、天猫精灵),也不用看网络脸色,插电即用,说句话就响应。

三、Alexa

Alexa 是亚马逊公司推出的云端智能语音助手,你可以把它理解为亚马逊版的“小爱同学”。它通过语音交互为你提供信息、娱乐,并控制智能家居设备。

Alexa的载体:最著名的是亚马逊的 Echo 系列智能音箱(如Echo Dot、Echo Show)。此外,Sonos音箱、部分耳机、甚至一些第三方冰箱、电视也内置了Alexa。同时,你也可以在手机上安装 Alexa App 来使用它。

国内与Alexa最相似、也最主流的选择,是小爱同学、小度、天猫精灵和华为小艺这四大平台。

End

智能家居_涂鸦私有云

一、涂鸦公有云和私有云

问:是不是只有公有云上才能用涂鸦app控制,而私有云的就不能用涂鸦的app控制,而得自己再建一个app的区别吗?

答:是的,你这个问题问到点子上了,确实存在这个区别。用公有云时可以直接用涂鸦公版App,但用私有云后,必须自己建一个专属App,不能再直接用涂鸦公版App了。

为什么会有这个区别?

公有云模式:直接使用涂鸦公版App

  • 设备连接涂鸦的公有云服务器
  • 用户直接下载“涂鸦智能”或“智能生活”公版App即可控制设备
  • 公版App天然指向涂鸦公有云地址

私有云模式:需要自建品牌App

当你的设备连接到私有云后,公版App就无法直接控制了,因为:

  1. 域名指向不同:公版App写死的是涂鸦公有云的API地址,无法指向你的私有云域名
  2. 设备发现机制:私有云环境下的设备,需要通过指向你私有云地址的App才能被发现和控制
  3. 数据主权隔离:私有云的数据存储在你的服务器上,公版App没有权限访问

二、私有云:数据什么时候“离开”涂鸦

涂鸦私有云的“部署文档”非公开

完整流程是:

阶段1:登记领证(在你看来“还在涂鸦”)

  1. 你在涂鸦开发者平台创建云项目,拿到项目Code和Access ID
  2. 把这个Code交给涂鸦项目经理
  3. 涂鸦内部把这个Code和你即将部署的私有云环境进行绑定

阶段2:部署落地(数据真正到“你自己的服务器”)

  1. 你在自己的云账号(AWS/阿里云/腾讯云)上开通服务器资源
  2. 涂鸦提供Cube私有云的安装包(包含IoT核心基座、设备运营平台等)
  3. 你(或涂鸦的运维团队)在你的服务器上部署这套软件
  4. 部署完成后,你之前创建的“云项目”里的配置信息(Access ID等)被导入到你的私有云环境

阶段3:设备迁移(数据彻底独立)

  1. 设备通过OTA升级,连接地址改为你自己的私有云域名
  2. 从此,所有设备数据都流向你的服务器,不再经过涂鸦公有云

参考材料:

涂鸦 Cube(私有云)

涂鸦 Cube 智慧私有云是涂鸦打造的私有化部署解决方案,旨在为企业快速创建自有 IoT 云平台。

涂鸦私有云集成与部署 确认到底是完全的Cube私有云(所有服务在自己服务器),还是混合部署(部分微应用在本地,核心在涂鸦云)。两者运维复杂度完全不同。

三、涂鸦私有云部署

问:涂鸦私有云部署前的准备,其实就是准备服务器和域名就行了是吧?

答:可以这么理解,但还不够完整。服务器和域名是核心,但“准备服务器”背后还隐藏着一系列必须同步准备的“配套资源”。 我帮你把这些“隐藏项”也列出来,这样你心里更有底。

📦 部署私有云(Cube)的核心准备清单

你可以把“准备服务器”想象成“买了一辆车”,但要上路,还得有牌照、保险和加油站。以下是涂鸦私有云部署前你需要准备的全部内容:

准备项 具体内容 说明与来源
1. 服务器资源 云服务器(如AWS EC2、阿里云ECS)或本地物理服务器 这是计算力的核心。你需要根据涂鸦提供的《环境要求清单》来采购指定数量、配置的服务器。
2. 配套云资源 云数据库(RDS)、云存储(OSS/S3)、负载均衡(SLB/ELB)等。 一个高可用的系统不能只用本地盘。涂鸦的架构通常会依赖这些成熟的云产品来保证性能和稳定性 。这属于“服务器”之外的云资源。
3. 网络与域名 备案好的独立域名 + DNS解析服务 这是用户App和设备访问你私有云的“门牌号”。你需要准备域名,并在云服务商(如阿里云DNS、AWS Route53)完成配置 。
4. 基础软件环境 操作系统(如CentOS)、Docker环境数据库客户端等。 在服务器开通后,你需要部署好这些基础环境,以便后续安装涂鸦的软件包 。
5. 项目凭证 在涂鸦开发者平台创建云项目后获得的项目CodeApp Key/Secret 这些是连接你的私有云与涂鸦系统的“身份证”和“钥匙”,是软件部署时必须输入的 。

总结:一句话概括你的准备范围

你的准备清单 = 服务器 + 配套云资源 + 域名 + 基础软件环境 + 涂鸦项目凭证

服务器和域名确实是其中的“大头”和“门面”,但配套的云资源(数据库、存储)是保证系统稳定运行的基石,同样不可忽视。

服务器相关

电脑编号 云服务名称 角色 核心职责 你怎么“得到”它
电脑A 云服务器 (ECS) 你的工作电脑 跑涂鸦的业务软件,处理开灯、配网等具体逻辑。 在云控制台点击“购买”,选配置,然后自己登录进去装软件。
电脑B 云数据库 (RDS) 档案室专用电脑 专门运行数据库(如MySQL),存储设备列表、用户账号等结构化数据。 在云控制台点击“购买”,选规格,拿到一个内网连接地址,不需要登录。
电脑C 云存储 (OSS/S3) 仓库专用电脑 专门存放固件包、设备图片、日志文件等非结构化数据。 在云控制台点击“创建Bucket”,拿到一个内网访问域名,不需要登录。
电脑D 负载均衡 (SLB/ELB) 前台专用电脑 站在所有工作电脑前面,统一接收用户请求,再分发给空闲的工作电脑。 在云控制台点击“创建”,配置监听端口和后端服务器,拿到一个公网IP/域名,不需要登录。

四、私有云上的定制

涂鸦私有云本质上是一个商业软件产品,不是一套开源代码框架。那定制怎么做?

虽然不能往里加代码,但涂鸦私有云给你开了三扇“门”,让你在“外面”做定制:

第一扇门:OpenAPI —— 让你的服务器调用私有云能力

这是最核心的定制方式。私有云提供了数百个开放API接口,你的业务服务器可以通过这些API获取设备数据、下发指令、管理用户。

你能做的

  • 在自己的服务器上写后端代码,定时调用API拉取设备列表
  • 根据业务需求组合API,实现自己的业务逻辑
  • 开发完全自定义的管理后台、数据大屏

第二扇门:消息订阅 —— 让私有云主动推数据给你

私有云支持将设备事件(如门铃呼叫、传感器报警)通过消息网关实时推送到你指定的服务器地址。

你能做的

  • 在自己的服务器上接收实时消息,触发自己的业务流程
  • 例如:收到“门锁异常”消息 → 自动调用CRM接口创建维修工单
  • 例如:收到“孩子回家”消息 → 自动给父母手机发推送

第三扇门:App SDK —— 定制用户端界面

私有云附带完整的App SDK、UI业务包,让你在不改云端代码的前提下,开发完全属于自己品牌的App。

你能做的

  • 修改App主题色、Logo、启动图
  • 基于SDK开发新功能页面
  • 集成第三方服务(如推送、支付)

🧩 低代码开发框架 —— 接近“往里加”的体验

涂鸦还提供了 SaaS开发框架微应用体系,让你能在私有云“旁边”部署自己开发的应用模块。

开发方式 说明
SaaS开发框架 涂鸦提供的开源前后端框架,集成了用户登录、设备管理、权限控制等基础功能,你基于源码二次开发
微应用 你开发自己的微应用(例如一个能耗分析工具),部署在私有云环境中,通过框架与核心平台通信

关键点:这些应用是部署在私有云环境里,但不修改核心代码,依然通过API和消息与底层通信。

📊 总结:代码在哪里?

你想加代码的地方 能不能加 那定制怎么做
私有云核心代码 ❌ 不能 ——
自己的业务服务器 ✅ 可以 写后端代码调用API、接收消息,实现业务逻辑
App端 ✅ 可以 用App SDK开发自定义界面
管理后台前端 ✅ 可以 用SaaS开发框架或微应用开发自定义UI
数据流转逻辑 ⚠️ 通过API/消息 在你的服务器上写代码处理

一句话总结:涂鸦私有云是平台,不是代码库。你的定制代码全部跑在平台之外(你自己的服务器、你的App、你的微应用),通过平台预留的API和消息通道与核心交互。这样既保证了平台的稳定,又给了你无限的自由度。

你现在是想做什么样的定制?是业务管理后台、数据大屏,还是App功能?告诉我具体方向,我可以帮你找对应的API文档。

五、App发版:只需要上架一次吗?有新硬件不需要再发版吗?新增一个类似有摄像头的门铃的智能家居,也不需要更新app代码吗?

答案是:不需要每次都发版,大部分情况只需要“新增固件”就行,App 会自己活过来。即使新增像摄像头门铃这样复杂的设备,通常情况下也完全不需要更新你的App代码。

涂鸦 Cube(私有云)项目创建

涂鸦Cube APP创建

涂鸦产品创建

摄像头的特殊能力是平台内置的

你担心的摄像头特有功能(如视频流、云存储、门铃呼叫),其实涂鸦平台已经封装好了

摄像头特性 实现方式 是否需要改App
实时视频流 App SDK内置RTSP/WebRTC播放器 ❌ 不需要,SDK已支持
视频云存储 云端API + SDK播放器 ❌ 不需要,配置即可
门铃呼叫推送 云端消息推送 + App SDK处理 ❌ 不需要,SDK已支持
IPC设备联动 云相框主动查看IPC视频 ❌ 不需要,标准能力
双向语音 SDK内置音视频通话能力 ❌ 不需要,SDK已支持

涂鸦的物联网音视频传输能力是标准化的,App SDK已经包含了视频流服务、云存储等功能 。你在开发者平台创建产品时,只需要开启对应的功能开关,App就会自动出现对应的UI和控制逻辑。

六、自建云

建立一个像涂鸦那样的私有云,完全脱离涂鸦,是不是不现实?我的硬件设备都是自己生产的,这样会简单很多吗,还是依然很复杂,代价很大?

答案是:依然非常复杂,代价极大。 硬件自研是你的核心竞争力,它让你在产品定义、成本控制、品质管理上拥有优势。但云平台是一个完全不同的、需要巨额投入且长尾效应极强的领域。

对绝大多数硬件厂商来说,最理性的选择是:

  1. 初期/中期:用涂鸦这类成熟平台的公有云服务,把资源和精力集中在把硬件做好、把销量做上去。这是最“轻资产”、风险最低的路径。
  2. 规模化后/有特殊需求时:当设备量达到数百万级,并且你确实有数据主权、深度定制等强需求时,再考虑迁移到像涂鸦 Cube 私有云这样的方案。这样,你依然是在一个成熟的底座上构建自己的独立王国,而非在一片荒地上从零打地基。

End

智能家居核心

前言:如何实现家居的智能化?都需要模组吗?

我们先给出答案,让一个普通灯泡智能化的最简单方式是**”智能开关/智能插座”模式。这是一种极致的智能家居,根本不需要设备本身有多智能**。(详情查看下文的执行器)。

以上的灯泡其实还只是普通灯泡,只是通过智能插座,让其看起来是智能的,类似的例子还有普通电视、普通饮水机、普通电饭煲等。

那如果要让硬件本身实现智能化,就需要涉及到嵌入式软件开发。在这个过程中,一般使用模组最简单,但并不是一定需要使用模组。

两种智能家居🏠的实现路径

路径一:**”后装”智能(需要模组)**

这是目前绝大多数智能设备的实现方式,即”模组路线”。

  • 原理:传统设备(灯泡、插座、空调)本身是”哑巴”,通过嵌入一个智能模组,让它学会”说话”和”听话”。

  • 典型例子

    • 普通灯泡 + 涂鸦模组 = 智能灯泡
    • 传统空调 + 智能空调伴侣 = 可以用手机控制的空调
  • 优点:灵活,旧设备也能变智能;成本低,几十块钱就能改造一个设备

  • 适用场景:你希望把家里已有的普通电器变智能,或者开发新产品时想快速上市

路径二:**”前装”智能(无需额外模组)**

设备从设计之初,就把”智能”作为原生功能集成在主控芯片里,而不是通过外挂一个独立的模组来实现。

  • 原理:设备的主芯片本身就具备联网和智能处理能力,不需要再单独买一个”翻译官”插上去。
  • 典型例子
    • 小米/华为等品牌的旗舰大家电:比如一台小米空调,它的主板上直接集成了Wi-Fi/蓝牙芯片,不需要再外挂一个模组。
    • 苹果HomePod、Google Nest Hub:这些本身就是智能中枢,自带完整的计算和连接能力。
  • 优点:集成度更高、成本更低(少了一个独立模组的物料成本)、更稳定(减少了一个连接点)
  • 适用场景:大规模生产的成熟产品,从零开始设计的智能设备

你可能会问:这不就是模组焊死在主板上的区别吗?

不完全对。 关键区别在于:

维度 模组方案 原生集成方案
硬件架构 独立的模组(带屏蔽罩、晶振、天线)通过焊接或插拔连接 联网芯片直接集成在主板上,共用电源、时钟等资源
开发难度 低,模组厂商已经做好了最难的射频部分 高,需要自己有射频设计能力
灵活性 高,可以随时更换模组供应商 低,一旦设计定型就很难改动
认证周期 短,模组已有FCC/CE等认证 长,整机需要重新过认证
  • 模组方案 = 你们现在做的定时插座,是给普通插座加一个定时模块
  • 原生集成 = 从电路设计之初,就把定时功能和主控功能做在同一块芯片上,不需要单独的定时模

一、智能家居的基础概念

假设你想实现”人来灯亮,人走灯灭”的智能场景:

1、三大核心基础角色/智能设备

智能设备的核心定义是:能通过无线通信接收或发送指令的设备

传感器、控制器、执行器,这三个都算智能设备(都必须有模组)。

首先要先了解智能家居的三个基本角色

角色 例如 核心职责
传感器 人体传感器、门磁 感知环境的(看到人、检测温度)
控制器/中枢 智能音箱、网关、APP 发指令的(判断”有人→开灯”)
执行器 智能插座、智能开关、智能灯泡 干活的(发光、转动、通电/断电)
被执行的工具 普通灯泡、普通风扇 被动干活,听不懂指令

一个完整的智能场景,这三个角色缺一不可。但关键是:它们可以在同一个设备里,也可以分开在不同设备里。

2、智能家居的四种实现

阅读全文 →

AI Agent & SKILL

一、AI编程工具

安装开源OpenCode (SST团队),告别付费Claude Code (Anthropic)

二、Agent Skills 安装方式

介绍哪些是值得的SKILL前,我们先介绍SKILL的安装方式

1、为单agent安装(不推荐)

例:如果只为opencode安装,建议存放位置:**open ~/.config/opencode/skills**

1.1、三方的SKILL

  • 以安装 anthropics/skills 为例:

    直接告诉opencode,请安装 https://github.com/anthropics/skills,然后其会自动进行以下操作:
    下载所有 17 个技能的 SKILL.md 文件
    同步全部技能目录(含支持文件)到 .opencode/skills/
    安装 opencode-agent-skills 插件增强技能管理

  • 但安装 superpowers 的时候,其好像就不是放在 .opencode/skills/目录下

1.2、自己的SKILL:手动软链接(简单)

1
2
3
4
5
6
7
8
9
# 注意:原文件不能使用相对路径,会导致无法正确链接,及显示原身失败,必须使用绝对路径
# 注意:原文件不能使用相对路径,会导致无法正确链接,及显示原身失败,必须使用绝对路径
# 注意:原文件不能使用相对路径,会导致无法正确链接,及显示原身失败,必须使用绝对路径

# 克隆仓库后链接整个 AI-qskills 目录(必须使用绝对路径)
ln -s "/Users/用户名/Project/AI/AI-qskills" ~/.config/opencode/skills/ai-qskills

# 或者链接单个 skill
ln -s "/Users/用户名/Project/AI/AI-qskills/life-reply-crush" ~/.config/opencode/skills/life-reply-crush

2、为所有agent安装(推荐)

但是我们的SKILL需要在多个agent中使用。所以推荐使用skill管理器。

下载skill管理器 https://github.com/xingkongliang/skills-manager/releases ,并安装。

遇到右键打开,还是提示**”应用已损坏,无法打开。 你应该将它移到废纸篓。”**,则改为在终端运行以下命令

1
xattr -cr /Applications/skills-manager.app

2.1、三方的SKILL

出名的SKILL,可通过从榜单中安装,如 obra/superpowers

从榜单安装

不出名的可以通过Git安装

从Git安装

2.2、自己的SKILL

从本地安装

三、Agent Skills(智能体技能)

Agent Skills(智能体技能)介绍

skill来源:

1、Superpowers

obra/superpowers

Superpowers 构建了一个端到端的开发流程:
第一阶段:头脑风暴(brainstorming) 当你说”我想做一个 X 功能”时,AI 不会直接写代码。它会像苏格拉底式对话一样,一次问一个问题,帮你厘清真正的需求。设计方案分 200-300 字的小节呈现,每节都要你确认”看起来对吗”。
第二阶段:工作区隔离(using-git-worktrees) 设计确认后,AI 会创建一个新的 git 分支和 worktree,确保开发环境隔离,不污染主分支。
第三阶段:编写计划(writing-plans) 把设计拆解成 2-5 分钟的小任务。每个任务都有精确的文件路径、完整的代码片段、验证步骤。目标是让”一个没有判断力、没有项目背景、讨厌写测试的热情初级工程师”也能执行。
第四阶段:子代理驱动开发(subagent-driven-development) 这是 v4.0 的重大创新。主代理为每个任务派遣一个”新鲜”的子代理(没有上下文污染),实现任务后进行两阶段审查:
规格符合性审查:代码是否完全符合需求规格?是否多做了?少做了?
代码质量审查:只有规格审查通过后才进行。检查代码是否干净、测试覆盖是否足够
审查是循环的:发现问题→修复→再审查,直到通过。
第五阶段:收尾(finishing-a-development-branch) 所有任务完成后,验证测试、呈现选项(合并/创建 PR/保留/丢弃),清理 worktree。

2、anthropics/skills 建议全装

2.1. anthropics/skill-creator

2.2. anthropics/frontend-design

2.3. anthropics/docx 、 anthropics/xlsx 、anthropics/pptx

不装的话,到时候生成文档95%以上会出问题

3、find-skills — 帮助发现和安装 agent skills

4、planning-with-file 选择其中的 planning-with-file-en 即可

5、ui-ux-pro-max-skill vs frontend-design

5.1 区别

frontend-design 理论上可以生成完整页面,但它的设计哲学决定了它并不擅长做这件事,或者说,它“不想”做这件事。

它的设计哲学是:“一页有一个惊艳的焦点就够了,其他地方保持克制”

问题 说明
风格一致性风险 frontend-design 每次生成都倾向于“选一个惊艳的方向并贯彻到底”。如果让它一次性生成整个页面,它会为每个模块都注入强烈个性,容易导致页面“每个地方都在抢眼”,缺乏视觉节奏和层次感。
整页生成质量不稳定 第三方评测显示,frontend-design 在“实现可执行性”上的评分偏低(50%),因为它的指导偏“设计哲学宣言”而非“可执行的代码模式”。换句话说,它更擅长讲“怎么做才美”,而不是“怎么写才完整”。
模块扩展能力有限 当你让它“扩展现有Hero到整个页面”时,它需要保持Hero的独特性同时补充其他模块。这不是它的设计初衷——它更适合“从零创造一个惊艳的Hero”,而非“基于已有风格补充剩余部分”。
对比维度 frontend-design ui-ux-pro-max-skill
擅长的事 创造惊艳的视觉焦点(Hero区域) 生成一整套完整的设计系统
整页生成能力 有,但容易风格过载 强,自带“行业规范+完整模块”模板
风格一致性保障 依赖单次prompt的发挥 通过设计系统强制执行
模块扩展 需要手动保持风格统一 自动分析已有风格并补全
适用场景 独立页面/核心视觉模块 完整页面/多页面项目

5.2 正确工作流的逻辑链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. frontend-design 生成惊艳的 Hero(确定“视觉灯塔”)

2. 把 Hero 代码给 ui-ux-pro-max(让它“抄作业”)

3. ui-ux-pro-max 分析 Hero 的风格特征
(颜色、字体、圆角、间距、动效模式)

4. ui-ux-pro-max 基于分析结果,匹配行业规则
(金融/医疗/宠物服务等)

5. ui-ux-pro-max 生成剩余模块
(服务展示、评价、预约表单、页脚等)

6. 最终:Hero 保持惊艳 + 全页面风格统一 = 成品

核心洞察frontend-design 负责“定调”,ui-ux-pro-max 负责“铺开”。前者是创意总监,后者是执行团队。

5.3 使用举例

场景:为一家高端宠物店设计预约网站

假设我们的目标是让用户预约“猫咪洗护”或“狗狗美容”服务。

方案一:组合拳 (先frontend-design,后ui-ux-pro-max)

这个方案的目的是先定惊艳调性,再补全整体结构

步骤描述 直接对AI说: 预期效果
1 frontend-design 打造视觉焦点 “请使用 frontend-design 技能,为一家高端宠物美容店的官网首页设计Hero区域。需要采用柔和、有机的“Biophilic Design”(亲生物设计) 风格,使用圆润的Organic Shapes(有机形态)和温暖的 earth tone(大地色系),重点突出“预约”按钮。” AI会为你生成一个极具高级感和亲和力的头部区域,视觉冲击力强,足以让用户记住这个品牌。
2 ui-ux-pro-max 扩展为完整页面(在Hero代码生成后) “很好,请保留上面的Hero风格,现在调用 ui-ux-pro-max 技能,帮我把这个页面扩展为完整的官网首页。需要补全以下模块:服务项目展示(猫咪洗护、狗狗美容)、优势特点、用户评价、页脚。这是一个宠物服务行业,请确保整体设计系统与Hero部分100%统一。” AI会分析第一步Hero代码中的色彩、圆角、字体等风格要素,并以此为基础生成一套完整的设计系统,然后用于搭建整个页面。最终页面既有惊艳的头部,又有规整、专业的下半部分。
方案二:单独使用 (仅用 ui-ux-pro-max)

如果你想让AI直接从零生成一个结构完整、风格正确的页面,可以直接这样问:

“请使用 ui-ux-pro-max 技能,为一个宠物美容服务设计完整的官网首页。风格要求‘活泼、亲和’,需要包含Hero区、服务项目、关于我们和预约按钮,使用HTML+Tailwind实现。”

你会看到:AI会先进行一系列“思考”,调用内部的搜索脚本,精确匹配“宠物服务”行业的推荐方案,然后生成一个配色(如温暖的奶油色、浅木色)、布局和字体都非常专业且符合行业预期的完整页面。你可能还会在回复中看到类似“正在搜索产品类型‘pet service’…”的过程信息。它的整体完成度很高,但第一个Hero区域的惊艳感可能不如frontend-design单独产出的效果

6、agent-browser 浏览器自动化

agent-browser

1
npx skills add vercel-labs/agent-browser@agent-browser -g -y

7、研发场景十大热门 Skill 推荐

8、其他

self-improving-agent 自我进化

NotebookLM

三、使用示例

1、个人知识库

个人知识库AnyThingLLM

End

结婚相关常识

相关事项

image-20250729180443948 image-20250729180646331

婚纱照+镜头

婚纱照

1、看客片(不要看样片),让客服发原片和样片的对比图?

2、看差评高频词是否是你的雷区?

底片:底片多少张、是否全送;是要双格式的吗?拍完多久选片?

精修:精修多少张,是否知道满意为止?精修是否全部入册,精修完多久拿到产品?

相册相框:材质、尺寸、成品是否包邮?

服装:男女各几服几造?是否可以试纱,要试纱费吗?是否分区收费(避免选衣服时被推销升级),全场任选?无清洗费?

内景外景:选几个?有需要什么费用吗?一套衣服是否可以拍摄多个场景?

拍摄:是否一对一?摄化老师什么等级(是否可以指定)?

改期:恶劣天气是否可以改期?自己原因改期,是否需要支付费用?

重拍:拍摄不满意是否免费重拍?选片不满意是否免费重拍?

定金付多少(是否可退,一般不可退),尾款什么时候付?

合同

拒绝口头承诺。

一定写上”无二次消费”。

订单备注:

1.全场服装通选,免清费

2.尾款选片确认不重拍后付
3.拍不满意免费重拍,重拍不满意退全款;选片时会有试修
4、全程一对一服务

5、全程无任何隐行消费,半自助选片,自愿加片

是否赠送结婚登记照,并且精修打印?

其他

出行:车费、午餐费用

门票(场地费用)

化妆品

道具:鲜花(可能要自己购买)、或者问清楚是否提供仿真花

镜头

image-20250729180157992

婚车

用自家车/朋友车组成车队,或租一辆喜欢的头车(尾车用普通车型)

婚房

布置

装饰画、立体囍子摆件、喜庆抱枕、柔软毛毯

流水席

厨师

食材选购厨师会负责

表演?

邀请

邀请人员

男方宾客:

女方宾客:

伴郎:

伴娘:

请帖请柬

电子请柬:可嵌入导航、祝福语

纸质请柬:仅给长辈准备少量纸质请柬,其余电子化

伴手礼

方式1(吃):简约布袋(2元/个)、宾客更在意巧克力是否好吃

方式2(用):杯子

业务中的算法

问题背景

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

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

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