在 Power Pages 中创建和部署单页应用程序

Power Pages支持集成使用下一代 AI 辅助工具(如GitHub Copilot)创建的单页应用程序(SPA)代码。 此功能使开发人员可以使用自然语言作为编码界面将基于组件的新式前端体验引入Power Pages。

通过引导、测试和优化 AI 生成的代码,制作者可以将注意力从重复的实现任务转移到更高层次的协调工作。 这支持更直观、更具创造性的开发,同时保持企业级的质量和标准。

本文介绍如何:

备注

  • SPA 站点是一个Power Pages站点,它完全在用户的浏览器(客户端呈现)中运行。 与传统Power Pages站点不同,SPA 站点只能通过源代码和命令行接口 (CLI) 工具进行管理。
  • Power Platform Git 集成不支持 Power Pages 中的单页应用程序(SPA)网站。

必备条件

开始之前,请确保您拥有:

  • 具有 admin 权限的Power Pages环境。
  • 已安装并验证 Power Platform CLI (PAC CLI) 版本 1.44.x 或更高版本。
  • 在版本 9.7.4.x 或更高版本上运行的 Power Pages 站点。
  • 允许在 Dataverse 环境中上传 JavaScript 文件。
  • 具有自定义前端项目的本地 Git 存储库,例如 React、Angular 或 Vue。

允许上传 JavaScript 文件

默认情况下,某些 Dataverse 环境会阻止上传 JavaScript () 文件。 如果遇到 错误“导入失败:附件不是有效类型或太大。无法上传或下载它。“请更新环境设置以允许此文件类型。

要调整环境 Power Platform 管理中心的设置,请按照以下步骤操作:

  1. 登录到 Power Platform 管理中心。
  2. 在导航窗格中,选择“ 管理”。
  3. 在管理窗格中,选择环境。
  4. 选择环境。
  5. 在命令栏中,选择设置。
  6. 展开产品,然后选择隐私和安全性。
  7. 在 “阻止的附件 ”部分中,从文件扩展名列表中删除 。
  8. 选择“保存”。

创建和部署 SPA 站点

Power Pages SPA 站点使用 PAC CLI 命令upload-code-sitedownload-code-site进行管理。 上传网站后,它将显示在 Power Pages非活动网站列表中。 激活站点以使其可供用户使用。

上传 SPA 站点

使用 pac pages upload-code-site 命令将本地源和已编译的资产上传到Power Pages环境。

语法

pac pages upload-code-site `
  --rootPath <local-source-folder> `
  [--compiledPath <build-output-folder>] `
  [--siteName <site-display-name>]

参数设置

参数 别名 需要 描述
--rootPath -rp 包含站点源文件的本地文件夹
--compiledPath -cp 编译后资源的路径,如 React
--siteName -sn Power Pages网站的显示名称

示例

pac pages upload-code-site `
  --rootPath "../your-project" `
  --compiledPath "./build" `
  --siteName "Contoso Code Site"

如果没有现有项目,请尝试 使用 React、Angular 和 Vue 的 SPA 站点的示例实现。

使用定义上传参数

通过在网站中包含一个 文件来自定义 命令的行为。 将此配置文件放在站点根文件夹中。 当您使用基于配置的网站上传时,只需使用 参数运行 命令。 该命令会自动从 文件中读取其他值,如编译后资源路径和网站显示名称。 如果同时提供命令行参数和配置值,命令行参数优先。

示例:

{
  "siteName": "Contoso Bank",
  "defaultLandingPage": "index.html",
  "compiledPath": "C:\\PowerPages\\your-project\\dist"
}

下载 SPA 站点

使用 pac pages download-code-site 命令将现有网站的代码下载到本地目录,以便编辑或备份。

语法

pac pages download-code-site `
  [--environment <env-url-or-guid>] `
  --path <local-target-folder> `
  --webSiteId <site-guid> `
  [--overwrite]

参数设置

参数 别名 需要 描述
--environment -env Dataverse 环境(GUID 或完整 URL)。 默认为活动身份验证配置文件
--path -p 用于下载网站代码的本地目录
--webSiteId -id Power Pages SPA 站点的网站记录 GUID
--overwrite -o 覆盖目标目录中的现有文件(如果存在)

示例

pac pages download-code-site `
  --environment "https://contoso.crm.dynamics.com" `
  --path "./downloaded-site" `
  --webSiteId "11112222-bbbb-3333-cccc-4444dddd5555" `
  --overwrite

激活并测试您的网站

  1. 转到 Power Pages
  2. 选择非活动网站,找到您的网站,然后选择重新激活。
  3. 当站点处于活动状态时,转到站点的 URL 检查部署情况。

提示

任何后续的 命令都会自动更新活动网站。

项目结构和配置

一致的项目布局帮助确保正确的上传行为。

/your-project
│
├─ src/                       ← Your source code, like React components
├─ build/                     ← Compiled assets, output of the `npm run build` command
├─ powerpages.config.json     ← Optional CLI configuration file
└─ README.md

使用可选的 文件自定义 命令的工作方式。

身份验证和授权

Power Pages SPA 站点使用与传统Power Pages站点相同的安全模型

配置身份提供者

  1. 转到 Power Pages
  2. 找到您的网站并选择编辑。
  3. 选择 安全标识提供者。
  4. 添加或设置 身份提供者,例如 Microsoft Entra ID。
  5. 每个新站点天然有一个默认 Microsoft Entra ID 提供者。

在代码中访问用户上下文

在客户端上获取身份验证元数据:

  • 机构 URL:

    Microsoft Entra ID的颁发机构或登录 URL 为:

    https://login.windows.net/<tenantId>
    
  • 要查找其他已配置标识提供者的机构 URL,转到 Power Pages><your site>>安全性>标识提供者>配置设置。

  • 用户详细信息:

    window["Microsoft"].Dynamic365.Portal.User
    

示例 React 流

import { IconButton, Tooltip } from '@mui/material';
import {
    Login,
    Logout
} from '@mui/icons-material';
import React from 'react';
export const AuthButton = () => {
    const username = (window as any)["Microsoft"]?.Dynamic365?.Portal?.User?.userName ?? "";
    const firstName = (window as any)["Microsoft"]?.Dynamic365?.Portal?.User?.firstName ?? "";
    const lastName = (window as any)["Microsoft"]?.Dynamic365?.Portal?.User?.lastName ?? "";
    const tenantId = (window as any)["Microsoft"]?.Dynamic365?.Portal?.tenant ?? "";
    const isAuthenticated = username !== "";
    const [token, setToken] = React.useState<string>("");

    React.useEffect(() => {
        const fetchAntiForgeryToken = async (): Promise<string> => {
            try {
                const tokenEndpoint = "/_layout/tokenhtml";

                const response = await fetch(tokenEndpoint, {});

                if (response.status !== 200) {
                    throw new Error(`Failed to fetch token: ${response.status}`);
                }

                const tokenResponse = await response.text();                
                const valueString = 'value="';
                const terminalString = '" />';
                const valueIndex = tokenResponse.indexOf(valueString);

                if (valueIndex === -1) {
                    throw new Error('Token not found in response');
                }

                const requestVerificationToken = tokenResponse.substring(
                    valueIndex + valueString.length,
                    tokenResponse.indexOf(terminalString, valueIndex)
                );

                return requestVerificationToken || '';
            } catch (error) {
                console.warn('[Impersonation] Failed to fetch anti-forgery token:', error);
                return '';
            }
        };

        const getToken = async () => {
            try {
                const token = await fetchAntiForgeryToken();
                setToken(token);
            } catch (error) {
                console.error('Error fetching token:', error);
            }
        };
        getToken();
    }, []);

    return (
        <div className="flex items-center gap-4">
            {isAuthenticated ? (
                <>
                    <span className="text-sm">Welcome {firstName + " " + lastName}</span>
                    <Tooltip title="Logout">
                        <IconButton color="primary" onClick={() => window.location.href = "/Account/Login/LogOff?returnUrl=%2F"}>
                            <Logout />
                        </IconButton>
                    </Tooltip>
                </>
            ) : (
                <form action="/Account/Login/ExternalLogin" method="post">
                    <input name="__RequestVerificationToken" type="hidden" value={token} />
                    <Tooltip title="Login">
                        <IconButton name="provider" type="submit" color="primary" value={`https://login.windows.net/${tenantId}/`}>
                            <Login />
                        </IconButton>
                    </Tooltip>
                </form>
            )}
        </div>
    );
};

使用 Power Pages Web API

开发人员可以利用 Power Pages Web API 将内容加载到 UI 或创建、更新和删除记录。 在使用这些 API 之前,确保已启用所需的 Web API,并正确配置了相应的表权限和 Web 角色。


// Create query to get all cards from Dataverse
const fetchCards = async () => {
    const response = await fetch("/_api/cr7ae_creditcardses");
    const data = await response.json();
    const cards = data.value;
    const returnData = [];

    // Loop through the cards and get the name and id of each card
    for (let i = 0; i < cards.length; i++) {
        const card = cards[i];
        const cardName = card.cr7ae_name;
        const cardId = card.cr7ae_creditcardsid;
        const features = card.cr7ae_features
            ?.split(',')
            .map((feature: string) => feature.trim());
        const type = card.cr7ae_type;
        const image = card.cr7ae_image;
        const category = card.cr7ae_category
            ?.split(',')
            .map((cat: string) => cat.trim());
        
        // ...additional processing/pushing to returnData...
    }

    return returnData;
};

通过启用使用 Microsoft Entra ID 身份验证的 localhost Web API 调用,来设置本地开发。

生成应用程序时,开发人员需要更快的迭代周期、本地调试和热重载功能。 SPA 通过使用 Microsoft Entra ID (Azure AD) v1 身份验证,支持从 localhost 进行安全的 Web API 调用,以实现这些工作流。

通过此设置,你可以:

  • 使用完全身份验证支持在本地运行应用。
  • 使用 Vite 等新式开发工具进行热重载和快速反馈。
  • 在调用 Power Pages Web API 时避免 CORS 问题。
  • 无需将更改部署到门户即可加速开发。

此配置可为 SPA 提供高效的本地开发体验,使开发人员能够快速生成、测试和迭代,并提供完整的 API 访问和身份验证支持。

重要提示

  • 仅使用 Microsoft Entra v1 端点进行身份验证。
  • 持有者身份验证仅在门户版本 9.7.6.6 或更高版本中受支持。
  • 仅在开发环境中应用这些设置。

配置步骤

  1. 启用 SPA 身份验证

    1. https://portal.azure.com 中,打开在门户中已注册的 Microsoft Entra 应用程序。
    2. 启用 单页应用程序(SPA) 身份验证。
    3. 使用平台配置添加为重定向 URI。 有关更多详细信息 ,请参阅如何在应用程序中添加重定向 URI 。
      • 重定向 URI: 。
  2. 添加网站设置

    Authentication/BearerAuthentication/Enabled = true
    Authentication/BearerAuthentication/Protocol = OpenIdConnect
    Authentication/BearerAuthentication/Provider = AzureAD
    
  3. 使用 ADAL.js 登录

    • 使用 ADAL.js实现客户端登录。

    备注

    MSAL.js 不兼容,因为 Power Pages 使用 Microsoft Entra v1 终结点,而 MSAL 使用 v2。 颁发者格式因版本而异。

  4. 添加授权标头

    • 在所有 Web API 请求中包含此标头:
    Authorization: Bearer <id_token>
    
  5. 将网站可见性设置为“公共”

    • 此设置允许 访问站点以进行开发和测试。
  6. 配置开发代理

    • 如果您使用 Vite,请在 中添加此项以避免 CORS 问题:
    export default defineConfig({
      plugins: [react()],
      server: {
        proxy: {
          '/_api': {
            target: 'https://site-foo.powerappsportals.com',
            changeOrigin: true,
            secure: true
          }
        }
      }
    });
    

与现有Power Pages网站的差异

下表总结了使用此功能创建的 SPA 站点与传统Power Pages站点之间的主要差异:

特性 SPA 站点行为
服务器端刷新 始终返回站点的根页面,客户端路由器呈现子路由。
路由冲突 客户端路由优先,硬刷新回退到根。
页面工作区 页面工作区不受支持。 使用客户端路由和客户端站点页面。 为了达到页面级安全性,使用全局用户对象检查分配的 Web 角色,并有条件地呈现 UI。
样式工作区 使用样式工作区进行样式设置不被支持。 使用框架的样式,如 CSS、CSS-in-JS 或实用类。
本地化 仅支持单一语言。 实现客户端资源加载。
Liquid 模板化 Liquid 代码和 Liquid 模板不被支持。 使用框架的模板引擎和 Web API 访问数据。

FAQ

单元测试和集成测试提供哪些支持?

目前,没有对单元和集成测试的内置支持。 制作者应在本地或 CI/CD 管道中编写并执行这些测试。

是否支持使用 WebAssembly 进行 Power Fx 集成?

此功能目前不受支持。

源代码在Power Pages中是否可用?

目前,制造商可以使用 TypeScript 或 GitHub Copilot 代理生成网站。 编译的 JavaScript 和 CSS 文件可访问,可在Visual Studio Code中编辑。 但是,目前不支持对 HTML 文件进行直接和广泛的编辑。

是否可以使用此功能在外部创建组件并将其引入Power Pages站点?

否,无法使用此功能将外部生成的组件引入现有Power Pages站点。

我可以添加现成的组件,如列表和窗体吗?

当前不支持添加现成的组件,如列表和窗体。 不过,您可以使用 React 框架和 Web API 构建自定义窗体和列表。

源代码管理是如何工作的?

开发人员可以使用 Power Platform Git 集成进行源代码管理。 然而,只有编译后的网页文件会被添加到仓库中,而非完整的源代码。

这些站点支持 SEO 吗?

由于 SPA 站点是使用 React 框架构建并使用客户端呈现的,因此 SEO 支持有限。

SPA 站点提供哪些 Power Pages 安全和治理支持?

Power Pages在 Web API 调用中强制实施表权限和安全 Web 角色,确保数据访问与用户角色保持一致。 使用 对象检索基本用户属性并根据用户角色定制体验。

此外,SPA 网站支持:

  • 公共和私有网站配置
  • 治理设置,包括对匿名数据访问的控制
  • 身份验证提供程序配置

这些功能有助于确保Power Pages中自定义组件的安全性和合规集成。