Skip to content

Frontend Admin Extensions

worable edited this page Oct 12, 2026 · 1 revision

管理后台扩展

插件前端包可以声明管理后台子路由(adminRoutes)和管理操作(adminActions),扩展管理后台界面。

管理后台页面入口(src/admin.tsx)

管理后台页面是客户端交互组件,必须放在插件的 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)

声明插件在管理后台的子页面:

{
  "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。

管理操作(adminActions)

声明插件的快捷操作组件,展示在插件管理页和插件 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 展示在两个位置:

  1. 插件管理页 /admin/plugins:每个激活插件旁边显示其 actions
  2. 插件 admin 根页面:插件 admin 子页面的根页面渲染操作组件

查询接口

GET /admin/ui/actions
→ {
  "actions": [
    { "pluginId": "store", "id": "sync-products", "label": "同步商品", "component": "SyncProductsAction", "permission": "store.write" }
  ]
}

Dashboard Widget

插件通过 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)

声明用户账户页面的子页面:

{
  "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)

声明用户账户页面的 Widget:

{
  "accountWidgets": [{ "id": "order-summary", "title": "订单摘要", "component": "OrderSummary" }]
}
字段 必填 说明
id 是 唯一标识
title 是 显示名
component 是 组件名
permission 否 所需权限

声明式插件操作(actions)

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。

Clone this wiki locally