Skip to content

Frontend Finders

worable edited this page Oct 20, 2026 · 2 revisions

Finder 数据源

Finder 是前端包的类型化数据源机制。页面通过 data 字段绑定 Finder,平台在渲染前执行 Finder 获取数据并注入组件。

声明 Finder

在前端清单的 finders 数组中声明 Finder 名称:

{
  "finders": ["blogList", "blogPost", "categories"]
}

绑定到页面

页面通过 data 字段绑定 Finder,data 是一个 FrontendPageDataRequirement 数组:

{
  "path": "/blog/:slug",
  "component": "BlogPostPage",
  "data": [{ "finder": "blogPost", "as": "post" }]
}

每个绑定声明:

  • finder — Finder 名称(必须在 finders 列表中声明)
  • as — 注入到组件 props 的键名
  • input — 可选,传给 Finder 的额外参数
  • permission — 可选,Finder 执行前检查的权限

Finder 调用流程

渲染页面时,平台:

  1. 解析路径参数(如 :slug → hello-world)
  2. 对 data 数组中的每个绑定,调用对应 Finder,传入路径参数、设置和 input
  3. 将每个返回值以 as 声明的键名注入组件 props
GET /blog/hello-world
  → 路径参数: { slug: "hello-world" }
  → Finder: blogPost({ slug: "hello-world" }, ctx) → 返回文章对象
  → 数据注入: <BlogPostPage post={finderResult} params={pathParams} />

FinderContext 提供 params(路径参数)、settings(前端设置)、api(ApiClient)和 session(会话信息)。

Finder 输入解析

平台把 data[].input 解析成传给 handler(input, ctx) 的第一个参数,规则如下:

input 值 解析结果
":param"(以 : 开头) 替换为路径参数,如 { id: ":slug" } → { id: "hello-world" }
任意字面量(字符串/数字/布尔) 原样传入
键为 $query 注入整份 URL query(Record<string, string | string[] | undefined>),用于服务端分页/筛选
其它键遇到同名 query 参数 URL query 覆盖声明的默认值(如 ?page=2 覆盖 { page: 1 })

因此「声明的默认值」可被 URL 覆盖,这是实现服务端分页的推荐方式。

管理后台路由的 data 绑定不做 query 覆盖,只解析字面量与 :param。

Finder 实现

Finder 是前端包导出的 FinderProvider,通过 handler 函数提供数据:

interface FinderProvider<Result = unknown> {
  handler(input: Record<string, unknown>, ctx: FinderContext): Promise<Result>;
}

interface FinderContext {
  params: Record<string, string>;
  settings: FrontendSettings;
  api: ApiClient;
  session?: FinderSession | null;
}

示例:

// frontend/src/index.tsx
export const blogPost: FinderProvider<Post> = {
  async handler(input, ctx) {
    // ctx.params 包含路径参数,ctx.api 是 ApiClient
    return ctx.api.get(`/my-plugin/posts/${ctx.params.slug}`, postSchema);
  },
};

export function BlogPostPage({ post, params }: { post: Post; params: { slug: string } }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

Finder 在前端包的 FrontendPackage.finders 中以名称为键导出,平台通过 data 绑定中的 finder 字段查找对应实现。

错误处理

Finder 抛出的异常会被平台 try/catch 吞掉,并以 null 作为该绑定的值注入。也就是说 Finder 失败时页面不会报错,只是 props.data.<as> 变成 undefined。调试时可在 Finder 内临时打印,或先确认 ctx.api 请求路径与响应 schema 正确。

Finder 注册与优先级

  • 公开页面:先注册主题的 Finder,再注册插件的;同名 Finder 主题优先,插件不能覆盖主题的。
  • 管理后台路由:合并所有已启用插件的 Finder(因此 store 的 admin 页可以读取 catalog 提供的 Finder)。

审计

Finder 调用会写入审计日志:

action 说明
frontend.finder.call Finder 数据调用

通过 POST /frontend/audit 端点记录:顶层 pluginId、resourceId(Finder 名)与 meta(含 status:ok / error)。注意:只有插件提供的 Finder 会记审计——主题 Finder 没有 pluginId,不会上报(apps/web/src/lib/pages.ts)。

校验规则

  • Finder 名称在 finders 数组中必须唯一
  • finders 在编译产物中始终存在,可为空数组 [](schema 默认 [])

Finder 命名约定

建议使用 camelCase,语义化命名:

Finder 说明
blogList 博客列表
blogPost 博客文章
categories 分类列表
products 商品列表
productDetail 商品详情

Clone this wiki locally