Blocks 字段

Blocks 字段功能极其强大,它基于你定义的字段存储一个对象数组,其中数组中的每一项都是一个具有自己独特模式的"块"。

Blocks 是创建灵活内容模型的绝佳方式,可用于构建多种内容类型,包括:

  • 布局构建工具:让编辑者能够设计高度可定制的页面或文章布局。块可以包含诸如 Quote(引用)、CallToAction(行动号召)、Slider(轮播)、Content(内容)或 Gallery(画廊)等配置。
  • 表单构建工具:可用的块配置可能包括 Text(文本)、Select(选择)或 Checkbox(复选框)。
  • 虚拟活动议程的"时间段":每个时间段可以是 Break(休息)、Presentation(演讲)或 BreakoutSession(分组讨论)。
管理面板截图:添加 Blocks 抽屉视图
管理面板截图:添加 Blocks 抽屉视图

要添加 Blocks 字段,请在你的字段配置中将 type 设置为 blocks

import type { Field } from 'payload'

export const MyBlocksField: Field = {
  // ...
  // highlight-start
  type: 'blocks',
  blocks: [
    // ...
  ],
  // highlight-end
}

配置选项

选项描述
name *作为属性名存储在数据库中并从数据库中检索时使用。了解更多
label在管理面板中用作标题的文本,或为每种语言提供键的对象。如果未定义,则根据名称自动生成。
blocks *可供此字段使用的块配置数组。
validate提供自定义验证函数,该函数将在管理面板和后端执行。了解更多
minRows当存在值时,验证期间允许的最小项目数。
maxRows当存在值时,验证期间允许的最大项目数。
saveToJWT如果此字段是顶级字段并嵌套在支持身份验证的配置中,则将其数据包含在用户 JWT 中。
hooks提供字段钩子来控制此字段的逻辑。更多详情
access提供字段访问控制,以指定用户可以对此字段的数据执行的操作。更多详情
hidden完全限制此字段在所有 API 中的可见性。仍会保存到数据库,但不会出现在任何 API 响应或管理面板中。
defaultValue提供用于此字段默认值的块数据数组。了解更多
localized为此字段启用本地化。需要在基础配置中启用本地化。如果启用,将为此字段中的所有数据保留单独的本地化集合,因此无需将每个嵌套字段指定为 localized
unique强制集合中的每个条目对此字段具有唯一值。
labels自定义管理仪表板中显示的块行标签。
admin特定于管理面板的配置。更多详情
custom用于添加自定义数据(例如插件)的扩展点。
typescriptSchema通过提供 JSON 模式覆盖字段类型生成。
virtual提供 true 以禁用数据库中的字段,或提供字符串路径以将字段与关系链接。参见虚拟字段

* 星号表示该属性为必填项。

管理选项

要自定义 Admin Panel 中 Blocks Field 的外观和行为,可以使用 admin 选项:

import type { Field } from 'payload'

export const MyBlocksField: Field = {
  // ...
  admin: {
    // highlight-line
    // ...
  },
}

Blocks Field 继承了基础 Field Admin Config 的所有默认管理选项,并新增了以下额外选项:

选项描述
group用于在 Blocks Drawer 中分组该 Block 的文本或本地化对象。
initCollapsed设置初始折叠状态
isSortable将此值设为 false 可禁用排序功能
disableBlockName将此值设为 true 可隐藏 blockName 字段

在 Lexical 编辑器中自定义块的渲染方式

如果你在 Lexical 编辑器 中使用这个块,还可以通过指定自定义组件来定制块在 Lexical 编辑器中的渲染方式。

  • admin.components.Label - 传入一个自定义 React 组件,用于定制该块的标签渲染方式
  • admin.components.Block - 传入一个组件,可以完全覆盖 Lexical 中块的渲染方式,使用你自己的组件

如果你想在富文本编辑器中为编辑人员呈现精心设计且美观的块"预览",这将非常有用。

例如,如果你有一个 gallery 块,你可能希望在 Lexical 块中直接渲染图片画廊。通过 admin.components.Block 属性,你可以轻松实现这一点!

提示: 如果自定义了块在 Lexical 中的渲染方式,你可以导入实用组件来轻松编辑/删除你的块,这样你就不需要自己构建这些功能了。

要为你的自定义块导入这些实用组件,可以使用以下导入语句:

import {
  // 编辑块按钮(根据你的使用场景选择对应的按钮)
  // 点击后会打开一个抽屉,显示块的字段
  // 让编辑人员可以编辑这些字段
  InlineBlockEditButton,
  BlockEditButton,

  // 从 Lexical 中移除该块的按钮
  //(根据你的使用场景选择对应的按钮)
  InlineBlockRemoveButton,
  BlockRemoveButton,

  // 内联块应渲染的标签
  InlineBlockLabel,

  // 内联块的默认"容器"渲染组件
  // 如果你想复用这个容器
  InlineBlockContainer,

  // 常规块的默认"可折叠"UI组件
  // 如果你想复用这个UI
  BlockCollapsible,
} from '@payloadcms/richtext-lexical/client'

区块配置

区块需要作为独立的配置进行定义。

提示: 最佳实践是将每个区块配置定义在单独的文件中,然后根据需要导入到你的 Blocks 字段中。这样每个区块配置可以轻松地在多个字段间共享。例如,在"布局构建器"的例子中,你可能希望在 Post 集合和 Page 集合中都使用几个相同的区块。将它们抽象到单独的文件中可以轻松实现复用。

OptionDescription
slug *该区块类型的标识符。会作为 blockType 属性保存在每个区块中。
fields *存储在该区块中的字段数组。
labels自定义在管理后台显示的区块标签。如果未定义,会根据 slug 自动生成。
imageURL提供自定义的缩略图图片,帮助编辑者在管理界面中识别该区块。
imageAltText自定义该区块缩略图的 alt 文本。
interfaceName创建顶层的可复用 Typescript 接口GraphQL 类型
graphQL.singularName用于 GraphQL 模式名称的文本。如果未定义,会根据 slug 自动生成。注意:此选项将被弃用,建议使用 interfaceName
dbName使用 SQL 数据库适配器(Postgres)时,该区块类型的自定义表名。如果未定义,会根据 slug 自动生成。
custom用于添加自定义数据的扩展点(例如插件使用)

每个区块自动生成的数据

除了你在每个区块上定义的字段数据外,Payload 还会在每个区块上存储两个额外的属性:

blockType

blockType 会被保存为所选区块的 slug。

blockName

Admin Panel 为每个区块提供一个 blockName 字段,允许编辑者可选地为区块添加标签,以提高可编辑性和可读性。可以通过 admin.disableBlockName 来隐藏这个字段。

示例

collections/ExampleCollection.js

import { Block, CollectionConfig } from 'payload'

const QuoteBlock: Block = {
  slug: 'Quote', // 必填
  imageURL: 'https://google.com/path/to/image.jpg',
  imageAltText: '展示该区块外观的缩略图',
  interfaceName: 'QuoteBlock', // 可选
  fields: [
    // 必填
    {
      name: 'quoteHeader',
      type: 'text',
      required: true,
    },
    {
      name: 'quoteText',
      type: 'text',
    },
  ],
}

export const ExampleCollection: CollectionConfig = {
  slug: 'example-collection',
  fields: [
    {
      name: 'layout', // 必填
      type: 'blocks', // 必填
      minRows: 1,
      maxRows: 20,
      blocks: [
        // 必填
        QuoteBlock,
      ],
    },
  ],
}

自定义组件

字段

服务端组件

import type React from 'react'
import { BlocksField } from '@payloadcms/ui'
import type { BlocksFieldServerComponent } from 'payload'

export const CustomBlocksFieldServer: BlocksFieldServerComponent = ({
  clientField,
  path,
  schemaPath,
  permissions,
}) => {
  return (
    <BlocksField
      field={clientField}
      path={path}
      schemaPath={schemaPath}
      permissions={permissions}
    />
  )
}

客户端组件

'use client'
import React from 'react'
import { BlocksField } from '@payloadcms/ui'
import type { BlocksFieldClientComponent } from 'payload'

export const CustomBlocksFieldClient: BlocksFieldClientComponent = (props) => {
  return <BlocksField {...props} />
}

标签

服务端组件

import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { BlocksFieldLabelServerComponent } from 'payload'

export const CustomBlocksFieldLabelServer: BlocksFieldLabelServerComponent = ({
  clientField,
  path,
}) => {
  return (
    <FieldLabel
      label={clientField?.label || clientField?.name}
      path={path}
      required={clientField?.required}
    />
  )
}

客户端组件

'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { BlocksFieldLabelClientComponent } from 'payload'

export const CustomBlocksFieldLabelClient: BlocksFieldLabelClientComponent = ({
  label,
  path,
  required,
}) => {
  return (
    <FieldLabel
      label={field?.label || field?.name}
      path={path}
      required={field?.required}
    />
  )
}

行标签

'use client'

import { useRowLabel } from '@payloadcms/ui'

export const BlockRowLabel = () => {
  const { data, rowNumber } = useRowLabel<{ title?: string }>()

  const customLabel = `${data.type} ${String(rowNumber).padStart(2, '0')} `

  return <div>自定义标签: {customLabel}</div>
}

区块引用

如果在多个地方使用多个区块,你的 Payload 配置可能会变得庞大,这可能导致向客户端发送更多数据,并在服务器上需要更多处理。不过,你可以通过一次性定义每个区块并在使用时仅引用其 slug 来优化性能,而不是传递整个区块配置。

要实现这一点,首先在 Payload 配置的 blocks 数组中定义区块。然后,在 Blocks Field 中,将区块 slug 传递给 blockReferences 数组 - 出于兼容性原因,blocks 数组需要保持为空。

import { buildConfig } from 'payload'
import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical'

// Payload 配置
const config = buildConfig({
  // 一次性定义区块
  blocks: [
    {
      slug: 'TextBlock',
      fields: [
        {
          name: 'text',
          type: 'text',
        },
      ],
    },
  ],
  collections: [
    {
      slug: 'collection1',
      fields: [
        {
          name: 'content',
          type: 'blocks',
          // 通过 slug 引用区块
          blockReferences: ['TextBlock'],
          blocks: [], // 出于兼容性原因,必须为空
        },
      ],
    },
    {
      slug: 'collection2',
      fields: [
        {
          name: 'editor',
          type: 'richText',
          editor: lexicalEditor({
            features: [
              BlocksFeature({
                // 相同的引用可以在任何地方重复使用,甚至在 lexical 编辑器中也不会影响性能
                blocks: ['TextBlock'],
              }),
            ],
          }),
        },
      ],
    },
  ],
})

提醒:blockReferences 数组中引用的区块被视为与 collection/global 配置隔离。这有以下影响:

  1. 区块配置不能在 collection 配置中被修改或扩展。它在所有引用处都将保持一致。
  2. blockReferences 中引用的区块的访问控制只会运行一次 - collection 中的数据将不会在区块的访问控制中可用。

TypeScript

在构建你自己的 Block 配置时,你可能希望将它们存储在单独的文件中,同时保留类型定义。为此,你可以导入并使用 Payload 的 Block 类型:

import type { Block } from 'payload'