mcpField
M
mobile-development

mobile-development

github.com/serac-labs开发7758 次安装10 周前更新
代码仓库

关于 mobile-development

面向 ServiceNow 移动开发的技能,介绍移动应用、屏幕、卡片、通知、离线规则和操作的配置,并为围绕 ServiceNow 记录构建移动体验提供 ES5 示例。

主要功能

  1. 01移动应用和屏幕配置
  2. 02列表卡片设置与操作
  3. 03推送通知配置
  4. 04离线同步规则与队列处理
  5. 05移动端专用操作

适用场景

  1. 01配置 IT 支持移动应用
  2. 02在移动列表中展示事件记录
  3. 03通知用户已分配的事件
  4. 04通过离线数据支持工作

MCP 相关

介绍用于查询移动配置、测试移动脚本和查找配置项目的 MCP 工具。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/serac-labs/serac --skill mobile-development

安装后在 Claude Code 中输入/mobile-development确认已加载。

详细介绍

ServiceNow 移动开发

移动开发支持打造具备离线功能的原生移动体验。

移动架构

Mobile App Configuration
    ├── App Screens
    │   ├── List Views
    │   ├── Detail Views
    │   └── Card Builders
    ├── Push Notifications
    ├── Offline Rules
    └── Mobile Actions

关键表

表用途
sys_sg_mobile_app移动应用配置
sys_sg_screen移动屏幕
sys_sg_card_builder卡片构建器
sys_push_notification推送配置
sys_sg_offline_rule离线规则

移动应用配置(ES5)

创建移动应用
// Create mobile app configuration (ES5 ONLY!)
var app = new GlideRecord("sys_sg_mobile_app")
app.initialize()

app.setValue("name", "IT Support")
app.setValue("description", "Mobile app for IT support tasks")

// App settings
app.setValue("active", true)
app.setValue("version", "1.0.0")

// Branding
app.setValue("primary_color", "#1976D2")
app.setValue("secondary_color", "#FFFFFF")
app.setValue("icon", "attachment_sys_id")

// Default screen
app.setValue("home_screen", homeScreenSysId)

// Roles
app.setValue("roles", "itil")

app.insert()
配置移动屏幕
// Create mobile screen (ES5 ONLY!)
var screen = new GlideRecord("sys_sg_screen")
screen.initialize()

screen.setValue("name", "My Incidents")
screen.setValue("mobile_app", mobileAppSysId)
screen.setValue("type", "list") // list, record, custom

// Data source
screen.setValue("table", "incident")
screen.setValue("filter", "assigned_to=javascript:gs.getUserID()^active=true")

// Display
screen.setValue("title", "My Incidents")
screen.setValue("icon", "list")

// Ordering
screen.setValue("order", 100)

screen.insert()

卡片构建器(ES5)

创建卡片配置
// Create card builder for list display (ES5 ONLY!)
var card = new GlideRecord("sys_sg_card_builder")
card.initialize()

card.setValue("name", "Incident Card")
card.setValue("table", "incident")

// Card layout
card.setValue("primary_field", "number")
card.setValue("secondary_field", "short_description")
card.setValue("tertiary_field", "priority")

// Additional fields
card.setValue(
  "fields",
  JSON.stringify([
    { field: "caller_id", label: "Caller" },
    { field: "state", label: "Status" },
    { field: "opened_at", label: "Opened" },
  ]),
)

// Visual indicators
card.setValue("color_field", "priority")
card.setValue(
  "color_mapping",
  JSON.stringify({
    1: "#D32F2F", // Critical - Red
    2: "#F57C00", // High - Orange
    3: "#FBC02D", // Moderate - Yellow
    4: "#388E3C", // Low - Green
    5: "#1976D2", // Planning - Blue
  }),
)

card.insert()
自定义卡片操作
// Add actions to card (ES5 ONLY!)
function addCardAction(cardSysId, actionDef) {
  var action = new GlideRecord("sys_sg_card_action")
  action.initialize()

  action.setValue("card_builder", cardSysId)
  action.setValue("label", actionDef.label)
  action.setValue("icon", actionDef.icon)
  action.setValue("order", actionDef.order)

  // Action type
  action.setValue("action_type", actionDef.type) // script, navigate, share

  // Script action (ES5 ONLY!)
  if (actionDef.type === "script") {
    action.setValue("script", actionDef.script)
  }

  action.insert()
}

// Example actions
addCardAction(cardSysId, {
  label: "Acknowledge",
  icon: "check",
  order: 100,
  type: "script",
  script:
    "(function(gr) {\n" +
    "    gr.state = 2;  // In Progress\n" +
    '    gr.work_notes = "Acknowledged via mobile";\n' +
    "    gr.update();\n" +
    '    gs.addInfoMessage("Incident acknowledged");\n' +
    "})(current);",
})

推送通知(ES5)

配置推送通知
// Create push notification config (ES5 ONLY!)
var push = new GlideRecord("sys_push_notification")
push.initialize()

push.setValue("name", "High Priority Incident Assigned")
push.setValue("description", "Notify when high priority incident assigned")

// Target table and condition
push.setValue("table", "incident")
push.setValue("condition", "priority<=2^assigned_to.changes()")

// Notification content
push.setValue("title", "High Priority Incident Assigned")
push.setValue("body", "${number}: ${short_description}")

// Recipients
push.setValue("recipient_type", "field")
push.setValue("recipient_field", "assigned_to")

// Deep link
push.setValue("deep_link", true)
push.setValue("link_url", "/incident/${sys_id}")

push.setValue("active", true)

push.insert()
以编程方式发送推送通知
// Send push notification (ES5 ONLY!)
function sendPushNotification(userSysId, message) {
  try {
    var push = new sn_notification.PushNotification()

    push.setTitle(message.title)
    push.setBody(message.body)

    if (message.data) {
      push.setData(message.data)
    }

    if (message.deepLink) {
      push.setDeepLink(message.deepLink)
    }

    push.send(userSysId)

    return { success: true }
  } catch (e) {
    gs.error("Push notification failed: " + e.message)
    return { success: false, error: e.message }
  }
}

// Example
sendPushNotification(userSysId, {
  title: "Task Assigned",
  body: "You have a new task assigned",
  deepLink: "/task/" + taskSysId,
})

离线功能(ES5)

配置离线规则
// Create offline sync rule (ES5 ONLY!)
var rule = new GlideRecord("sys_sg_offline_rule")
rule.initialize()

rule.setValue("name", "My Open Incidents")
rule.setValue("mobile_app", mobileAppSysId)
rule.setValue("table", "incident")

// Sync filter
rule.setValue("filter", "assigned_to=javascript:gs.getUserID()^active=true")

// Fields to sync
rule.setValue("fields", "number,short_description,description,priority,state,caller_id,opened_at")

// Related records
rule.setValue("include_references", true)
rule.setValue("reference_fields", "caller_id,assignment_group")

// Sync limits
rule.setValue("max_records", 100)

// Update frequency
rule.setValue("sync_frequency", "on_demand") // on_demand, periodic

rule.setValue("active", true)

rule.insert()
处理离线数据
// Check for offline changes on sync (ES5 ONLY!)
function processOfflineChanges(userId) {
  var offlineQueue = new GlideRecord("sys_sg_offline_queue")
  offlineQueue.addQuery("user", userId)
  offlineQueue.addQuery("processed", false)
  offlineQueue.orderBy("created_on")
  offlineQueue.query()

  var results = { processed: 0, errors: [] }

  while (offlineQueue.next()) {
    try {
      var tableName = offlineQueue.getValue("table")
      var recordSysId = offlineQueue.getValue("record")
      var changes = JSON.parse(offlineQueue.getValue("changes"))

      // Apply changes
      var gr = new GlideRecord(tableName)
      if (gr.get(recordSysId)) {
        for (var field in changes) {
          if (changes.hasOwnProperty(field)) {
            gr.setValue(field, changes[field])
          }
        }
        gr.update()
        results.processed++
      }

      // Mark as processed
      offlineQueue.processed = true
      offlineQueue.update()
    } catch (e) {
      results.errors.push({
        record: offlineQueue.getValue("record"),
        error: e.message,
      })
    }
  }

  return results
}

移动操作(ES5)

创建移动操作
// Create mobile-specific action (ES5 ONLY!)
var action = new GlideRecord("sys_sg_action")
action.initialize()

action.setValue("name", "Scan Barcode")
action.setValue("label", "Scan Asset")
action.setValue("description", "Scan barcode to find asset")

// Action type
action.setValue("type", "native") // native, script, link
action.setValue("native_action", "barcode_scan")

// Available on
action.setValue("screens", screenSysIds)

// Callback script (ES5 ONLY!)
action.setValue(
  "callback_script",
  "(function(result) {\n" +
    "    if (!result.value) return;\n" +
    "    \n" +
    '    var asset = new GlideRecord("alm_asset");\n' +
    '    asset.addQuery("asset_tag", result.value);\n' +
    "    asset.query();\n" +
    "    \n" +
    "    if (asset.next()) {\n" +
    "        // Navigate to asset\n" +
    '        sn_mobile.navigate("record", {\n' +
    '            table: "alm_asset",\n' +
    "            sys_id: asset.getUniqueValue()\n" +
    "        });\n" +
    "    } else {\n" +
    '        gs.addErrorMessage("Asset not found: " + result.value);\n' +
    "    }\n" +
    "})(scanResult);",
)

action.insert()
基于位置的操作
// Get user location for mobile (ES5 ONLY!)
// Available in mobile context

function getCurrentLocation() {
  try {
    var location = sn_mobile.getLocation()
    return {
      latitude: location.latitude,
      longitude: location.longitude,
      accuracy: location.accuracy,
    }
  } catch (e) {
    return null
  }
}

// Use location for nearby assets
function findNearbyAssets(latitude, longitude, radiusMeters) {
  var assets = []

  var gr = new GlideRecord("alm_asset")
  gr.addNotNullQuery("location.latitude")
  gr.query()

  while (gr.next()) {
    var assetLat = parseFloat(gr.location.latitude)
    var assetLon = parseFloat(gr.location.longitude)

    var distance = calculateDistance(latitude, longitude, assetLat, assetLon)

    if (distance <= radiusMeters) {
      assets.push({
        sys_id: gr.getUniqueValue(),
        name: gr.getDisplayValue(),
        distance: Math.round(distance),
      })
    }
  }

  return assets.sort(function (a, b) {
    return a.distance - b.distance
  })
}

MCP 工具集成

可用工具
工具用途
snow_query_table查询移动配置
snow_execute_script测试移动脚本
snow_artifact_manage (action='find')查找配置
示例工作流
// 1. Query mobile apps
await snow_query_table({
  table: "sys_sg_mobile_app",
  query: "active=true",
  fields: "name,description,version,roles",
})

// 2. Get push notification configs
await snow_query_table({
  table: "sys_push_notification",
  query: "active=true",
  fields: "name,table,condition,title",
})

// 3. Check offline rules
await snow_query_table({
  table: "sys_sg_offline_rule",
  query: "active=true",
  fields: "name,table,filter,max_records",
})

最佳实践

  1. 离线优先 - 针对网络连接问题进行设计
  2. 精简数据 - 仅同步必要字段
  3. 合理推送 - 不要发送过多通知
  4. 原生功能 - 使用摄像头、GPS、条形码
  5. 卡片设计 - 让关键信息一目了然
  6. 性能 - 针对移动设备进行优化
  7. 测试 - 在实际设备上进行测试
  8. 仅限 ES5 - 不使用现代 JavaScript 语法

评论

支持 Markdown