仪表板是一种跟踪、分析和显示数据以深入了解组织或特定流程的工具。 Teams 中的仪表板允许监视和查看重要指标。

Teams 工具包中的“仪表板”选项卡模板允许你开始将画布与多个卡片集成,这些卡片提供 Teams 中内容的概述。 可以执行下列操作:

  • 使用小组件在“仪表板”选项卡中显示应用和服务中的内容。
  • 将应用与图形 API集成,以可视化有关所选数据实现的详细信息。
  • 创建可自定义的仪表板,使你的企业能够设置特定目标,帮助你跟踪你需要在多个领域和跨部门查看的信息。
  • 你的团队可以使用 Teams 仪表板 选项卡应用从 Teams 中的不同源获取最新更新。 使用仪表板选项卡应用连接许多指标、数据源、API 和服务,以帮助你的企业从源中提取相关信息并将其呈现给用户。 有关创建仪表板选项卡应用和源代码目录结构的详细信息,请参阅 分步指南

    添加新仪表板

    创建仪表板选项卡应用后,可以添加新仪表板。

    若要添加新仪表板,请执行以下步骤:

  • 创建仪表板类
  • 重写方法以自定义仪表板选项卡应用
  • 为新的仪表板选项卡应用添加路由
  • 修改清单以添加新仪表板选项卡应用
  • 创建仪表板类

    在 tabs/src/views/dashboards 目录中为仪表板创建扩展名为 .tsx 的文件,例如 YourDashboard.tsx。 然后,创建一个扩展 Dashboard 类的类:

    export default class YourDashboard extends Dashboard {}

    所有方法都是可选的。 如果不重写任何方法,则使用默认仪表板布局。

    重写方法以自定义仪表板选项卡应用

    dashboard 提供了一些可替代方法来自定义仪表板布局。 下表列出了可以替代的方法:

    以下代码是自定义仪表板布局的示例:

    export default class YourDashboard extends Dashboard {
      override rowHeights(): string | undefined {
        return "500px";
      override columnWidths(): string | undefined {
        return "4fr 6fr";
      override dashboardLayout(): JSX.Element | undefined {
        return (
            <SampleWidget />
    

    为新的仪表板选项卡应用添加路由

    必须将小组件链接到数据源文件。 小组件从源文件中选取仪表板中显示的数据。

    打开 tabs/src/App.tsx 并为新仪表板添加路由。 下面是一个示例:

    import YourDashboard from "./views/dashboards/YourDashboard";
    export default function App() {
      <Route exact path="/yourdashboard" component={YourDashboard} />
    

    修改清单以添加新仪表板选项卡应用

    打开 templates/appPackage/manifest.template.json,并在 staticTabs 下添加新的“仪表板”选项卡。 下面是一个示例:

    "name": "Your Dashboard", "entityId": "yourdashboard", "contentUrl": "{{state.fx-resource-frontend-hosting.endpoint}}{{state.fx-resource-frontend-hosting.indexPath}}/yourdashboard", "websiteUrl": "{{state.fx-resource-frontend-hosting.endpoint}}{{state.fx-resource-frontend-hosting.indexPath}}/yourdashboard", "scopes": ["personal"]

    自定义仪表板布局

    Teamsfx 提供了用于定义和修改仪表板布局的便捷方法。 以下是方法:

  • 一行中的三个小组件,高度为 350 像素,分别占宽度的 20%、60% 和 20%。

    export default class SampleDashboard extends Dashboard {
      override rowHeights(): string | undefined {
        return "350px";
      override columnWidths(): string | undefined {
        return "2fr 6fr 2fr";
      override dashboardLayout(): undefined | JSX.Element {
        return (
            <ListWidget />
            <ChartWidget />
            <NewsWidget />
    
  • 一行中的两个小组件,宽度为 600 像素和 1100 像素。 第一行的高度是其内容的最大高度,第二行的高度为 400 像素。

    export default class SampleDashboard extends Dashboard {
      override rowHeights(): string | undefined {
        return "max-content 400px";
      override columnWidths(): string | undefined {
        return "600px 1100px";
      override dashboardLayout(): undefined | JSX.Element {
        return (
            <ListWidget />
            <ChartWidget />
            <NewsWidget />
    

    为了调整仪表板的布局,Teamsfx 提供了一个dashboard类,供开发人员实现仪表板。

    以下代码是 仪表板 类的示例:

    import React, { Component } from "react";
    import { mergeStyles } from "@fluentui/react";
    interface IDashboardState {
      isMobile?: boolean;
      observer?: ResizeObserver;
    export class Dashboard extends Component<{}, IDashboardState> {
      private ref: React.RefObject<HTMLDivElement>;
      constructor(props: any) {
        super(props);
        this.state = {
          isMobile: undefined,
          observer: undefined,
        this.ref = React.createRef<HTMLDivElement>();
      componentDidMount(): void {
        // Observe the dashboard div for resize events
        const observer = new ResizeObserver((entries) => {
          for (let entry of entries) {
            if (entry.target === this.ref.current) {
              const { width } = entry.contentRect;
              this.setState({ isMobile: width < 600 });
        observer.observe(this.ref.current!);
      componentWillUnmount(): void {
        // Unobserve the dashboard div for resize events
        if (this.state.observer && this.ref.current) {
          this.state.observer.unobserve(this.ref.current);
      render() {
        const styling = mergeStyles({
          display: "grid",
          gap: "20px",
          padding: "1rem",
          gridTemplateColumns: "4fr 6fr",
          gridTemplateRows: "1fr",
          ...(this.state.isMobile && { gridTemplateColumns: "1fr" }),
          ...(this.columnWidths() && { gridTemplateColumns: this.columnWidths() }),
          ...(this.rowHeights() && { gridTemplateRows: this.rowHeights() }),
        return (
            <div ref={this.ref} className={styling}>
              {this.dashboardLayout()}
      protected rowHeights(): string | undefined {
        return undefined;
      protected columnWidths(): string | undefined {
        return undefined;
      protected dashboardLayout(): JSX.Element | undefined {
        return undefined;
    

    在 类中 dashboard ,Teamsfx 提供具有可自定义方法的基本布局。 仪表板仍然是一个 react 组件,Teamsfx 根据响应组件的生命周期提供函数的基本实现,例如:

  • 基于网格布局实现基本呈现逻辑。
  • 添加观察程序以自动适应移动设备。
  • 下面是要替代的可自定义方法:

    在仪表板中使用小组件

    小组件在仪表板上显示可配置的信息和图表。 它们显示在小组件板上,你可以在其中固定、取消固定、排列、调整小组件大小和自定义小组件以反映你的兴趣。 小组件板经过优化,可根据使用情况显示相关小组件和个性化内容。

    自定义小组件

    可以通过重写 类中的 widget 以下方法来自定义小组件:

  • 重写 headerContent()bodyContent()footerContent() 以自定义小组件。

    export class NewsWidget extends Widget<any, any> {
      headerContent(): JSX.Element | undefined {
        return (
            <News28Regular />
            <Text>Your News</Text>
            <Button icon={<MoreHorizontal32Regular />} appearance="transparent" />
      bodyContent(): JSX.Element | undefined {
        return (
          <div className="content-layout">
            <Image src="image.svg" className="img" />
            <Text className="title">Lorem Ipsum Dolor</Text>
            <Text className="desc">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Enim, elementum sed</Text>
      footerContent(): JSX.Element | undefined {
        return (
          <Button
            appearance="transparent"
            icon={<ArrowRight16Filled />}
            iconPosition="after"
            size="small"
            className="footer-button"
            onClick={() => { }} // navigate to detailed page
            View details
          </Button>
    
  • 重写 bodyContent()footerContent() 以自定义小组件。

    export class NewsWidget extends Widget<any, any> {
      bodyContent(): JSX.Element | undefined {
        return (
          <div className="content-layout">
            <Image src="image.svg" className="img" />
            <Text className="title">Lorem Ipsum Dolor</Text>
            <Text className="desc">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Enim, elementum sed</Text>
      footerContent(): JSX.Element | undefined {
        return (
          <Button
            appearance="transparent"
            icon={<ArrowRight16Filled />}
            iconPosition="after"
            size="small"
            className="footer-button"
            onClick={() => { }} // navigate to detailed page
            View details
          </Button>
    
    export class NewsWidget extends Widget<any, any> {
        bodyContent(): JSX.Element | undefined {
        return (
          <div className="content-layout">
            <Image src="image.svg" className="img" />
            <Text className="title">Lorem Ipsum Dolor</Text>
            <Text className="desc">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Enim, elementum sed</Text>
    

    包括数据加载程序

    如果要在加载小组件之前将数据加载程序包含在小组件中,可以向小组件的状态添加属性,以指示数据加载程序正在加载。 可以使用此属性向用户显示加载指示器。

    以下步骤演示如何将 属性添加到 的状态 ListWidget ,以及如何在加载数据时使用它来显示加载微调器。

  • 定义状态类型:定义状态类型,其中包括一个名为 loading 的属性,该属性指示数据是否正在加载。

    interface ListWidgetState {
      data: ListModel[];
      loading?: boolean;
    
  • 添加数据加载程序:修改 方法以 bodyContent 在加载数据时显示加载微调器。

    bodyContent(): JSX.Element | undefined {
      return (
          {this.state.loading !== false ? (
            <div className="loading-class-name">
              <Spinner label="Loading..." labelPosition="below" />
          ) : (
            <div className="list-body">
    
  • 如果正在加载数据,则隐藏页脚按钮。

    以下代码是页脚按钮的示例:

    footerContent(): JSX.Element | undefined {
      if (this.state.loading === false) {
        return (
          <Button
          </Button>
    
  • 更新状态引用:更新小组件文件中的状态引用以使用新的状态类型,并更新 方法中的 getData 状态,以在加载数据后将 loading 属性设置为 false

    现在,加载数据时会显示加载微调器。 加载数据时,加载微调器处于隐藏状态,并显示列表数据和页脚按钮。

    处理空状态

    当数据为空时,可以在小组件中显示特定内容。 为此,需要修改 bodyContent 小组件文件中的 方法,以采用数据的不同状态。

    以下示例演示如何在 的数据 ListWidget 为空时显示空图像。

    .empty-layout {
      display: grid;
      gap: 1rem
      justify-content: center;
      align-content: center;
    
    bodyContent(): JSX.Element | undefined {
      let hasData = this.state.data && this.state.data.length > 0;
      return (
        <div className="list-body">
          {hasData ? (
              {this.state.data?.map((t: ListModel) => {
          ) : (
            <div className="empty-layout">
              <Image src="empty-default.svg" height="150px" />
              <Text align="center">No data</Text>
    

    当数据为空时,可以使用类似的方法来删除小组件的页脚内容。

    footerContent(): JSX.Element | undefined {
      let hasData = this.state.data && this.state.data.length > 0;
      if (hasData) {
        return (
          <Button
          </Button>
    

    按计划刷新数据

    以下示例演示如何在小组件中显示实时数据。 小组件显示当前时间和更新。

    import { Widget } from "../lib/Widget";
    interface IRefreshWidgetState {
      data: string;
    export class RefreshWidget extends Widget<any, IRefreshWidgetState> {
      bodyContent(): JSX.Element | undefined {
        return <>{this.state.data}</>;
      async componentDidMount() {
        setInterval(() => {
          this.setState({ data: new Date().toLocaleTimeString() });
        }, 1000);
    

    可以修改 setInterval 方法以调用自己的函数来刷新数据,如下所示 setInterval(() => yourGetDataFunction(), 1000)

    小组件抽象

    为了简化小组件的开发,Teamsfx 提供了一个 widget 类,供开发人员继承以实现满足其需求的小组件,而无需过多关注实现小组件布局。

    以下代码是小组件类的示例:

    import "./Widget.css";
    import { Component } from "react";
    export abstract class Widget<P, T> extends Component<P, T> {
      constructor(props: any) {
        super(props);
        this.state = {} as T;
      async componentDidMount() {
        this.setState({ ...(await this.getData()) });
      render() {
        return (
          <div className="widget-root">
            {this.headerContent() && <div className="widget-header">{this.headerContent()}</div>}
            {this.bodyContent() && <div>{this.bodyContent()}</div>}
            {this.footerContent() && <div>{this.footerContent()}</div>}
      protected async getData(): Promise<T> {
        return new Promise<T>(() => {});
      protected headerContent(): JSX.Element | undefined {
        return undefined;
      protected bodyContent(): JSX.Element | undefined {
        return undefined;
      protected footerContent(): JSX.Element | undefined {
        return undefined;
    

    下面是替代的建议方法:

    Microsoft Graph 工具包作为小组件内容

    Microsoft Graph 工具包是一组可更新的、与框架无关的 Web 组件,可帮助访问和使用 Microsoft Graph。 可以将 Microsoft Graph 工具包与任何 Web 框架一起使用,也可以不使用框架。

    若要使用 Microsoft Graph 工具包作为小组件内容,请执行以下步骤:

  • 向 Teams 应用添加 SSO 功能:Microsoft Teams 为应用提供单一登录 (SSO) 功能,以获取登录的 Teams 用户令牌以访问 Microsoft Graph。 有关详细信息,请参阅 Teams 应用的 SSO 功能

  • 安装所需的 npm 包。

    在项目 tabs 文件夹中运行以下命令以安装所需的 npm 包:

    npm install @microsoft/mgt-react @microsoft/mgt-teamsfx-provider
    
  • 添加新的 Graph 工具包小组件:在项目选项卡/src/views/widgets 文件夹中创建新的小组件文件,例如 GraphyWidget.tsx。 在此小组件中,我们将引导用户同意我们的应用访问 Microsoft Graph,然后使用 Microsoft Graph 工具包显示用户 Todo 列表。

    以下代码是在小组件中使用 Todo Microsoft Graph 工具包中的组件的一个示例:

      import { Providers, ProviderState, Todo } from "@microsoft/mgt-react";
      import { TeamsFxProvider } from "@microsoft/mgt-teamsfx-provider";
      import { loginAction } from "../../internal/login";
      import { TeamsUserCredentialContext } from "../../internal/singletonContext";
      import { Widget } from "../lib/Widget";
      interface IGraphWidgetState {
        needLogin: boolean;
      export class GraphWidget extends Widget<any, IGraphWidgetState> {
        override bodyContent(): JSX.Element | undefined {
          return <div>{this.state.needLogin === false && <Todo />}</div>;
        async componentDidMount() {
          super.componentDidMount();
          // Initialize TeamsFx provider
          const provider = new TeamsFxProvider(TeamsUserCredentialContext.getInstance().getCredential(), [
            "Tasks.ReadWrite",
          Providers.globalProvider = provider;
          // Check if user is signed in
          if (await this.checkIsConsentNeeded()) {
            await loginAction(["Tasks.ReadWrite"]);
          // Update signed in state
          Providers.globalProvider.setState(ProviderState.SignedIn);
          this.setState({ needLogin: false });
         * Check if user needs to consent
         * @returns true if user needs to consent
        async checkIsConsentNeeded() {
          let needConsent = false;
          try {
            await TeamsUserCredentialContext.getInstance().getCredential().getToken(["Tasks.ReadWrite"]);
          } catch (error) {
            needConsent = true;
          return needConsent;
    

    有关详细信息,请参阅 Microsoft Graph 工具包

  • 将小组件添加到仪表板布局。 在仪表板文件中包含新小组件。

    export default class YourDashboard extends Dashboard { override dashboardLayout(): undefined | JSX.Element { return ( <GraphWiget />

    现在,启动或刷新 Teams 应用,你将看到使用 Microsoft Graph 工具包的新小组件。

    图形 API调用

    Microsoft 图形 API是一个 Web API,可用于与 Microsoft 云和其他服务进行通信。 自定义应用程序可以使用 Microsoft Graph API 连接到数据,并在自定义应用程序中使用它来增强组织工作效率。

    添加图形 API调用:

  • 从前端调用图形 API (使用委托的权限)
  • 从后端调用图形 API (使用应用程序权限)
  • 从前端调用图形 API (使用委托的权限)

    如果要从前端选项卡调用图形 API,请执行以下步骤:

  • 首先同意委托的权限:可以调用 addNewPermissionScope(scopes: string[]) 来同意要添加的权限范围。 同意的状态保留在全局上下文 FxContext中。

  • 通过添加与要调用图形 API相关的范围来创建 Graph 客户端。

    let teamsfx;
    teamsfx = FxContextInstance.getTeamsFx();
    const graphClient = createMicrosoftGraphClient(teamsfx, scope);
    
  • 调用图形 API并将响应分析为特定模型。

    try {
      const graphApiResult = await graphClient.api("<GRAPH_API_PATH>").get();
      // Parse the graphApiResult into a Model you defined, used by the front-end.
    } catch (e) {}
    

    从后端调用图形 API (使用应用程序权限)

    如果要从后端调用图形 API,请执行以下步骤:

  • 同意应用程序权限
  • 添加 Azure 函数
  • 在 Azure 函数中添加逻辑
  • 从前端调用 Azure 函数
  • 若要同意应用程序权限,请执行以下步骤:

  • 转到Azure 门户
  • 选择“Azure Active Directory”
  • 在左窗格中选择“应用注册”。
  • 选择仪表板应用。
  • 在左窗格中选择“ API 权限 ”。
  • 选择“ 添加权限”。
  • 选择 Microsoft Graph
  • 选择“应用程序权限”。
  • 查找所需的权限。
  • 选择底部的 “添加权限 ”按钮。
  • 选择“ ✔授予管理员同意”。
  • 选择“ ”按钮以完成管理员同意。
  • 添加 Azure 函数

    在Visual Studio Code的左窗格中,选择“Teams 工具包>添加功能>Azure Functions>并输入函数名称。

    在 Azure 函数中添加逻辑

    index.ts/index.ts在名为 Azure Function 的文件夹下,可以添加包含后端图形 API调用应用程序权限的逻辑。 请参阅以下代码片段:

    * This function handles requests from teamsfx client. * The HTTP request should contain an SSO token queried from Teams in the header. * Before triggering this function, teamsfx binding would process the SSO token and generate teamsfx configuration. * You should initializes the teamsfx SDK with the configuration and calls these APIs. * The response contains multiple message blocks constructed into a JSON object, including: * - An echo of the request body. * - The display name encoded in the SSO token. * - Current user's Microsoft 365 profile if the user has consented. * @param {Context} context - The Azure Functions context object. * @param {HttpRequest} req - The HTTP request. * @param {teamsfxContext} TeamsfxContext - The context generated by teamsfx binding. export default async function run( context: Context, req: HttpRequest, teamsfxContext: TeamsfxContext ): Promise<Response> { context.log("HTTP trigger function processed a request."); // Initialize response. const res: Response = { status: 200, body: {}, // Your logic here. return res;

    从前端调用 Azure 函数

    按函数名称调用 Azure 函数。 请参阅以下代码片段来调用 Azure 函数:

    const functionName = process.env.REACT_APP_FUNC_NAME || "myFunc";
    async function callFunction(teamsfx) {
      if (!teamsfx) {
        throw new Error("TeamsFx SDK is not initialized.");
      try {
        const credential = teamsfx.getCredential();
        const apiBaseUrl = teamsfx.getConfig("apiEndpoint") + "/api/";
        // createApiClient(...) creates an Axios instance which uses BearerTokenAuthProvider to inject token to request header
        const apiClient = createApiClient(
          apiBaseUrl,
          new BearerTokenAuthProvider(
            async () => (await credential.getToken(""))!.token
        const response = await apiClient.get(functionName);
        return response.data;
      } catch (e) {}
    

    有关更多信息,请参阅:

  • 开发人员指南
  • 嵌入 Power BI 以仪表板

    若要将 Power BI 嵌入到仪表板,请参阅 Power BI 客户端响应

    按照分步指南生成仪表板,并了解如何向仪表板添加小组件和图形 API调用。

  • 什么是 Teams 选项卡
  • 选项卡的应用设计指南
  • Fluent UI 库
  • Fluent UI React图表示例
  •