Repository navigation
Frontend Admin Extensions
插件前端包可以声明管理后台子路由(adminRoutes)和管理操作(adminActions),扩展管理后台界面。
管理后台页面是客户端交互组件,必须放在插件的 frontend/src/admin.tsx,并以命名导出 adminPages 暴露:
// frontend/src/admin.tsx
'use client';
import type { AdminPageComponentProps } from '@stackpanel/sdk';
function AdminProductsPage(props: AdminPageComponentProps) {
const create = props.actions['store.create-product'];
// ...交互逻辑(useState / Dialog / 表单 action 等)
}
export const adminPages = {
'admin/products': AdminProductsPage,
} as const;pnpm build:frontend 检测到 src/admin.tsx 后,会额外编译出 dist/admin.js,并生成 apps/web/src/lib/frontend-client-registry.ts,由客户端组件 AdminPluginPage 按 component 名解析并渲染。
入口选择的三条规则:
-
adminRoutes[].component(如admin/products)必须与adminPages的键一一对应。 - 管理后台页面不要写在
src/index.tsx的ui.adminPages里。服务端dist/index.js打包的ui.adminPages不会被任何渲染方读取,页面会静默显示「未找到插件管理页面」。 - 非交互的 dashboard Widget(
ui.adminWidgets)仍由服务端渲染,可留在src/index.tsx。
声明插件在管理后台的子页面:
{
"adminRoutes": [
{
"path": "/my-plugin",
"component": "AdminHome",
"nav": { "label": "商品管理" }
},
{
"path": "/my-plugin/orders",
"component": "AdminOrders",
"nav": { "label": "订单管理" }
}
]
}| 字段 | 必填 | 说明 |
|---|---|---|
path |
是 | 路由路径,必须以 / 开头 |
component |
是 | 组件名 |
permission |
否 | 所需权限 |
data |
否 | Finder 数据绑定(同页面 data) |
nav |
否 | 菜单信息:{ label, group? }
|
管理后台路由在 /admin/* 下渲染。插件 adminRoute 的 path 会被映射到 /admin/plugin/<pluginId><path>。
例如插件 store 声明了 /products,则管理后台访问路径为 /admin/plugin/store/products。
管理后台路由需要 ADMIN 角色访问(由管理后台 proxy.ts 强制)。
管理路由渲染时写入审计日志 frontend.admin.route.render。
声明插件的快捷操作组件,展示在插件管理页和插件 admin 根页面:
{
"adminActions": [
{
"id": "sync-products",
"label": "同步商品",
"component": "SyncProductsAction",
"permission": "store.write"
},
{
"id": "export-orders",
"label": "导出订单",
"component": "ExportOrdersAction"
}
]
}| 字段 | 必填 | 说明 |
|---|---|---|
id |
是 | 唯一标识 |
label |
是 | 显示名 |
component |
是 | 组件名 |
permission |
否 | 所需权限(ADMIN 总是通过) |
渲染前,管理后台检查当前用户是否拥有 permission 声明的权限。未授权的 action 不会展示。权限不足时写入审计日志 frontend.permission.denied。
Admin Actions 展示在两个位置:
- 插件管理页
/admin/plugins:每个激活插件旁边显示其 actions - 插件 admin 根页面:插件 admin 子页面的根页面渲染操作组件
GET /admin/ui/actions
→ {
"actions": [
{ "pluginId": "store", "id": "sync-products", "label": "同步商品", "component": "SyncProductsAction", "permission": "store.write" }
]
}插件通过 ui.admin.dashboard 扩展点贡献 Dashboard Widget。元数据只包含 id、title 和可选 description,渲染组件在 frontend 包的 ui.adminWidgets 中以 Widget id 为键声明:
// 后端注册
onActivate(ctx) {
ctx.registerExtension('ui.admin.dashboard', {
id: 'sales-stats',
title: '销售统计',
description: '销售数据概览',
});
}// 前端包导出
export const ui = {
adminWidgets: {
'sales-stats': ({ pluginId }) => <SalesStatsWidget pluginId={pluginId} />,
},
};查询接口:
GET /admin/ui/widgets
→ {
"widgets": [
{ "id": "sales-stats", "title": "销售统计", "description": "销售数据概览", "pluginId": "store" }
]
}声明用户账户页面的子页面:
{
"accountRoutes": [
{
"path": "/my-orders",
"component": "MyOrders",
"nav": { "label": "历史订单", "group": "费用" }
}
]
}字段与 adminRoutes 相同:path、component、permission?、data?、nav?({ label, group? })。账户路由在 /account/* 下渲染,需要用户认证。渲染审计写入 frontend.account.route.render。
账户页组件与 admin 页不同:它由服务端入口 src/index.tsx 的 ui.accountPages 提供(不是 admin.tsx),键必须与 accountRoutes[].component 一致:
// frontend/src/index.tsx
ui: {
accountRoutes: [{ path: '/my-orders', component: 'account/orders', /* ... */ }],
accountPages: { 'account/orders': MyOrdersPage },
},组件的 props 是 AccountPageComponentProps,比 admin 页多出 user 与 searchParams:
function MyOrdersPage({ data, params, user, searchParams, settings, actions }: AccountPageComponentProps) {
const orders = data.orders as Order[];
const page = Number(searchParams.page ?? '1');
// ...
}searchParams 是当前请求的 query(值均为字符串,重复键取第一个)。账号页没有客户端包可用,因此筛选、分页、选中行这类交互都靠「读 query + 服务端重新渲染」实现,而不是 useState。
nav.group 决定该项归属的顶部菜单,账户外壳按固定顺序渲染 服务(业务对象,如云服务器、工单)、大模型(LLM 相关,如模型定价、API 密钥)、费用(与钱相关,如余额充值、历史订单、用量)三组。用 "group": "费用" / "group": "大模型" 显式归组,其余(含未声明)一律归入服务(apps/web/src/app/(account)/account/layout.tsx)。
声明用户账户页面的 Widget:
{
"accountWidgets": [{ "id": "order-summary", "title": "订单摘要", "component": "OrderSummary" }]
}| 字段 | 必填 | 说明 |
|---|---|---|
id |
是 | 唯一标识 |
title |
是 | 显示名 |
component |
是 | 组件名 |
permission |
否 | 所需权限 |
actions 声明表单式提交操作,平台统一处理鉴权、输入校验、审计和 BFF 转发:
{
"actions": [
{
"id": "create-item",
"method": "POST",
"path": "/my-plugin/items",
"permission": "my-plugin.write",
"input": [
{ "name": "name", "type": "string", "required": true, "maxLength": 191 },
{ "name": "price", "type": "money", "required": true }
],
"redirect": { "responsePath": "id", "external": false }
}
]
}| 字段 | 必填 | 说明 |
|---|---|---|
id |
是 | 唯一标识 |
method |
是 |
POST / PATCH / DELETE
|
path |
是 | 目标插件路由路径 |
permission |
否 | 所需权限 |
input |
否 | 输入字段定义列表 |
redirect |
否 | 提交后重定向配置:{ responsePath, external? }
|
| 规则 | 说明 |
|---|---|
| adminRoute path | 必须以 / 开头 |
| adminRoute 唯一性 | path 和 component 不可重复 |
| adminAction 唯一性 | id 和 component 不可重复 |
| 能力 | 入口文件 | 导出名 | 组件 props |
|---|---|---|---|
| 公开页面 | src/index.tsx |
pageComponents |
FrontendPageProps |
| 账户页面 | src/index.tsx |
ui.accountPages |
AccountPageComponentProps(含 user、searchParams) |
| 账户 Widget | src/index.tsx |
ui.accountWidgetComponents |
AccountWidgetProps |
| 管理后台页面 |
src/admin.tsx('use client') |
adminPages |
AdminPageComponentProps |
| 管理操作组件 | src/index.tsx |
ui.adminActionComponents |
AdminActionComponentProps |
| Dashboard Widget | src/index.tsx |
ui.adminWidgets |
AdminWidgetProps |
判断入口时看是否需要客户端交互(useState / Dialog / 事件):需要就放 admin.tsx,纯服务端渲染的展示或声明放 index.tsx。
核心开发
插件开发
主题开发
前端包开发
RESTful API