仪表板是一种跟踪、分析和显示数据以深入了解组织或特定流程的工具。 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图表示例