嵌套文档插件
该插件允许你轻松地将应用中的文档相互嵌套。它通过在每个文档上添加一个新的 parent
字段来实现这一功能,当选择父文档时,当前文档就会附加到父文档的树结构中。例如,当你编辑某个文档的曾曾祖父文档时,其所有后代文档都会递归更新。这是在 collection 中实现层级结构(如页面之间的父子关系)的极其强大的方式。
文档还会获得一个新的 breadcrumbs
字段。一旦分配了父文档,这些面包屑导航就会根据树结构中的每个祖先自动填充。面包屑导航允许你根据文档在层级结构中的位置动态生成标签和 URL。即使父文档的 slug 发生变化,或者整个树结构被嵌套到更深层级,这些变更也会级联影响整个树结构,所有面包屑导航都会相应更新。
通过这种模式,你可以对任意深度嵌套的文档执行应用所需的任何副作用操作。例如,你可以轻松地在每个文档上添加自定义的 fullTitle
字段,并将父文档的标题注入其中,形成类似"父标题 > 子标题"的格式。这样你就可以基于该字段而非原始标题进行搜索和筛选。当你拥有两个标题相同但父文档不同的文档时,这一功能尤其有用。
核心功能
- 自动为每个文档添加
parent
关联字段 - 允许同一集合内的文档建立父子关系
- 当父文档变更时递归更新所有子文档
- 自动填充
breadcrumbs
字段,包含树形结构中的所有祖先 - 动态生成每个面包屑的标签和 URL
- 支持本地化
安装
使用 pnpm、npm 或 Yarn 等 JavaScript 包管理器安装插件:
pnpm add @payloadcms/plugin-nested-docs
基础用法
在 Payload 配置的 plugins
数组中,调用插件并传入选项:
import { buildConfig } from 'payload'
import { nestedDocsPlugin } from '@payloadcms/plugin-nested-docs'
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'slug',
type: 'text',
},
],
},
],
plugins: [
nestedDocsPlugin({
collections: ['pages'],
generateLabel: (_, doc) => doc.title,
generateURL: (docs) =>
docs.reduce((url, doc) => `${url}/${doc.slug}`, ''),
}),
],
})
export default config
字段
父级关系
parent
关联字段会自动添加到每个文档中,允许编辑者从同一集合中选择另一个文档作为直接父级。
面包屑导航
breadcrumbs
字段是一个数组,它会动态填充文档的所有父级关系直至顶层,并存储以下字段。
字段 | 描述 |
---|---|
label | 面包屑导航项的标签。该字段会自动设置为 collection.admin.useAsTitle (如果已定义),否则会设置为文档的 ID 。你也可以通过向 generateLabel 的 options 属性传递一个函数来动态定义 label 。 |
url | 面包屑导航项的 URL。默认情况下,该字段未定义。你可以通过向插件选项属性 generateURL 传递一个函数来手动定义该字段。 |
配置选项
collections
一个包含 collection 名称的数组,用于启用嵌套文档功能。
generateLabel
每个 breadcrumb
都有一个必需的 label
字段。默认情况下,其值会被设置为 collection 的 admin.useAsTitle
,或者回退到文档的 ID
。
你也可以传递一个函数来动态设置 breadcrumb 的 label
。
// payload.config.ts
nestedDocsPlugin({
//...
generateLabel: (_, doc) => doc.title, // 注意: 'title' 是一个假设字段
})
该函数接收两个参数并返回一个字符串:
参数 | 类型 | 描述 |
---|---|---|
docs | Array | 到当前点为止的 breadcrumbs 数组 |
doc | Object | 当前正在编辑的文档 |
generateURL
这个函数允许你动态生成每个 breadcrumb 的 url
。每个 breadcrumb
都有一个可选的 url
字段,默认情况下是 undefined。例如,你可能想要格式化一个完整的 URL 来包含到该点为止的所有 breadcrumbs,比如 /about-us/company/our-team
。
// payload.config.ts
nestedDocsPlugin({
//...
generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ''), // 注意: 'slug' 是一个假设字段
})
参数 | 类型 | 描述 |
---|---|---|
docs | Array | 到当前点为止的 breadcrumbs 数组 |
doc | Object | 当前正在编辑的文档 |
parentFieldSlug
当定义此属性时,parent
字段不会自动提供,而是需要你手动为每个 collection 添加自己的 parent
字段。这让你可以完全控制该字段在管理面板中的位置等。将此属性设置为你自定义字段的 name
。
breadcrumbsFieldSlug
当定义此属性时,系统将不会自动提供 breadcrumbs
字段,而是需要你手动为每个 collection 添加自己的 breadcrumbs
字段。将此属性设置为你自定义字段的 name
。
注意:
如果你选择不使用系统自动提供的 parent
或 breadcrumbs
字段,需要确保这两个字段都位于文档的顶层。它们不能存在于任何嵌套数据结构中,如 group
、array
或 blocks
。
覆盖配置
你可以通过使用 createParentField
和 createBreadcrumbField
方法,在每个 collection 中扩展内置的 parent
和 breadcrumbs
字段。这些方法会将你的自定义配置与插件的基础字段配置进行合并。
import type { CollectionConfig } from 'payload'
import { createParentField } from '@payloadcms/plugin-nested-docs/fields'
import { createBreadcrumbsField } from '@payloadcms/plugin-nested-docs/fields'
const examplePageConfig: CollectionConfig = {
slug: 'pages',
fields: [
createParentField(
// 第一个参数等于该字段引用的 collection 的 slug
'pages',
// 第二个参数是你指定的字段覆盖配置,
// 这些配置将与基础 parent 字段配置合并
{
admin: {
position: 'sidebar',
},
// 注意:如果你覆盖了 `parent` 字段的 `filterOptions`,
// 请确保继续阻止文档将自身引用为父级,如下所示:
// filterOptions: ({ id }) => ({ id: {not_equals: id }})
},
),
createBreadcrumbsField(
// 第一个参数等于该字段引用的 collection 的 slug
'pages',
// 参数是你指定的字段覆盖配置,
// 这些配置将与基础 `breadcrumbs` 字段配置合并
{
label: '页面面包屑',
},
),
],
}
注意:
如果覆盖了 breadcrumbs
或 parent
字段的 name
属性,你必须分别指定 breadcrumbsFieldSlug
或 parentFieldSlug
。
本地化
本插件默认支持本地化。如果你的 Payload Config 中设置了 localization
属性,breadcrumbs
字段会自动进行本地化。有关 Payload 中本地化工作原理的更多细节,请参阅 本地化 文档。
TypeScript
所有类型都可以直接导入:
import {
PluginConfig,
GenerateURL,
GenerateLabel,
} from '@payloadcms/plugin-nested-docs/types'
示例
Templates Directory 中还包含官方的 Website Template 和 E-commerce Template,这两个模板都使用了此插件。