关于 commerce-app-admin-ui
commerce-app-admin-ui 用于配置 Adobe Commerce 应用的 Admin UI 扩展,可通过应用配置和生成的扩展脚手架添加网格列、操作、订单按钮、菜单或自定义 ACL 资源。
主要功能
- 01为订单、产品和客户配置网格列
- 02添加批量操作和订单视图按钮
- 03添加 Admin UI 菜单项
- 04声明自定义 ACL 资源
- 05生成前端路由和 worker 处理程序
适用场景
- 01在 Commerce 管理网格中添加自定义数据
- 02为管理实体创建批量操作
- 03从 Admin UI 菜单打开应用视图
- 04定义应用专属的管理权限
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ adobe/ skills --skill commerce-app-admin-ui安装后在 Claude Code 中输入/commerce-app-admin-ui确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/custom-acl.mdgrid-columns.mdmass-actions.mdmenu.mdorder-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、customer | view / worker | 仅 worker | mass-actions |
| 订单视图按钮 | 仅 order | view / worker | 仅 worker | order-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/ 中声明并实现的操作。
- 将处理程序源代码放在
src/commerce-backend-ui-2/actions/下。 - 在
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):
-
检查路由。 在
src/app.jsx中查找routes条目,其path是否与条目的配置path相同;并在web-src/src/pages/中查找对应的页面文件。如果该path已有路由,则保留该路由及其页面,不做更改,然后继续处理下一个条目。 -
创建占位页面(仅在缺少时)。 添加
web-src/src/pages/<name>.jsx;如果应用配置使用 TypeScript,则使用.tsx(和 TypeScript)。保持简洁,并与生成的main-page/welcome风格一致(包含一个标题的<main>)。不要添加版权声明——生成的web-src文件中没有此类声明。根据条目类型预先接入上下文钩子(见下表)。 -
在
src/app.jsx中注册路由(仅在缺少时)。 通过#web/pages/*别名导入页面,并将{ path, element }条目追加到routes。path必须与配置中的path完全相同,包括开头的#/——原样复制,以便路由与配置条目清晰对应。保持索引路由位于首位。
根据视图类型预先接入钩子——所有钩子都来自 @adobe/aio-commerce-lib-admin-ui/web:
| 视图条目 | 上下文钩子 | 另外 | 参考 |
|---|---|---|---|
批量操作(view) | useMassActionContext() → { data, error };使用 data.selectedIds | useHostConnection() → { actions, error };使用 actions.close() | 批量操作 |
| 订单视图按钮 | useOrderViewButtonContext() → { data, error };使用 data.orderId | useHostConnection() → { 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,为工作进程操作提供可查询的数据库存储
参考资料
- references/grid-columns.md — 网格列配置和处理器契约
- references/mass-actions.md — 批量操作配置(视图和工作进程)及处理器契约
- references/order-view-buttons.md — 订单视图按钮配置(视图和工作进程)及处理器契约
- references/menu.md — 菜单配置和父级菜单常量
- references/custom-acl.md — 自定义 ACL 资源配置和运行时权限检查