生成第一个 SharePoint 自适应卡扩展

自适应卡片扩展 (ACE) 是一种新的SharePoint 框架组件类型,使开发人员能够生成丰富的本机扩展来Viva Connections的仪表板和 SharePoint 页面。 由于自适应卡片扩展使用 Microsoft 的自适应卡片框架通过其声明性 JSON 架构生成 UI,因此只需专注于组件的业务逻辑,并让 SharePoint 框架 (SPFx) 处理使组件看起来良好,并在所有平台上工作。

重要

本教程假设你已安装 SPFx v1.13。 有关安装 v1.13 SPFx 的详细信息,请参阅 SharePoint 框架 v1.13 发行说明

搭建自适应卡片扩展项目

为项目创建新的项目目录,将当前文件夹更改为该目录。

通过从你创建的新目录中运行 Yeoman SharePoint 生成器来创建新项目:

yo @microsoft/sharepoint

出现提示时,请输入以下值(为下面省略的所有提示选择默认选项):

  • 是否允许租户管理员选择立即将解决方案部署到所有站点,而无需在站点中运行任何功能部署或添加应用?
  • 要创建哪种类型的客户端组件? 自适应卡扩展
  • 要使用哪个模板? 主文本模板
  • 自适应卡片扩展名是什么? HelloWorld
  • 自适应卡片扩展说明是什么? Hello World 说明

此时,Yeoman 安装必需的依赖项,并为解决方案文件搭建基架。 此过程可能需要几分钟时间。

更新项目的托管工作台 URL

使用 gulp 任务 服务时,默认情况下,它将启动具有项目中指定的专用托管工作台 URL 的浏览器。 新项目中托管工作台的默认 URL 指向无效的 URL。

  • 找到并打开项目中的文件./config/serve.json

  • 找到属性 initialPage

    {
      "$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
      "port": 4321,
      "https": true,
      "initialPage": "https://enter-your-SharePoint-site/_layouts/workbench.aspx"
    }
    
  • enter-your-SharePoint-site 域更改为要用于测试 SharePoint 租户和网站的 URL。 例如:https://contoso.sharepoint.com/sites/devsite/_layouts/workbench.aspx

提示

还可以通过将 nobrowser 参数包含到 gulp serve 命令来启动本地 Web 服务器,而无需启动浏览器。 例如,你可能不希望修改所有项目中的 serve.json 文件,而是使用书签启动托管工作台。

gulp serve --nobrowser

在 workbench 中为 ACE 提供服务

在深入了解代码之前,请先运行基架输出并查看自适应卡片扩展的外观。

AES 的内部开发循环类似于 SPFx Web 部件。 我们可以在本地提供服务,并在 workbench 上运行代码。

gulp serve

一旦本地 Webserver 运行,则导航到托管的Workbench: https://{tenant}.sharepoint.com/_layouts/15/workbench.aspx

打开 Web 部件工具箱并选择 ACE:

从工具箱中选择 ACE

浏览卡片视图

AES 可以采用两种不同的方式呈现。 ACE 呈现的第一种方法称为 卡视图

在仪表板或页面上呈现时,AES 将始终在此视图中启动。

在卡片视图中呈现的 ACE

浏览快速视图

ACE 可以呈现的第二种方法称为 快速视图。 与 ACE 交互时,ACE 可以启动更大的自定义体验。

注意

编辑 模式下禁用 ACE 交互。 Workbench 或 Page 必须处于 “预览”“读取” 模式才能与 ACE 交互。

将 Workbench 切换到 “预览” 模式。

将 workbench 设置为预览模式

选择 ACE 上的 快速视图 按钮:

选择 ACE 上的“快速视图”按钮

检查基架代码

浏览基类

在项目中定位并打开以下文件: ./src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts

export default class HelloWorldAdaptiveCardExtension
  extends BaseAdaptiveCardExtension<IHelloWorldAdaptiveCardExtensionProps,IHelloWorldAdaptiveCardExtensionState> {
  // ...
}

所有 AES 必须从 BaseAdaptiveCardExtension 类扩展。 可以选择实现两个泛型:

  • TProperties: 与Web 部件类似,这是组件的持久化属性集 (属性包)。
  • TState: AES 是独一无二的, 可选地 定义可呈现数据集。

呈现 ACE

protected renderCard(): string | undefined {
  return CARD_VIEW_REGISTRY_ID;
}

renderCard()方法virtual返回字符串标识符到已注册视图; 稍后有更多查看注册的信息。 在卡片视图的 初始 呈现期间调用此方法。

如果未重写 renderCard() ,则将呈现默认“卡片视图”。

注释掉 renderCard() 方法,看看会发生什么情况:

/*
protected renderCard(): string | undefined {
  return CARD_VIEW_REGISTRY_ID;
}
*/

注释 renderCard() 方法的结果

取消对 renderCard() 方法的注释,以返回到原始状态。

默认卡片视图将使用清单中的以下属性呈现:

  • 图标: iconProperty
  • 标题:title
  • 卡片文本: description

注意

与卡片视图不同,没有默认的快速视图。

注册 ACE 视图

若要使用视图,必须将其注册到各自的 ViewNavigator。 在 ACE 上公开了两个 ViewNavigatorcardNavigatorquickViewNavigator

this.cardNavigator.register(CARD_VIEW_REGISTRY_ID, () => new CardView());
this.quickViewNavigator.register(QUICK_VIEW_REGISTRY_ID, () => new QuickView());

注意

必须在使用视图之前注册它。 可以在类的构造函数或 onInit() 方法中执行此操作。

“卡片视图”

定位并打开文件: ./src/adaptiveCardExtensions/helloWorld/cardView/CardView.ts

卡片视图必须从这些基类中扩展:

  • BaseBasicCardView

    BaseBasicCardView

  • BaseImageCardView

    BaseImageCardView

  • BasePrimaryTextCardView

    BasePrimaryTextCardView

这些视图中的每一个都会以不同的方式呈现,并对可以提供给模板的数据具有不同的约束。

注意

自适应卡片模板的卡片视图是固定的,无法更改。

此外,视图和 ACE 之间共享的 propertiesstate 对象有两个泛型。

  • TProperties: 视图的属性接口,与 ACE 的持久性属性 (属性包) 使用的相同接口。
  • TState: AES 是独一无二的, 可选地 定义可呈现数据集。

注意

SPFx 会自动将 ACE 的状态变化传播到每个视图。

data getter 是唯一必须由“卡片”视图实现的方法。 返回类型对于“视图”的父类是唯一的。

cardButtons 属性确定卡片上显示的按钮数以及单击后需要执行的操作。

如果未实现 cardButtons ,则卡片上不会显示任何按钮。

注意

初始卡视图是在 ACE 的 renderCard() 方法中指定,而初始的“快速视图”则作为按钮操作 parameters 的一部分指定。 这允许两个按钮有可能打开不同的视图。

通过在 cardButtons() 方法返回的数组中添加另一个对象来添加第二个按钮:

public get cardButtons(): [ICardButton] | [ICardButton, ICardButton] | undefined {
  return [
    {
      title: strings.QuickViewButton,
      action: {
        type: 'QuickView',
        parameters: {
          view: QUICK_VIEW_REGISTRY_ID
        }
      }
    },
    {
      title: 'Bing',
      action: {
        type: 'ExternalLink',
        parameters: {
          target: 'https://www.bing.com'
        }
      }
    }
  ];
}

最初,“卡片”中不会有任何更改。 这是因为 BasePrimaryTextCardView“中等 卡片”大小仅显示一个按钮。 SPFx 将选择元组中的第一个元素。

  1. 通过转到“属性窗格”并选择 “大型” 来更改“卡片”大小。

    选择卡片大小

    呈现的大型 ACE 卡片

  2. 现在,选择“必应”按钮时,必应将在新的浏览器选项卡中打开。

onCardSelection() 方法确定如果单击卡片会发生什么情况。 如果未实现 onCardSelection() 方法,则单击卡片时将不会发生任何情况。

  1. 通过修改 onCardSelection() 方法,更改卡片选择以打开 “快速” 视图:

    public get onCardSelection(): IQuickViewCardAction | IExternalLinkCardAction | undefined {
      return {
        type: 'QuickView',
        parameters: {
          view: QUICK_VIEW_REGISTRY_ID
        }
      };
    }
    
  2. 现在,当你选择该卡片时,它将打开快速视图。

ACE 快速视图

定位并打开以下文件: ./src/adaptiveCardExtensions/helloWorld/quickView/QuickView.ts

快速视图必须扩展 BaseAdaptiveCardView 基类。 可以定义三个可选泛型:

  • TData: 从 data() getter 方法返回的类型。
  • TProperties: 与卡片视图类似,这是 ACE 的持久性属性使用的相同接口(属性包)。
  • TState 与卡片视图类似,这是视图需要呈现的有状态的数据集。 TState 必须与 ACE 的状态接口共享属性。

与“卡片”视图相比,“快速视图”对“自适应卡片”模板架构有更多控制。 template() getter 必须返回有效的自适应卡模板 JSON。 SPFx ATE 支持自适应卡模板化。 从 data getter 返回的对象上的属性将自动映射到绑定的模板槽。

例如,${description} 绑定到 this.properties.description

// QuickView.ts
public get data(): IQuickViewData {
  return {
    // ...
    description: this.properties.description
  };
}
// QuickViewTemplate.json.ts
{
  "type": "TextBlock",
  "text": "${description}",
  "wrap": true
}

注意

必须使用自适应卡绑定语法,该语法使用 ${} 括号。

让我们更改这一点:

  1. 从“快速视图”数据中删除 description 属性,并添加两个按钮。

  2. 更新 IQuickViewData 接口,如以下代码所示:

    export interface IQuickViewData {
      title: string;
      subTitle: string;
    }
    
  3. 更新 data() 方法,如以下代码所示:

    public get data(): IQuickViewData {
      return {
        subTitle: this.state.subTitle,
        title: strings.Title
      };
    }
    
  4. 定位并打开以下文件: ./src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts

  5. 按如下所示更新 IHelloWorldAdaptiveCardExtensionState 接口和 onInit() 方法:

    export interface IHelloWorldAdaptiveCardExtensionState {
      subTitle: string;
    }
    
    ..
    
    public onInit(): Promise<void> {
      this.state = {
        subTitle: 'No button clicked'
      };
      // ...
    }
    

接下来,从“卡片”视图中删除对 this.properties.description 的引用:

  1. 定位并打开以下文件: ./src/adaptiveCardExtensions/helloWorld/cardView/CardView.ts

  2. 删除返回的对象中的 description 属性:

    public get data(): IPrimaryTextCardParameters {
      return {
        primaryText: strings.PrimaryText
      };
    }
    

在其 template() getter 中,生成的 ACE “快速”视图从 JSON 文件中返回对象。 现在我们来修改该模板:

  1. 定位并打开以下文件: ./src/adaptiveCardExtensions/helloWorld/quickView/template/QuickViewTemplate.json

  2. 使用以下 JSON 替换此文件的内容:

    {
      "schema": "http://adaptivecards.io/schemas/adaptive-card.json",
      "type": "AdaptiveCard",
      "version": "1.2",
      "body": [
        {
          "type": "TextBlock",
          "weight": "Bolder",
          "text": "${title}"
        },
        {
          "type": "TextBlock",
          "text": "${subTitle}",
          "wrap": true
        },
        {
          "type": "ActionSet",
          "actions": [
            {
              "type": "Action.Submit",
              "title": "Button One",
              "style": "positive",
              "data": {
                "id": "button1",
                "message": "Clicked Button One"
              }
            },
            {
              "type": "Action.Submit",
              "title": "Button Two",
              "data": {
                "id": "button2",
                "message": "Clicked Button Two"
              }
            }
          ]
        }
      ]
    }
    

通过刷新浏览器中的托管工作台来测试更改。 如果 gulp serve 仍在运行,则应该会显示已应用于项目的更改:

更新的 ACE 快速视图

提示

有关自适应卡片的详细信息,请参阅 https://adaptivecards.io。 此网站还包括自适应卡片设计器,可让你在创建自适应卡片时预览这些卡片的呈现和结构。

此时,你已修改 ACE 以在快速视图卡片中包括两个新按钮。 下一步是实现选择这些按钮时发生的情况。 这将使用 操作处理程序完成。

操作处理程序

操作由所定义的视图处理。

快速视图有两个按钮,但该视图当前未处理 提交 操作。 每当执行“自适应卡片操作”时 (例如在启动 Action.Submit 操作时),就会调用 onAction() 方法。

定位并打开文件 QuickView.ts,并重写 onAction() 以处理两个按钮的选择,如以下代码所示:

import { ISPFxAdaptiveCard, BaseAdaptiveCardView, IActionArguments } from '@microsoft/sp-adaptive-card-extension-base';

..

public onAction(action: IActionArguments): void {
  if (action.type === 'Submit') {
    const { id, message } = action.data;
    switch (id) {
      case 'button1':
      case 'button2':
        this.setState({
          subTitle: message
        });
        break;
    }
  }
}

通过刷新浏览器中的托管工作台来测试更改。 如果 gulp serve 仍在运行,则应该会显示已应用于项目的更改。

选择任一按钮现在都会将状态的 subTitle 设置为 data.message 值,从而导致重新呈现 (稍后会有更多相关内容)。 快速视图的自适应卡片现在将显示此消息,因为它的模板绑定到 subTitle

属性窗格

与 Web 部件类似,ACE 可以具有由具有适当权限的用户设置的可配置属性。 这些可以让你自定义 ACE 的每个实现。 这将使用属性窗格完成。

可以像配置 Web 部件一样配置 ACE。 对于 HelloWorldAdaptiveCardExtension.ts 文件中找到的以下方法,API 签名是相同的:

  • getPropertyPaneConfiguration()
  • onPropertyPaneFieldChanged()

ACE 的默认基架使用新的 API,其旨在在组件未处于 “编辑” 模式时最大程度地减小捆绑包大小。 loadPropertyPaneResources() 方法利用 Webpack 的分块功能将“属性面板”的特定代码分离到自己的 JS 文件中,然后可以根据需要加载。

除了返回属性窗格配置之外, HelloWorldPropertyPane 类还用于封装所有 “编辑” 模式逻辑。

属性

“卡片大小”字段外,基架 ACE 还有三个 (3 个) 可配置字段,这些字段在 getPropertyPaneConfiguration()IHelloWorldAdaptiveCardExtension 接口中定义的方法&中定义:

  • title
  • iconProperty
  • description

卡片视图设计为自动适用于所有卡片尺寸。 除了指定默认卡大小之外,AES 无法控制此属性。

ACE 文件中定义的 titleiconProperty 属性(即: HelloWorldAdaptiveCardExtension.ts)分别用于 ACE 的 title()iconProperty() getter 来配置卡片的标题和图标:

title 值用于“属性窗格”的标题和“卡片”上显示的标题中。

public get title(): string {
  return this.properties.title;
}

iconProperty 值是卡片视图使用的图标的 URL。

protected get iconProperty(): string {
  return this.properties.iconProperty || require('./assets/sharepointlogo.png');
}

状态

state 属性必须在调用 setState() 方法之前进行初始化,并且仅能初始化一次。

public onInit(): Promise<void> {
  this.state = {
    subTitle: 'No button clicked'
  };
  // ...
}

properties 不同,state 不会保留在当前会话中,仅能应用于临时“视图”状态。

注意

基架 ACE 维护 state 对象中的一个 description 属性。 这已过时,因为 ACE 及其所有视图可以直接引用 properties 中存储的 description

正在重新渲染

在更新 PropertyPane 中属性或调用 setState() 时,将发生重新呈现。

更新属性窗格的“说明字段”值时,它将更新卡片上的说明。 让我们看一下如何这样做:

  1. 定位并打开以下文件: ./src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts

  2. 作为一个简单的示例,请在 onPropertyPaneFieldChanged 事件期间 description 更新时更新 subTitle 值。 将以下代码添加到 ACE HelloWorldAdaptiveCardExtension 类:

    protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
      if (propertyPath === 'description') {
        this.setState({
          subTitle: newValue
        });
      }
    }
    

Partial<TState> 对象传递给 setState() 方法将使用新值更新所有 Views。 现在,更新属性窗格中的“说明字段”将更新“快速视图”上显示的 subTitle

如果未传递任何值或相同的值,则仍会出现重新呈现。

setState() 方法不仅限于“属性窗格”。 它可以在接收到新数据时使用,也可以作为某些用户操作的结果。

结束语

本教程结束后,应熟悉以下内容:

  • 搭建“自适应卡扩展”的基架
  • 正在注册视图
  • 更改卡片视图和快速视图
  • 基本操作处理
  • 正在更改用属性窗格
  • 延迟加载属性窗格
  • 如何使用 state
  • propertiesstate 之间的区别