关于 ui-builder-patterns
面向 ServiceNow UI Builder 的技能,用于构建 Next Experience 工作区和应用。内容讲解页面结构、数据代理、客户端状态、事件、组件和可复用宏组件,并介绍创建与修改 UI Builder 页面的工具。
主要功能
- 01页面与变体结构
- 02GraphQL、Script、REST 和 Transform 数据代理
- 03客户端状态参数
- 04组件事件与处理器
- 05可复用宏组件
- 06工作区与页面创建工具
适用场景
- 01构建事件列表和表单页面
- 02通过客户端状态与事件连接组件
- 03创建可复用 UI 构建模块
- 04为页面数据配置数据代理
MCP 相关
文档介绍用于创建页面、添加元素、配置数据代理、客户端状态和事件、构建工作区及更新页面的 MCP 工具。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ serac-labs/ serac --skill ui-builder-patterns安装后在 Claude Code 中输入/ui-builder-patterns确认已加载。
详细介绍
ServiceNow 的 UI Builder 模式
UI Builder(UIB)是 ServiceNow 用于构建 Next Experience 工作区和应用程序的现代框架。
UI Builder 架构
组件层级
UX Application
└── App Shell
└── Chrome (Header, Navigation)
└── Pages
└── Variants
└── Macroponents
└── Components
└── Elements
核心概念
| 概念 | 描述 |
|---|---|
| Macroponent | 包含组件和逻辑的可复用容器 |
| Component | UI 构建模块(列表、表单、按钮) |
| Data Broker | 为组件获取和管理数据 |
| Client State | 页面级状态管理 |
| Event | 组件之间的通信 |
页面结构
页面剖析
Page: incident_list
├── Variants
│ ├── Default (desktop)
│ └── Mobile
├── Data Brokers
│ ├── incident_data (GraphQL)
│ └── user_preferences (Script)
├── Client States
│ ├── selectedRecord
│ └── filterActive
├── Events
│ ├── RECORD_SELECTED
│ └── FILTER_APPLIED
└── Layout
├── Header (macroponent)
├── Sidebar (macroponent)
└── Content (macroponent)
数据代理
数据代理类型
| 类型 | 用途 | 示例 |
|---|---|---|
| GraphQL | 表查询 | Incident 列表 |
| Script | 复杂逻辑 | 计算指标 |
| REST | 外部 API | 天气数据 |
| Transform | 数据处理 | 格式化日期 |
GraphQL 数据代理
// Data Broker: incident_list
// Type: GraphQL
// Query
query ($limit: Int, $query: String) {
GlideRecord_Query {
incident(
queryConditions: $query
limit: $limit
) {
number { value displayValue }
short_description { value }
priority { value displayValue }
state { value displayValue }
assigned_to { value displayValue }
sys_id { value }
}
}
}
// Variables (from client state or props)
{
"limit": 50,
"query": "active=true"
}
Script 数据代理(ES5)
// Data Broker: incident_metrics
// Type: Script
;(function execute(inputs, outputs) {
var result = {
total: 0,
byPriority: {},
avgAge: 0,
}
var gr = new GlideRecord("incident")
gr.addQuery("active", true)
gr.query()
var totalAge = 0
while (gr.next()) {
result.total++
// Count by priority
var priority = gr.getValue("priority")
if (!result.byPriority[priority]) {
result.byPriority[priority] = 0
}
result.byPriority[priority]++
// Calculate age
var opened = new GlideDateTime(gr.getValue("opened_at"))
var now = new GlideDateTime()
var age = gs.dateDiff(opened, now, true)
totalAge += parseInt(age)
}
if (result.total > 0) {
result.avgAge = Math.round(totalAge / result.total / 3600) // hours
}
outputs.metrics = result
})(inputs, outputs)
客户端状态参数
定义客户端状态
// Page Client State Parameters
{
"selectedIncident": {
"type": "string",
"default": ""
},
"filterQuery": {
"type": "string",
"default": "active=true"
},
"viewMode": {
"type": "string",
"default": "list",
"enum": ["list", "card", "split"]
},
"selectedRecords": {
"type": "array",
"items": { "type": "string" },
"default": []
}
}
在组件中使用客户端状态
// In component configuration
{
"query": "@state.filterQuery",
"selectedItem": "@state.selectedIncident"
}
// Updating client state via event
{
"eventName": "NOW_RECORD_LIST#RECORD_SELECTED",
"handlers": [
{
"action": "UPDATE_CLIENT_STATE",
"payload": {
"selectedIncident": "@payload.sys_id"
}
}
]
}
事件和处理程序
事件类型
| 事件 | 触发条件 | 载荷 |
|---|---|---|
NOW_RECORD_LIST#RECORD_SELECTED | 点击行 | { sys_id, table } |
NOW_BUTTON#CLICKED | 点击按钮 | { label } |
NOW_DROPDOWN#SELECTED | 下拉列表更改 | { value } |
CUSTOM#EVENT_NAME | 自定义事件 | 自定义载荷 |
事件处理程序配置
// Event: Record Selected
{
"eventName": "NOW_RECORD_LIST#RECORD_SELECTED",
"handlers": [
{
"action": "UPDATE_CLIENT_STATE",
"payload": {
"selectedIncident": "@payload.sys_id"
}
},
{
"action": "REFRESH_DATA_BROKER",
"payload": {
"dataBrokerId": "incident_details"
}
},
{
"action": "DISPATCH_EVENT",
"payload": {
"eventName": "INCIDENT_SELECTED",
"payload": "@payload"
}
}
]
}
客户端脚本事件处理程序(ES5)
// Client Script for custom event handling
;(function (coeffects) {
var dispatch = coeffects.dispatch
var state = coeffects.state
var payload = coeffects.action.payload
// Custom logic
var selectedId = payload.sys_id
// Update multiple states
dispatch("UPDATE_CLIENT_STATE", {
selectedIncident: selectedId,
detailsVisible: true,
})
// Conditional dispatch
if (payload.priority === "1") {
dispatch("DISPATCH_EVENT", {
eventName: "CRITICAL_INCIDENT_SELECTED",
payload: payload,
})
}
})(coeffects)
组件配置
常用组件
| 组件 | 用途 | 关键属性 |
|---|---|---|
now-record-list | 数据表 | columns, query, table |
now-record-form | 记录表单 | table, sysId, fields |
now-button | 操作按钮 | label, variant, icon |
now-card | 卡片容器 | header, content |
now-tabs | 选项卡容器 | tabs, activeTab |
now-modal | 模态对话框 | opened, title |
记录列表配置
{
"component": "now-record-list",
"properties": {
"table": "incident",
"query": "@state.filterQuery",
"columns": [
{ "field": "number", "label": "Number" },
{ "field": "short_description", "label": "Description" },
{ "field": "priority", "label": "Priority" },
{ "field": "state", "label": "State" },
{ "field": "assigned_to", "label": "Assigned To" }
],
"pageSize": 20,
"selectable": true,
"selectedRecords": "@state.selectedRecords"
}
}
表单配置
{
"component": "now-record-form",
"properties": {
"table": "incident",
"sysId": "@state.selectedIncident",
"fields": ["short_description", "description", "priority", "assignment_group", "assigned_to"],
"readOnly": false
}
}
Macroponent
创建可复用的 Macroponent
Macroponent: incident-summary-card
├── Properties (inputs)
│ ├── incidentSysId (string)
│ └── showActions (boolean)
├── Internal State
│ └── expanded (boolean)
├── Data Broker
│ └── incident_data (uses incidentSysId)
└── Layout
├── now-card
│ ├── Header: @data.incident.number
│ ├── Content: @data.incident.short_description
│ └── Footer: Action buttons
└── now-modal (if expanded)
Macroponent 属性
{
"properties": {
"incidentSysId": {
"type": "string",
"required": true,
"description": "Sys ID of incident to display"
},
"showActions": {
"type": "boolean",
"default": true,
"description": "Show action buttons"
},
"variant": {
"type": "string",
"default": "default",
"enum": ["default", "compact", "detailed"]
}
}
}
MCP 工具集成
可用的 UIB 工具
| 工具 | 用途 |
|---|---|
snow_uib_page_manage (action='create') | 创建新页面 |
snow_uib_page_manage (action='add_element') | 向页面添加组件 |
snow_create_uib_data_broker | 创建数据代理 |
snow_create_uib_client_state | 定义客户端状态 |
snow_create_uib_event | 配置事件 |
snow_create_complete_workspace | 完整工作区 |
snow_record_manage (action='update') | 修改页面 |
示例工作流
// 1. Create workspace
await snow_create_complete_workspace({
name: "IT Support Workspace",
description: "Agent workspace for IT support",
landing_page: "incident_list",
})
// 2. Create data broker
await snow_create_uib_data_broker({
page_id: pageId,
name: "incident_list",
type: "graphql",
query: incidentQuery,
})
// 3. Add components
await snow_uib_page_manage({
action: "add_element",
page_id: pageId,
component: "now-record-list",
properties: listConfig,
})
// 4. Configure events
await snow_create_uib_event({
page_id: pageId,
event_name: "NOW_RECORD_LIST#RECORD_SELECTED",
handlers: eventHandlers,
})
最佳实践
- 使用数据代理 - 切勿直接在组件中获取数据
- 使用客户端状态管理 UI - 用于筛选器、选择项和视图模式
- 使用事件进行通信 - 通过事件解耦组件
- 使用 Macroponent 实现复用 - 创建可复用的构建模块
- 使用 GraphQL 查询 - 比 Script 数据代理更高效
- 检查结构 - 部署前手动验证页面布局和绑定
- 移动端变体 - 创建响应式变体
- 无障碍访问 - 遵循 WCAG 指南
评论
支持 Markdown