向卡片添加互动式界面元素

本页介绍了如何向卡片添加 widget 和界面元素,以便用户与您的 Google Chat 应用互动,例如点击按钮或提交信息。

聊天应用可以使用以下聊天界面来构建互动式卡片:

  • 包含一个或多个卡片的消息
  • 首页:一种卡片,显示在与 Chat 应用的私信中的首页标签页中。
  • 对话框:从消息和首页在新窗口中打开的卡片。

当用户与卡片互动时,Chat 应用可以使用收到的数据进行处理并做出相应回应。如需了解详情,请参阅收集和处理 Google Chat 用户的信息


使用卡片构建器设计和预览 Chat 应用的消息和界面:

打开卡片构建器

前提条件

已配置为接收和响应互动事件的 Google Chat 应用。如需创建交互式 Chat 应用,请根据要使用的应用架构完成以下任一快速入门 Codelab:

添加按钮

ButtonList widget 会显示一组按钮。按钮可以显示文字、图标,也可以同时显示文字和图标。每个 Button 都支持用户点击按钮时发生的 OnClick 操作。例如:

  • 使用 OpenLink 打开超链接,以便为用户提供更多信息。
  • 运行执行自定义函数(例如调用 API)的 action

为了方便残障人士使用,按钮支持替代文本。

添加一个运行自定义函数的按钮

以下是一个包含 ButtonList widget 和两个按钮的卡片。 一个按钮可在新标签页中打开 Google Chat 开发者文档。另一个按钮运行名为 goToView() 的自定义函数,并传递 viewType="BIRD EYE VIEW" 参数。

添加具有 Material Design 样式的按钮

以下代码显示了一组采用不同 Material Design 按钮样式的按钮。

如需应用 Material Design 样式,请勿添加“color”属性。

添加了具有自定义颜色的按钮和已停用的按钮

您可以通过设置 "disabled": "true" 来禁止用户点击按钮。

以下显示了一张卡片,其中包含一个带有两个按钮的 ButtonList widget。一个按钮使用 Color 字段自定义按钮的背景颜色。另一个按钮通过 Disabled 字段停用,这可防止用户点击该按钮并执行相应函数。

添加带图标的按钮

以下内容显示了一张卡片,其中包含一个 ButtonList widget 和两个图标 Button widget。一个按钮使用 knownIcon 字段来显示 Google Chat 的内置电子邮件图标。另一个按钮使用 iconUrl 字段显示自定义图标 widget

添加带有图标和文字的按钮

以下显示了一张卡片,其中包含一个 ButtonList widget,用于提示用户发送电子邮件。第一个按钮显示电子邮件图标,第二个按钮显示文字。用户可以点击图标或文字按钮来运行 sendEmail 函数。

自定义可折叠部分的按钮

自定义用于折叠和展开卡片内各部分的控制按钮。您可以从一系列图标或图片中进行选择,以直观地呈现版块的内容,方便用户理解和互动。

添加溢出菜单

Overflow menu 可在 Chat 卡片中使用,以提供其他选项和操作。这样一来,您就可以添加更多选项,而不会使卡片的界面显得杂乱无章,从而确保设计简洁有序。

添加条状标签列表

ChipList widget 提供了一种用途广泛且极具视觉吸引力的信息显示方式。使用 chip 列表来表示标签、类别或其他相关数据,以便用户更轻松地浏览和互动您的内容。

收集用户提供的信息

本部分介绍了如何添加可收集信息(例如文本或选择)的 widget。

如需了解如何处理用户输入的内容,请参阅收集和处理 Google Chat 用户的信息

收集文本

TextInput widget 提供了一个字段,供用户在其中输入文本。该 widget 支持建议(可帮助用户输入统一的数据)和 on-change 操作(即当文本输入字段发生更改时运行的 Actions,例如用户添加或删除文本时)。

当您需要从用户那里收集抽象或未知数据时,请使用此 TextInput widget。如需从用户那里收集已定义的数据,请改用 SelectionInput widget。

以下是包含 TextInput widget 的卡片:

收集日期或时间

借助 DateTimePicker widget,用户可以输入日期、时间或同时输入日期和时间。或者,用户可以使用选择器选择日期和时间。如果用户输入了无效的日期或时间,选择器会显示一条错误消息,提示用户正确输入信息。

以下显示了一张包含三种不同类型 DateTimePicker widget 的卡片:

允许用户选择商品

SelectionInput widget 提供了一组可选择的项,例如复选框、单选按钮、开关或下拉菜单。您可以使用此 widget 从用户那里收集已定义和标准化的数据。如需从用户处收集未定义的数据,请改用 TextInput widget。

SelectionInput widget 支持建议(可帮助用户输入统一的数据)和 on-change 操作(即当选择输入字段发生更改时运行的 Actions,例如用户选择或取消选择某个项)。

聊天应用可以接收和处理所选商品的值。 如需详细了解如何处理表单输入,请参阅处理用户输入的信息

本部分提供了使用 SelectionInput widget 的卡片示例。这些示例使用了不同类型的部分输入:

添加复选框

以下显示了一张卡片,其中包含一个使用复选框的 SelectionInput widget,用于询问用户联系人是专业联系人、个人联系人还是两者兼具:

添加单选按钮

以下显示了一张卡片,其中包含一个使用单选按钮的 SelectionInput widget,用于询问用户联系人是专业联系人还是个人联系人:

添加开关

以下显示了一张卡片,其中包含一个使用开关的 SelectionInput widget,用于询问用户联系人是专业联系人、个人联系人还是两者兼而有之:

以下代码显示了一个卡片,其中包含一个使用下拉菜单的 SelectionInput widget,用于询问用户指定联系人是专业联系人还是个人联系人:

动态填充下拉菜单

您可以从 Google Workspace 中的数据源或外部数据源动态填充下拉菜单的项。如需使用动态数据源,请指定 data_source_configs 字段,该字段是一个 DataSourceConfig 对象数组。每个 DataSourceConfig 可以包含 platformDataSourceremoteDataSource。目前仅支持一种 DataSourceConfig

填充 Google Workspace 中的内容

如需填充来自 Google Workspace 数据源(例如 Google Workspace 用户)的项,您需要在 DataSourceConfig 中指定 platformDataSource 字段。与使用静态 items 不同,您无需使用 SelectionItem 对象,因为这些选择项是从 Google Workspace 动态获取的。

以下代码展示了一个用于填充 Google Workspace 用户的下拉菜单:

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "contacts",
            "type": "DROPDOWN",
            "label": "Select contact from organization",
            "data_source_configs": [
              {
                "platformDataSource": {
                  "commonDataSource": "USER"
                },
                "min_characters_trigger": 1
              }
            ]
          }
        }
      ]
    }
  ]
}
从外部数据源填充商品

如需从第三方或外部数据源(例如客户关系管理 [CRM] 系统)填充商品,您可以使用 DataSourceConfig 中的 remoteDataSource 字段来指定一个从数据源返回商品的函数。

以下代码展示了一个下拉菜单,该菜单通过运行函数 getCrmLeads 从一组外部联系人填充项:

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "crm_leads",
            "type": "DROPDOWN",
            "label": "Select CRM Lead",
            "data_source_configs": [
              {
                "remoteDataSource": {
                  "function": "getCrmLeads"
                },
                "min_characters_trigger": 2
              }
            ],
            "items": [
              {
                "text": "Suggested Lead 1",
                "value": "lead-1"
              }
            ]
          }
        }
      ]
    }
  ]
}

为了减少对动态数据源的请求,您可以添加在用户输入之前显示在下拉菜单中的建议项。您还可以通过在 DataSourceConfig 中设置 min_characters_trigger,将下拉菜单配置为根据用户输入的内容自动补全项目。当用户输入的字符数至少达到 min_characters_trigger 中指定的数量时,系统会触发 remoteDataSource 中指定的函数。传递给函数的事件对象会在 autocomplete_widget_query 键中包含用户的输入内容。

添加多选菜单

以下代码显示了一张卡片,其中要求用户从多选菜单中选择联系人:

您可以在 Google Workspace 中从以下数据源填充多选菜单的项:

  • Google Workspace 用户:您只能填充同一 Google Workspace 组织内的用户。
  • 聊天室:在多选菜单中输入内容的用户只能查看和选择其所属 Google Workspace 组织中的聊天室。

如需使用 Google Workspace 数据源,请指定 platformDataSource 字段。与其他选择输入类型不同,您需要省略 SelectionItem 对象,因为这些选择项是从 Google Workspace 动态获取的。

以下代码展示了 Google Workspace 用户的多选菜单。 为了填充用户,选择输入将 commonDataSource 设置为 USER

JSON

{
  "selectionInput": {
    "name": "contacts",
    "type": "MULTI_SELECT",
    "label": "Selected contacts",
    "multiSelectMaxSelectedItems": 5,
    "multiSelectMinQueryLength": 1,
    "platformDataSource": {
      "commonDataSource": "USER"
    }
  }
}

以下代码展示了一个 Chat 空间的单选菜单。为了填充空间,选择输入指定了 hostAppDataSource 字段。多选菜单还会将 defaultToCurrentSpace 设置为 true,这会使当前空间成为菜单中的默认选择:

JSON

{
  "selectionInput": {
    "name": "spaces",
    "type": "MULTI_SELECT",
    "label": "Selected contacts",
    "multiSelectMaxSelectedItems": 3,
    "multiSelectMinQueryLength": 1,
    "platformDataSource": {
      "hostAppDataSource": {
        "chatDataSource": {
          "spaceDataSource": {
            "defaultToCurrentSpace": true
          }
        }
      }
    }
  }
}

多选菜单还可以从第三方或外部数据源填充项。例如,您可以使用多选菜单帮助用户从客户关系管理 (CRM) 系统中的销售线索列表中进行选择。

如需使用外部数据源,您可以使用 externalDataSource 字段指定一个可从数据源返回项的函数。

为了减少对外部数据源的请求,您可以在用户在多选菜单中输入内容之前,先在其中显示建议的项。例如,您可以为用户填充最近搜索过的联系人。如需从外部数据源填充建议的商品,请指定 SelectionItem 对象。

以下代码显示了一个多选菜单,其中包含来自用户外部联系人集的项。默认情况下,菜单会显示一个联系人,并运行函数 getContacts 以从外部数据源检索和填充项:

Node.js

node/selection-input/index.js
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: "getContacts" },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getContact("3")]
}

Python

python/selection-input/main.py
'selectionInput': {
  'name': "contacts",
  'type': "MULTI_SELECT",
  'label': "Selected contacts",
  'multiSelectMaxSelectedItems': 3,
  'multiSelectMinQueryLength': 1,
  'externalDataSource': { 'function': "getContacts" },
  # Suggested items loaded by default.
  # The list is static here but it could be dynamic.
  'items': [get_contact("3")]
}

Java

java/selection-input/src/main/java/com/google/chat/selectionInput/App.java
.setSelectionInput(new GoogleAppsCardV1SelectionInput()
  .setName("contacts")
  .setType("MULTI_SELECT")
  .setLabel("Selected contacts")
  .setMultiSelectMaxSelectedItems(3)
  .setMultiSelectMinQueryLength(1)
  .setExternalDataSource(new GoogleAppsCardV1Action().setFunction("getContacts"))
  .setItems(List.of(getContact("3")))))))))));

Apps 脚本

apps-script/selection-input/selection-input.gs
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: "getContacts" },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getContact("3")]
}

对于外部数据源,您还可以自动补全用户在多选菜单中开始输入的项。例如,如果用户开始输入 Atl 以打开填充美国城市的菜单,您的 Chat 应用可以在用户完成输入之前自动建议 Atlanta。您最多可以自动补全 100 个项目。

如需自动补全项,您可以创建一个函数,该函数会在用户在多选菜单中输入内容时查询外部数据源并返回项。该函数必须执行以下操作:

  • 传递一个表示用户与菜单互动的事件对象。
  • 确定互动事件的 invokedFunction 值与 externalDataSource 字段中的函数相匹配。
  • 当函数匹配时,从外部数据源返回建议的项目。如需根据用户输入的内容推荐商品,请获取 autocomplete_widget_query 键的值。此值表示用户在菜单中输入的内容。

以下代码会自动补全外部数据资源中的项。沿用上一个示例,聊天应用会根据函数 getContacts 的触发时间建议商品:

Node.js

node/selection-input/index.js
/**
 * Responds to a WIDGET_UPDATE event in Google Chat.
 *
 * @param {Object} event The event object from Chat API.
 * @return {Object} Response from the Chat app.
 */
function onWidgetUpdate(event) {
  if (event.common["invokedFunction"] === "getContacts") {
    const query = event.common.parameters["autocomplete_widget_query"];
    return { actionResponse: {
      type: "UPDATE_WIDGET",
      updatedWidget: { suggestions: { items: [
        // The list is static here but it could be dynamic.
        getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
      // Only return items based on the query from the user
      ].filter(e => !query || e.text.includes(query))}}
    }};
  }
}

/**
 * Generate a suggested contact given an ID.
 *
 * @param {String} id The ID of the contact to return.
 * @return {Object} The contact formatted as a suggested item for selectors.
 */
function getContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

Python

python/selection-input/main.py
def on_widget_update(event: dict) -> dict:
  """Responds to a WIDGET_UPDATE event in Google Chat."""
  if "getContacts" == event.get("common").get("invokedFunction"):
    query = event.get("common").get("parameters").get("autocomplete_widget_query")
    return { 'actionResponse': {
      'type': "UPDATE_WIDGET",
      'updatedWidget': { 'suggestions': { 'items': list(filter(lambda e: query is None or query in e["text"], [
        # The list is static here but it could be dynamic.
        get_contact("1"), get_contact("2"), get_contact("3"), get_contact("4"), get_contact("5")
      # Only return items based on the query from the user
      ]))}}
    }}


def get_contact(id: str) -> dict:
  """Generate a suggested contact given an ID."""
  return {
    'value': id,
    'startIconUri': "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    'text': "Contact " + id
  }

Java

java/selection-input/src/main/java/com/google/chat/selectionInput/App.java
// Responds to a WIDGET_UPDATE event in Google Chat.
Message onWidgetUpdate(JsonNode event) {
  if ("getContacts".equals(event.at("/invokedFunction").asText())) {
    String query = event.at("/common/parameters/autocomplete_widget_query").asText();
    return new Message().setActionResponse(new ActionResponse()
      .setType("UPDATE_WIDGET")
      .setUpdatedWidget(new UpdatedWidget()
        .setSuggestions(new SelectionItems().setItems(List.of(
          // The list is static here but it could be dynamic.
          getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
        // Only return items based on the query from the user
        ).stream().filter(e -> query == null || e.getText().indexOf(query) > -1).toList()))));
  }
  return null;
}

// Generate a suggested contact given an ID.
GoogleAppsCardV1SelectionItem getContact(String id) {
  return new GoogleAppsCardV1SelectionItem()
    .setValue(id)
    .setStartIconUri("https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png")
    .setText("Contact " + id);
}

Apps 脚本

apps-script/selection-input/selection-input.gs
/**
 * Responds to a WIDGET_UPDATE event in Google Chat.
 *
 * @param {Object} event The event object from Chat API.
 * @return {Object} Response from the Chat app.
 */
function onWidgetUpdate(event) {
  if (event.common["invokedFunction"] === "getContacts") {
    const query = event.common.parameters["autocomplete_widget_query"];
    return { actionResponse: {
      type: "UPDATE_WIDGET",
      updatedWidget: { suggestions: { items: [
        // The list is static here but it could be dynamic.
        getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
      // Only return items based on the query from the user
      ].filter(e => !query || e.text.includes(query))}}
    }};
  }
}

/**
 * Generate a suggested contact given an ID.
 *
 * @param {String} id The ID of the contact to return.
 * @return {Object} The contact formatted as a suggested item for selectors.
 */
function getContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

验证输入到卡片中的数据

本页面介绍了如何验证输入到卡片的 action 和 widget 中的数据。例如,您可以验证文本输入字段是否包含用户输入的文本,或者是否包含一定数量的字符。

为操作设置必需的 widget

在卡片的 action 中,将操作所需的 widget 名称添加到其 requiredWidgets 列表中。

如果此处列出的任何 widget 在调用此操作时没有值,则会取消表单操作提交。

如果某个操作设置为 "all_widgets_are_required": "true",则该操作需要卡片中的所有 widget。

在多选模式下设置 all_widgets_are_required 操作

JSON

{
  "sections": [
    {
      "header": "Select contacts",
      "widgets": [
        {
          "selectionInput": {
            "type": "MULTI_SELECT",
            "label": "Selected contacts",
            "name": "contacts",
            "multiSelectMaxSelectedItems": 3,
            "multiSelectMinQueryLength": 1,
            "onChangeAction": {
              "all_widgets_are_required": true
            },
            "items": [
              {
                "value": "contact-1",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 1",
                "bottomText": "Contact one description",
                "selected": false
              },
              {
                "value": "contact-2",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 2",
                "bottomText": "Contact two description",
                "selected": false
              },
              {
                "value": "contact-3",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 3",
                "bottomText": "Contact three description",
                "selected": false
              },
              {
                "value": "contact-4",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 4",
                "bottomText": "Contact four description",
                "selected": false
              },
              {
                "value": "contact-5",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 5",
                "bottomText": "Contact five description",
                "selected": false
              }
            ]
          }
        }
      ]
    }
  ]
}
在 dateTimePicker 中设置 all_widgets_are_required 操作

JSON

{
  "sections": [
    {
      "widgets": [
        {
          "textParagraph": {
            "text": "A datetime picker widget with both date and time:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_date_and_time",
            "label": "meeting",
            "type": "DATE_AND_TIME"
          }
        },
        {
          "textParagraph": {
            "text": "A datetime picker widget with just date:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_date_only",
            "label": "Choose a date",
            "type": "DATE_ONLY",
            "onChangeAction":{
              "all_widgets_are_required": true
            }
          }
        },
        {
          "textParagraph": {
            "text": "A datetime picker widget with just time:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_time_only",
            "label": "Select a time",
            "type": "TIME_ONLY"
          }
        }
      ]
    }
  ]
}
在下拉菜单中设置 all_widgets_are_required 操作

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 1,
      "widgets": [
        {
          "selectionInput": {
            "name": "location",
            "label": "Select Color",
            "type": "DROPDOWN",
            "onChangeAction": {
              "all_widgets_are_required": true
            },
            "items": [
              {
                "text": "Red",
                "value": "red",
                "selected": false
              },
              {
                "text": "Green",
                "value": "green",
                "selected": false
              },
              {
                "text": "White",
                "value": "white",
                "selected": false
              },
              {
                "text": "Blue",
                "value": "blue",
                "selected": false
              },
              {
                "text": "Black",
                "value": "black",
                "selected": false
              }
            ]
          }
        }
      ]
    }
  ]
}

为文本输入 widget 设置验证

textInput widget 的验证字段中,可以为此文本输入 widget 指定字符限制和输入类型。

为文本输入 widget 设置字符数限制

JSON

{
  "sections": [
    {
      "header": "Tell us about yourself",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 2,
      "widgets": [
        {
          "textInput": {
            "name": "favoriteColor",
            "label": "Favorite color",
            "type": "SINGLE_LINE",
            "validation": {"character_limit":15},
            "onChangeAction":{
              "all_widgets_are_required": true
            }
          }
        }
      ]
    }
  ]
}
为文本输入 widget 设置输入类型

JSON

{
  "sections": [
    {
      "header": "Validate text inputs by input types",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 2,
      "widgets": [
        {
          "textInput": {
            "name": "mailing_address",
            "label": "Please enter a valid email address",
            "type": "SINGLE_LINE",
            "validation": {
              "input_type": "EMAIL"
            },
            "onChangeAction": {
              "all_widgets_are_required": true
            }
          }
        },
        {
          "textInput": {
            "name": "validate_integer",
            "label": "Please enter a number",
              "type": "SINGLE_LINE",
            "validation": {
              "input_type": "INTEGER"
            }
          }
        },
        {
          "textInput": {
            "name": "validate_float",
            "label": "Please enter a number with a decimal",
            "type": "SINGLE_LINE",
            "validation": {
              "input_type": "FLOAT"
            }
          }
        }
      ]
    }
  ]
}

问题排查

当 Google Chat 应用或卡片返回错误时,Chat 界面会显示一条消息,提示“出了点问题”。 或“无法处理您的请求”。有时,Chat 界面不会显示任何错误消息,但 Chat 应用或卡片会产生意外结果;例如,卡片消息可能不会显示。

虽然聊天界面中可能不会显示错误消息,但当您为聊天应用启用错误日志记录功能后,系统会提供描述性错误消息和日志数据,帮助您修复错误。如需有关查看、调试和修复错误的帮助,请参阅排查和修复 Google Chat 错误