Appearance
UI构建规范
本文档用于指导 AI、代码生成器或脚本生成器创建可被前端运行页识别的动态 UI。
目标:
- 生成合法的 Lua UI 构建代码。
- 保持旧 UI 兼容。
- 正确使用布局组件。
- 正确设置
name/ key,保证参数可以被UICurrentValue和onUIChanged(values)读取。
基础规则
动态 UI 最终由一个组件数组构成:
lua
local elements = {
ui.createInput("name", "名称", "请输入名称", ""),
ui.createSlider("speed", 50, "速度", 0, 100, 1)
}
ui.buildUI(elements, 1)
StartUI()ui.buildUI(elements, updateMode) 的第二个参数:
0:强制使用代码里的默认值覆盖当前 UI。1:更新 UI 结构,同时尽量保留用户已经修改过的值。推荐使用。
前端点击“保存配置”会写入 ui.json。正在运行的 Lua 进程不会因为保存按钮重新执行初始化代码;验证保存后的默认值时,需要停止脚本后重新运行。
节点结构
每个动态 UI 节点必须是“单组件键对象”:
lua
{
Switch = {
name = "enableFeature",
title = "启用功能",
isSelected = true
}
}对应 JSON 结构:
json
{
"Switch": {
"name": "enableFeature",
"title": "启用功能",
"isSelected": true
}
}AI 生成时必须遵守:
- 一个节点只放一个组件类型。
- 输入型组件必须设置取值名。
- 容器/布局/展示组件不要设置业务取值名。
- 制作任何动态 UI 时必须考虑大小屏幕适配。这是硬性要求,不是可选优化。
- 默认按小屏优先生成布局,避免窄弹窗或侧栏中溢出。
- 短输入组件推荐使用
layout = { span = 6, lg = 3 },实现小屏最多 2 列、大屏最多 4 列。 - 长组件、说明文字、图片、Alert、Slider 和复杂分组默认使用
layout = { span = 12 }。 - 不要生成事件函数字符串,例如
onClick = "xxx"。
支持组件
| 组件 | 类型 | 是否产生值 | 取值字段 |
|---|---|---|---|
Input | 输入 | 是 | defaultValue |
NumberInput | 输入 | 是 | defaultValue |
Slider | 输入 | 是 | defaultValue |
Select | 输入 | 是 | defaultSelectedKeys |
Autocomplete | 输入 | 是 | defaultSelectedKey |
Checkbox | 输入 | 是 | defaultSelected |
Switch | 输入 | 是 | isSelected |
CheckboxGroup | 输入 | 是 | defaultValue |
RadioGroup | 输入 | 是 | defaultValue |
Alert | 展示 | 否 | - |
Chip | 展示 | 否 | - |
Divider | 展示 | 否 | - |
Image | 展示 | 否 | - |
P | 展示 | 否 | - |
Div | 布局 | 否 | - |
Group | 布局 | 否 | - |
Tabs | 容器 | 否 | - |
Accordion | 容器 | 否 | - |
取值规则
输入组件的取值名来自:
ui.createXxx(key, ...)的第一个参数。- 手写节点中的
name字段。
示例:
lua
local input = ui.createInput("username", "用户名", "请输入", "")
local enable = {
Switch = {
name = "enableFeature",
title = "启用功能",
isSelected = true
}
}取值:
lua
function onUIChanged(values)
if values.username ~= nil then
print(values.username)
end
if values.enableFeature ~= nil then
print(values.enableFeature)
end
end
function readCurrent()
print(UICurrentValue.username)
print(UICurrentValue.enableFeature)
endAI 必须保证:
- 所有需要取值的组件都有唯一的 key /
name。 - 不同输入组件不要重复使用同一个 key /
name。 Div、Group、P、Tabs、Accordion自身不作为业务参数读取;它们内部的输入组件仍按自己的 key /name取值。onUIChanged(values)收到的是当前 UI 的完整参数表,可以读取任意输入组件当前值。- 回调外、主循环或其它函数中主动读取当前参数时,使用
UICurrentValue.xxx。
各组件返回值类型
| 组件 | 示例取值 | 返回值 |
|---|---|---|
Input | values.username | 字符串 |
NumberInput | values.count | 数字 |
Slider 单值 | values.speed | 数字 |
Slider 范围 | values.range | 数字数组 |
Select 单选 | values.mode | 字符串 |
Select 多选 | values.multiMode | 逗号分隔字符串 |
Autocomplete | values.project | 字符串或 nil |
Checkbox | values.enableLog | 布尔值 |
Switch | values.enableFeature | 布尔值 |
CheckboxGroup | values.fruits | 字符串数组 |
RadioGroup | values.priority | 字符串 |
布局规则
旧 UI 默认垂直排列。不要为了旧 UI 主动添加布局字段。
需要横向或栅格布局时使用:
lua
component.ComponentName.layout = { span = 6 }常用 span:
12:占满一行。6:半行,两个组件一行。4:三分之一行,三个组件一行。3:四分之一行,四个组件一行。
响应式宽度:
span:默认宽度,也是小屏宽度。sm:容器宽度大于等于 480px 时使用。md:容器宽度大于等于 768px 时使用。lg:容器宽度大于等于 1024px 时使用。xl:容器宽度大于等于 1280px 时使用。
推荐写法:小屏最多 2 列,大屏最多 4 列。
lua
nameInput.Input.layout = {
span = 6, -- 小屏:半行,一行最多 2 个
lg = 3, -- 大屏:四分之一行,一行最多 4 个
}长输入、说明文字、Slider 通常保持整行:
lua
speedSlider.Slider.layout = { span = 12 }
description.P.layout = { span = 12 }示例:
lua
local nameInput = ui.createInput("name", "名称", "请输入", "")
nameInput.Input.layout = { span = 6 }
local countInput = ui.createNumberInput("count", 1, "数量", 0, 100, 1, "1~100")
countInput.NumberInput.layout = { span = 6 }条件显示和禁用
输入组件和布局组件可以使用条件字段,让一个组件根据另一个组件的值改变状态。
支持字段:
visibleWhen:条件满足时显示,否则隐藏。hiddenWhen:条件满足时隐藏。disabledWhen:条件满足时禁用。用于Input、NumberInput、Select、Autocomplete、Slider、Checkbox、Switch、CheckboxGroup、RadioGroup等输入组件。
条件写法:
lua
component.Input.visibleWhen = {
field = "enableAdvanced",
equals = true,
}可用判断:
equals/value:等于指定值。notEquals:不等于指定值。in:当前值在数组内。notIn:当前值不在数组内。truthy = true:值为真。falsy = true:值为空、false 或 nil。exists = true:字段存在且不为 nil。
示例:开关控制高级参数禁用。
lua
local enableAdvanced = {
Switch = {
name = "enableAdvanced",
title = "启用高级设置",
isSelected = false,
layout = { span = 6, lg = 3 },
}
}
local threshold = ui.createSlider("threshold", 75, "识别阈值", 0, 100, 1)
threshold.Slider.layout = { span = 12, lg = 6 }
threshold.Slider.disabledWhen = {
field = "enableAdvanced",
equals = false,
}示例:调试模式开启后才显示备注输入框。
lua
local debugText = ui.createInput("debugText", "调试备注", "请输入备注", "")
debugText.Input.layout = { span = 12 }
debugText.Input.visibleWhen = {
field = "debugMode",
equals = true,
}示例:下拉框选择不同模式时显示不同配置区。
lua
local modeItems = {
{ key = "normal", label = "普通模式" },
{ key = "advanced", label = "高级模式" },
{ key = "custom", label = "自定义模式" },
}
local modeSelect = ui.createSelect("mode", "normal", "运行模式", modeItems)
modeSelect.Select.layout = { span = 6, lg = 3 }
local advancedGroup = {
Group = {
title = "高级参数",
description = "只有运行模式为 advanced 时显示。",
direction = "grid",
columns = 12,
gap = 4,
visibleWhen = {
field = "mode",
equals = "advanced",
},
item = {
{ ui.createSlider("advancedSpeed", 50, "高级速度", 0, 100, 1) },
},
layout = { span = 12 },
}
}
local customInput = ui.createInput("customValue", "自定义参数", "请输入", "")
customInput.Input.layout = { span = 12 }
customInput.Input.visibleWhen = {
field = "mode",
equals = "custom",
}示例:勾选某个多选项后显示提示。
lua
local featureItems = {
{ key = "log", label = "日志" },
{ key = "notify", label = "通知" },
{ key = "safe", label = "安全模式" },
}
local features = ui.createCheckboxGroup("features", {"log"}, "功能选项", featureItems)
features.CheckboxGroup.orientation = "horizontal"
features.CheckboxGroup.layout = { span = 12 }
local safeTip = {
Alert = {
title = "安全模式已启用",
description = "启用后部分高风险参数会被限制。",
color = "warning",
visibleWhen = {
field = "features",
in = {"safe"},
},
layout = { span = 12 },
}
}示例:开启锁定后禁用整个分组。
lua
local lockSwitch = {
Switch = {
name = "lockConfig",
title = "锁定配置",
isSelected = false,
layout = { span = 6, lg = 3 },
}
}
local configGroup = {
Group = {
title = "可编辑配置",
direction = "grid",
columns = 12,
gap = 4,
disabledWhen = {
field = "lockConfig",
equals = true,
},
item = {
{ ui.createInput("name", "名称", "请输入", "") },
{ ui.createNumberInput("count", 1, "数量", 0, 100, 1, "1~100") },
},
layout = { span = 12 },
}
}示例:值不存在时显示初始化提示,存在后隐藏。
lua
local initTip = {
Alert = {
title = "请先选择项目",
description = "选择项目后会显示后续配置。",
color = "primary",
visibleWhen = {
field = "project",
falsy = true,
},
layout = { span = 12 },
}
}
local projectConfig = {
Group = {
title = "项目配置",
visibleWhen = {
field = "project",
exists = true,
},
item = {
{ ui.createInput("projectName", "项目名称", "请输入", "") },
},
layout = { span = 12 },
}
}注意:隐藏或禁用只影响界面显示和交互,不要把 Div、Group、P 当作业务参数读取。真正取值仍然读取内部输入组件自己的 name。
Group 布局
Group 是纯布局组件,不产生值。
推荐用于明确控制一组组件的排列方式。
lua
local group = {
Group = {
title = "基础参数",
description = "这里的 Group 只负责布局,不参与取值。",
direction = "grid",
columns = 12,
gap = 4,
item = {
{ nameInput, countInput }
}
}
}direction 可选值:
"vertical":从上到下排列。"horizontal":横向排列,空间不足自动换行。"grid":栅格布局,配合layout.span。
AI 推荐:
- 表单区域使用
direction = "grid"。 - 开关、标签、短选项使用
direction = "horizontal"。 - 普通说明区使用
direction = "vertical"。
Div 和 P
Div 是通用布局容器,不产生值。
lua
local card = {
Div = {
title = "配置区域",
description = "这个容器只负责外观。",
className = "rounded-small border border-default-200 p-4",
item = {
{ nameInput },
{ countInput }
},
layout = { span = 12 }
}
}P 是段落文本,不产生值。
lua
local tip = {
P = {
text = "请先填写参数,再点击运行。",
className = "text-sm text-default-500",
layout = { span = 12 }
}
}常用 className:
"text-sm text-default-500":普通说明文字。"text-sm text-warning font-medium":警告文字。"rounded-small border border-default-200 p-4":普通卡片。"rounded-small bg-default-50 p-4":浅色区域。
Tabs 和 Accordion
Tabs 和 Accordion 是容器组件,不产生业务值。
内部输入组件正常取值。
lua
local tabs = ui.createTabs({
ui.createTab("基础设置", {
{ nameInput, countInput }
}),
ui.createTab("高级设置", {
{ speedSlider }
})
}, "top", true)左侧导航式 Tabs:
lua
local tabs = ui.createTabs({
ui.createTab("总览", {
{ ui.createAlert("提示", "总览内容", "info") }
}),
ui.createTab("参数", {
{ nameInput, countInput }
})
}, "start", false)
tabs.Tabs.variant = "solid"
tabs.Tabs.color = "primary"Accordion:
lua
local panel = ui.createAccordion({
ui.createTab("基础设置", {
{ nameInput }
}),
ui.createTab("高级设置", {
{ speedSlider }
})
})
panel.Accordion.variant = "splitted"
panel.Accordion.selectionMode = "multiple"
panel.Accordion.defaultSelectedKeys = {"基础设置"}选项结构
Select、Autocomplete、CheckboxGroup、RadioGroup 使用相同选项格式:
lua
local items = {
{ label = "自动", key = "auto", description = "按默认策略运行" },
{ label = "手动", key = "manual" },
{ label = "调试", key = "debug", isDisabled = true },
}字段说明:
label:显示文字。key:实际取值。description:说明文字,可选。isDisabled:是否禁用,可选。
推荐完整模板
lua
local ui = require("ui_builder")
local modeItems = {
{ label = "自动", key = "auto", description = "按默认策略运行" },
{ label = "手动", key = "manual" },
}
local nameInput = ui.createInput("name", "名称", "请输入名称", "")
nameInput.Input.variant = "bordered"
nameInput.Input.layout = { span = 6 }
local countInput = ui.createNumberInput("count", 1, "数量", 0, 100, 1, "1~100")
countInput.NumberInput.variant = "bordered"
countInput.NumberInput.layout = { span = 6 }
local modeSelect = ui.createSelect("mode", "auto", "模式", modeItems)
modeSelect.Select.variant = "bordered"
modeSelect.Select.layout = { span = 12 }
local enableSwitch = {
Switch = {
name = "enableFeature",
title = "启用功能",
isSelected = true,
color = "primary",
layout = { span = 12 }
}
}
local page = {
Div = {
className = "rounded-small border border-default-200 p-4",
item = {
{
{
P = {
text = "这是 AI 生成的动态 UI 示例。",
className = "text-sm text-default-500",
layout = { span = 12 }
}
}
},
{
{
Group = {
title = "基础参数",
direction = "grid",
columns = 12,
gap = 4,
item = {
{ nameInput, countInput },
{ modeSelect },
{ enableSwitch }
}
}
}
}
}
}
}
ui.buildUI({ page }, 1)
StartUI()
function onUIChanged(values)
if values.name ~= nil then
print("名称变化:", values.name)
end
if values.count ~= nil then
print("数量变化:", values.count)
end
if values.mode ~= nil then
print("模式变化:", values.mode)
end
if values.enableFeature ~= nil then
print("启用功能变化:", values.enableFeature)
end
end
function readCurrentValues()
print(UICurrentValue.name)
print(UICurrentValue.count)
print(UICurrentValue.mode)
print(UICurrentValue.enableFeature)
end禁止事项
AI 生成时不要做这些事:
- 不要给
Div、Group、P、Tabs、Accordion生成业务name。 - 不要从
values.Div、values.Group、values.P取值。 - 不要把
onUIChanged(values)当成增量变化表;它是全量当前值表。 - 不要重复使用相同的输入组件 key /
name。 - 不要生成事件字符串,例如
onClick = "run()"。 - 不要把多个组件类型写进同一个节点。
- 不要假设
Select多选返回数组;当前返回逗号字符串。
取值检查清单
生成 UI 后,AI 应检查:
- 每个需要读取的输入组件都有唯一 key /
name。 onUIChanged(values)中可以读取全量当前值;读取可选字段时仍建议用~= nil判断。UICurrentValue.xxx中的xxx对应真实输入组件。- 布局组件只负责排版,不参与取值。
- 嵌套在
Tabs、Accordion、Group、Div内的输入组件仍按自己的 key /name取值。