mcpField
U
ui-builder-patterns

ui-builder-patterns

github.com/serac-labs设计与 UI7763 次安装9 周前更新
代码仓库

关于 ui-builder-patterns

面向 ServiceNow UI Builder 的技能,用于构建 Next Experience 工作区和应用。内容讲解页面结构、数据代理、客户端状态、事件、组件和可复用宏组件,并介绍创建与修改 UI Builder 页面的工具。

主要功能

  1. 01页面与变体结构
  2. 02GraphQL、Script、REST 和 Transform 数据代理
  3. 03客户端状态参数
  4. 04组件事件与处理器
  5. 05可复用宏组件
  6. 06工作区与页面创建工具

适用场景

  1. 01构建事件列表和表单页面
  2. 02通过客户端状态与事件连接组件
  3. 03创建可复用 UI 构建模块
  4. 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包含组件和逻辑的可复用容器
ComponentUI 构建模块(列表、表单、按钮)
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,
})

最佳实践

  1. 使用数据代理 - 切勿直接在组件中获取数据
  2. 使用客户端状态管理 UI - 用于筛选器、选择项和视图模式
  3. 使用事件进行通信 - 通过事件解耦组件
  4. 使用 Macroponent 实现复用 - 创建可复用的构建模块
  5. 使用 GraphQL 查询 - 比 Script 数据代理更高效
  6. 检查结构 - 部署前手动验证页面布局和绑定
  7. 移动端变体 - 创建响应式变体
  8. 无障碍访问 - 遵循 WCAG 指南

评论

支持 Markdown