mcpField
C
commerce-app-admin-ui

commerce-app-admin-ui

github.com/adobe开发316282 次安装今天更新
代码仓库

关于 commerce-app-admin-ui

commerce-app-admin-ui 用于配置 Adobe Commerce 应用的 Admin UI 扩展,可通过应用配置和生成的扩展脚手架添加网格列、操作、订单按钮、菜单或自定义 ACL 资源。

主要功能

  1. 01为订单、产品和客户配置网格列
  2. 02添加批量操作和订单视图按钮
  3. 03添加 Admin UI 菜单项
  4. 04声明自定义 ACL 资源
  5. 05生成前端路由和 worker 处理程序

适用场景

  1. 01在 Commerce 管理网格中添加自定义数据
  2. 02为管理实体创建批量操作
  3. 03从 Admin UI 菜单打开应用视图
  4. 04定义应用专属的管理权限

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/adobe/skills --skill commerce-app-admin-ui

安装后在 Claude Code 中输入/commerce-app-admin-ui确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
custom-acl.md
grid-columns.md
mass-actions.md
menu.md
order-view-buttons.md

详细介绍

配置 Commerce 应用管理界面

在现有的 app.commerce.config.ts 中添加或修改 adminUi 块。 管理界面扩展点(commerce/backend-ui/2)允许 Commerce 应用通过自定义网格列、批量操作、订单视图按钮、菜单项和自定义 ACL 资源扩展 Commerce 管理后台。 其他扩展领域(webhook、事件、业务配置)需通过各自的技能单独添加。

前提条件

  • 确认应用已搭建并完成初始化,而不只是配置文件存在。必须同时满足以下两项:
    • 项目根目录中存在 app.commerce.config.ts,并且
    • 项目已初始化——这由生成的 src/commerce-extensibility-1/ 目录和已安装的 node_modules(@adobe/aio-commerce-lib-app 依赖)表明。
  • 如果缺少 app.commerce.config.ts,请停止并先调用 commerce-app-init(它会写入配置,然后运行初始化)。
  • 如果配置文件存在但项目尚未初始化(没有 src/commerce-extensibility-1/ 或 node_modules),请先运行 npx @adobe/aio-commerce-lib-app init,然后再继续。初始化操作具有幂等性——它会找到现有配置,跳过交互式提示,安装依赖并生成项目文件。
  • 只有项目具备用于 TypeScript Commerce 配置的 TypeScript 构建设置(init 会为其搭建 webpack-config.cjs 和根目录下的 tsconfig.json)后,才能用 TypeScript 编写操作——请参阅 commerce-app-init。否则,请使用 JavaScript 编写操作。

扩展点一览

扩展点实体变体服务端处理程序参考
网格列order、product、customer仅 worker是grid-columns
批量操作order、product、customerview / worker仅 workermass-actions
订单视图按钮仅 orderview / worker仅 workerorder-view-buttons
菜单单个条目(adminUi.menu)view(iframe)否menu
自定义 ACL列表(adminUi.acl)无否custom-acl

view 会在应用 Web UI(web-src)中将 iframe 渲染到条目的 path;worker 会在服务端调用运行时操作。 自定义 ACL 资源没有变体,也没有处理程序:它们是 Commerce 在用户角色树中呈现的独立权限,由应用自行检查(请参阅 custom-acl)。 网格列始终使用 worker;菜单始终使用 iframe。

步骤 1 — 了解需求

对于用户想要添加的每一项,请收集以下信息:

  • 扩展点——网格列、批量操作、订单视图按钮、菜单或自定义 ACL 资源
  • 实体——order、product 或 customer(网格列和批量操作适用;视图按钮仅适用于订单;菜单和自定义 ACL 资源没有实体)
  • 对于批量操作和视图按钮,选择变体——worker(运行时操作)或 view(嵌入 web-src 的 iframe)
  • 该扩展点所需的字段(列定义、按钮标签、菜单父项等)——完整字段列表请参阅上表中的参考文件

步骤 2 — 在 app.commerce.config.ts 中声明

在顶层 adminUi 块中添加(或合并),并保留所有其他域。如果 adminUi 已存在,请合并到其中,而不是替换它——保留现有实体、菜单和现有数组条目。

这些字段在各个扩展点之间通用:

字段限制
runtimeAction<package>/<action>;必须解析为你声明的处理程序操作(步骤 4)
confirm可选的 { title?, message? } — 操作运行前显示确认对话框
notifications可选的 { success?, error? } — Commerce 在操作完成时显示的提示文本
sandboxPermissions可选数组(适用于 view/iframe 条目);不能为空,不能有重复项;每项必须是 allow-downloads、allow-modals、allow-popups 之一

涵盖各项功能的最简示例(仅使用你需要的部分):

import { MENU_SALES } from "@adobe/aio-commerce-sdk/admin-ui/menu";

// inside defineConfig({ ... }):
adminUi: {
  order: {
    // Custom column on the order grid (worker only)
    gridColumns: {
      label: "Fulfillment data",
      description: "Fulfillment status from the warehouse system.",
      runtimeAction: "my-app/order-grid", // <package>/<action> — declare in Step 4
      columns: [
        { id: "fulfillment_status", label: "Fulfillment", type: "string", align: "left" },
      ],
    },
    // Bulk action on selected orders (worker variant shown)
    massActions: [
      { type: "worker", id: "archive-orders", label: "Archive",
        runtimeAction: "my-app/archive-orders", selectionLimit: 500 },
    ],
    // Button on the order view page (worker variant shown)
    viewButtons: [
      { type: "worker", id: "sync-inventory", label: "Sync inventory",
        runtimeAction: "my-app/sync-inventory" },
    ],
  },
  // Custom Admin menu entry (iframe into the app)
  menu: {
    id: "my_app_dashboard", // letters, digits, / : _ only
    label: "My Dashboard",
    description: "Custom dashboard for my app.",
    parentMenu: MENU_SALES,
  },
  // Custom ACL resources — standalone permissions the app checks itself (no handler)
  acl: [
    { id: "reports", label: "Reports", children: [
      { id: "export", label: "Export" },
    ]},
    { id: "approve_refunds", label: "Approve Refunds" },
  ],
}

对于 view 变体(iframe),以及各个扩展点的完整字段集和限制,请先阅读 References 下对应的参考文件,再进行编写。

步骤 3 — 注册扩展点

运行 init,将 commerce/backend-ui/2 添加到 app.config.yaml 和 install.yaml,并生成 src/commerce-backend-ui-2/ 扩展文件夹。此操作具有幂等性,即使扩展已注册,也可以安全运行。

npx @adobe/aio-commerce-lib-app init

构建过程会根据你的 adminUi 配置生成扩展的 ext.config.yaml:每个 worker runtimeAction 都会变成一个 workerProcess 操作;当你声明 menu 或任何 view 类型的条目时,则会写入一个 view 操作和显式的 web: web-src 键。这些 hooks、operations 和 web 部分由库管理,请勿手动编辑。

存在 view 操作时,init/generate 还会自动搭建 Web 前端(如果 web-src/index.html 已存在,则跳过)。它会生成 src/commerce-backend-ui-2/web-src/,其中包含 index.html、src/app.jsx、src/pages/main-page.jsx、src/components/welcome.jsx(如果应用配置使用 TypeScript,则生成 .tsx 文件和独立的 tsconfig.json);向 package.json 添加 #web/* 导入别名;声明并安装固定版本的 react、react-dom、@react-spectrum/s2 和 @adobe/aio-commerce-lib-admin-ui(React 和 Spectrum S2 是 admin-ui 库的可选对等依赖项);此外还会添加一些 devDependencies,以提供正确的 TypeScript 和 Babel 支持/配置。每次生成时都会单独运行一个必需文件阶段,以确保一组 Web 源代码支持文件存在,同时不替换现有版本。当前文件集包含 web-src/.babelrc,它会为每个环境选择 React 的自动 JSX 转换,从而在开发构建中保留 JSX 诊断,而在生产构建中不生成 jsxDEV 调用。如果设置了 BABEL_ENV,请确保它与 NODE_ENV 保持同步,因为 Babel 选择配置环境时会优先使用 BABEL_ENV。TypeScript 脚手架还会向项目组合后的 typecheck 脚本添加 typecheck:web-src。请勿自行选择这些依赖项的其他版本;如果已安装的版本不兼容,脚手架会失败。

如果因 web-src 已存在而跳过脚手架,请检查其中的 package.json,确认是否使用了经典版 React Spectrum(@adobe/react-spectrum 或不带 s2 的 @react-spectrum/<component>),而非 @react-spectrum/s2。两者兼容,但 Adobe 建议迁移到 S2,本脚手架也以 S2 为目标版本,因此请建议升级。此技能仅配置 Admin UI 扩展,不会执行升级本身,因此请引导用户使用 commerce-app-migrate 技能来实际迁移:

npx skills add adobe/skills --skill commerce-app-migrate

除非用户提出要求,否则不要安装它或自行执行升级。

步骤 4 — 实现处理程序

具体实现取决于变体。以下示例使用 TypeScript;如果项目使用 JavaScript,请省略类型导入和类型注解。

Worker 变体(网格列、worker 批量操作、worker 视图按钮)

每个 worker runtimeAction 都需要一个在 Admin UI 扩展文件夹 src/commerce-backend-ui-2/ 中声明并实现的操作。

  1. 将处理程序源代码放在 src/commerce-backend-ui-2/actions/ 下。
  2. 在 src/commerce-backend-ui-2/ext.config.yaml 中你自己的包下的 runtimeManifest 中声明它(包名任意)。构建过程会管理此文件中的 hooks 和 operations,但会保留你在 runtimeManifest 下添加的包,因此你的操作在重新构建后仍会保留:
# src/commerce-backend-ui-2/ext.config.yaml
runtimeManifest:
  packages:
    my-app: # must match the <package> in runtimeAction
      actions:
        order-grid:
          function: actions/order-grid/index.js # relative to src/commerce-backend-ui-2/
          web: "yes"
          runtime: nodejs:24
          annotations:
            require-adobe-auth: true # Commerce calls the action with an IMS token — validate it
            final: true

runtimeAction 中的 <package>/<action> 会直接映射:my-app/order-grid → 包 my-app,操作 order-grid。Commerce 会使用 Adobe IMS 令牌调用这些 worker 操作,因此它们需要 require-adobe-auth: true(以及 final: true,以锁定绑定的输入)。

请使用 @adobe/aio-commerce-sdk/admin-ui/* 入口提供的 wire-contract 构建器实现处理程序。 不同扩展点对应不同的构建器——参考文件会给出确切的请求格式和响应构建器。网格列示例:

// src/commerce-backend-ui-2/actions/order-grid/index.ts
import {
  parseGridRequest,
  okGridResponse,
  errorGridResponse,
} from "@adobe/aio-commerce-sdk/admin-ui/grid-columns";
import type { RuntimeActionParams } from "@adobe/aio-commerce-sdk/core/params";

export async function main(params: RuntimeActionParams) {
  const { gridType, ids } = parseGridRequest(params);
  try {
    const rows = await fetchRows(gridType, ids);
    // row keys must match the column ids declared in config
    return okGridResponse(rows, { fulfillment_status: "unknown" });
  } catch (error) {
    return errorGridResponse(
      500,
      error instanceof Error ? error.message : String(error),
    );
  }
}
视图变体(视图批量操作、视图按钮、菜单)

无需服务器处理程序。Commerce 会在条目 path 指定的位置,将 iframe 加载到应用的 web-src 中;该前端已在步骤 3 中为你生成(index.html、src/app.jsx、src/pages/main-page.jsx、src/components/welcome.jsx)。生成的 src/app.jsx 使用 @adobe/aio-commerce-lib-admin-ui/web 中的 createExtensionApp 挂载 commerce/backend-ui/2 iframe 应用,并且其 routes 数组必须以索引路由开头:

// src/commerce-backend-ui-2/web-src/src/app.jsx (generated)
import { createExtensionApp } from "@adobe/aio-commerce-lib-admin-ui/web";
import "@react-spectrum/s2/page.css";

import config from "#app.commerce.config";
import { MainPage } from "#web/pages/main-page.jsx";

createExtensionApp({
  metadata: { extensionId: config.metadata.id },
  routes: [{ index: true, element: <MainPage /> }],
});

与工作器变体将 runtimeAction 绑定到 action 一样,每个 view 条目的 path 都必须绑定到页面和路由。为每个条目搭建这一绑定,但仅创建缺少的内容。切勿覆盖或修改现有页面或路由。

菜单没有 path:它会在索引路由(src/pages/main-page.jsx)中呈现,该页面已由脚手架创建为普通页面。无需为菜单搭建任何内容——保留 main-page.jsx(你可以按需自定义其内容)。它只需要在配置中使用 MENU_* 常量来设置 parentMenu(参见 菜单)。

对于每个 view 类型的批量操作和订单视图按钮(两者都有 path):

  1. 检查路由。 在 src/app.jsx 中查找 routes 条目,其 path 是否与条目的配置 path 相同;并在 web-src/src/pages/ 中查找对应的页面文件。如果该 path 已有路由,则保留该路由及其页面,不做更改,然后继续处理下一个条目。

  2. 创建占位页面(仅在缺少时)。 添加 web-src/src/pages/<name>.jsx;如果应用配置使用 TypeScript,则使用 .tsx(和 TypeScript)。保持简洁,并与生成的 main-page/welcome 风格一致(包含一个标题的 <main>)。不要添加版权声明——生成的 web-src 文件中没有此类声明。根据条目类型预先接入上下文钩子(见下表)。

  3. 在 src/app.jsx 中注册路由(仅在缺少时)。 通过 #web/pages/* 别名导入页面,并将 { path, element } 条目追加到 routes。path 必须与配置中的 path 完全相同,包括开头的 #/——原样复制,以便路由与配置条目清晰对应。保持索引路由位于首位。

根据视图类型预先接入钩子——所有钩子都来自 @adobe/aio-commerce-lib-admin-ui/web:

视图条目上下文钩子另外参考
批量操作(view)useMassActionContext() → { data, error };使用 data.selectedIdsuseHostConnection() → { actions, error };使用 actions.close()批量操作
订单视图按钮useOrderViewButtonContext() → { data, error };使用 data.orderIduseHostConnection() → { actions, error };使用 actions.close()订单视图按钮
菜单无(普通索引页面)—菜单

这些钩子会返回错误,而不是抛出错误。路由可以在渲染期间抛出返回的错误,将其交给 SDK 的错误边界;错误边界会用回退 UI 替换扩展内容。如果路由必须保持挂载状态,请在本地处理错误,例如呈现提示消息、提供重试选项、禁用受影响的功能,或提供其他降级状态。下面的占位示例会抛出错误,因为它们没有定义自定义恢复 UI。 路由组件还可以调用 useIms() 和 useCommerce(),分别访问 Commerce REST API 或直接从宿主获取数据。两者都返回 { data, error };处理好 error 后,从 useIms() 读取 data.imsToken 和 data.imsOrgId,并从 useCommerce() 读取 data.commerceHost。生成的 Welcome 组件演示了如何处理 useIms() 的结果。只有当页面确实需要时,才添加这些钩子。

示例 — view 批量操作占位页面及其路由注册:

// src/commerce-backend-ui-2/web-src/src/pages/export-customers.jsx
import {
  useHostConnection,
  useMassActionContext,
} from "@adobe/aio-commerce-lib-admin-ui/web";

export function ExportCustomersPage() {
  const { data, error: contextError } = useMassActionContext();
  const { actions, error: hostError } = useHostConnection();
  if (contextError) throw contextError;
  if (hostError) throw hostError;

  const { selectedIds } = data; // non-empty string[] — the selected record ids
  const { close } = actions; // await close() (or actions.closeWithError()) when done

  return (
    <main>
      <h1>Export customers</h1>
      <p>{selectedIds.length} selected</p>
    </main>
  );
}
// src/commerce-backend-ui-2/web-src/src/app.jsx
import { createExtensionApp } from "@adobe/aio-commerce-lib-admin-ui/web";
import "@react-spectrum/s2/page.css";

import config from "#app.commerce.config";
import { MainPage } from "#web/pages/main-page.jsx";
import { ExportCustomersPage } from "#web/pages/export-customers.jsx";

createExtensionApp({
  metadata: { extensionId: config.metadata.id },
  routes: [
    { index: true, element: <MainPage /> }, // keep the index route first
    { path: "#/export-customers", element: <ExportCustomersPage /> }, // path === config `path`
  ],
});

对于订单查看按钮,将钩子替换为 useOrderViewButtonContext(),并在处理好 error 后读取 data.orderId — 参见 order-view-buttons。

从菜单/视图页面调用自己的操作

菜单或视图页面有时需要获取自己的数据 — 例如,自定义菜单页面渲染表格 — 而不是从 Commerce 接收数据。为此,它会直接从 web-src 调用应用自己的某个操作。

只要扩展同时声明了 actions 和 web-src,aio app build/aio app dev 就会自动生成 web-src/src/config.json(config.web.injectedConfig),其中包含每个操作的实时 URL — 此文件会被 git 忽略,并在每次构建时重新生成,无需手动搭建任何内容:

// src/commerce-backend-ui-2/web-src/src/hooks/use-config.js
import { useCallback } from "react";
import actionUrls from "../config.json";

export function useConfig() {
  return { getActionUrl: useCallback((action) => actionUrls[action], []) };
}

// Usage — combine with useIms() for the auth headers:
import { useIms } from "@adobe/aio-commerce-lib-admin-ui/web";

const { data, error } = useIms();
if (error) throw error;

const response = await fetch(getActionUrl("my-app/order-grid"), {
  headers: {
    authorization: `Bearer ${data.imsToken}`,
    "x-gw-ims-org-id": data.imsOrgId, // required for require-adobe-auth: true actions, or the request fails
  },
});

步骤 5 — 验证

构建项目以确认更新后的配置有效:

aio app build

如果构建失败并出现验证错误,错误会直接指出有问题的 adminUi 字段。

常见问题

  • view 与 worker 不匹配:每个变体都是严格对象 — worker 条目必须包含 runtimeAction(且不能包含 path/sandboxPermissions);view 条目必须包含 path(且不能包含 runtimeAction/timeout)。两者通过 type 区分。
  • 网格列仅适用于 worker:没有 view 网格列。只有 order、product 和 customer 支持 gridColumns,且只有 order 支持 viewButtons。
  • runtimeAction 没有处理程序:如果某个 worker 条目的 <package>/<action> 未在 src/commerce-backend-ui-2/ext.config.yaml 的 runtimeManifest 下声明,部署时生成的 workerProcess 引用将无法解析。
  • 操作位置错误:处理程序源文件及其 runtimeManifest 条目应位于 Admin UI 扩展文件夹 src/commerce-backend-ui-2/ 中 — 而不是 src/commerce-extensibility-1/(webhook 和事件处理程序所在的位置)。function 路径相对于 src/commerce-backend-ui-2/。
  • 网格行键必须与列 ID 匹配:okGridResponse 行中的键必须与 gridColumns.columns 中的 id 相同,否则单元格将显示为空(或回退到默认值对象)。
  • 视图路由 path:在 src/app.jsx 中注册的 { path } 必须与条目配置中的 path 完全相同 — 原样复制,包括井号,确保两者一致。
  • 菜单 id 字符集:菜单 id 只允许使用字母、数字、/、: 和 _ — 不能使用连字符或空格。
  • 找不到 defineConfig:从 @adobe/aio-commerce-lib-app/config 导入 defineConfig。
  • 开发环境中的重复渲染/请求:由 aio app dev 和 aio app run 提供服务的本地构建属于开发构建,createExtensionApp 会将应用包装在 React <StrictMode> 中 — 因此组件会渲染两次,挂载时效果会额外执行一轮设置和清理。开发期间出现重复渲染或由效果触发的请求是 StrictMode 的预期行为,并非需要修复的错误。aio app build 和 aio app deploy 会生成生产构建,渲染时不包含 <StrictMode>(它会从构建包中移除)。

质量标准

  • aio app build 成功完成且无错误
  • 每个 worker runtimeAction 都有对应的操作,且该操作已在 src/commerce-backend-ui-2/ext.config.yaml 的 runtimeManifest 下声明

链接后续功能

aio app build 通过后:

  • 添加商户设置 — 调用 commerce-app-business-config,在 Commerce Admin 中公开可配置的设置
  • 添加 Webhook 拦截器 — 调用 commerce-app-webhooks,拦截 Commerce 操作
  • 添加事件订阅 — 调用 commerce-app-eventing,订阅 Commerce 或外部事件
  • 添加持久化存储 — 调用 commerce-app-storage,为工作进程操作提供可查询的数据库存储

参考资料

评论

支持 Markdown