公告: 旧版文档请移步 -> http://v2.laravel-filament.cn/docs

Languages

Version

Theme

表单构造器 - 字段

Repeater

概述

Repeater 组件允许你输出重复表单组件的 JSON 数组:

use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
 
Repeater::make('members')
->schema([
TextInput::make('name')->required(),
Select::make('role')
->options([
'member' => 'Member',
'administrator' => 'Administrator',
'owner' => 'Owner',
])
->required(),
])
->columns(2)
Repeater

建议将 repeater 数据存储在数据库的 JSON 字段中。此外,如果你使用了 Eloquent,请确保该对该字段进行 array 强制转换(casts)。

上例可以明显看出,组件 Schema 在组件的 schema() 方法中定义:

use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\TextInput;
 
Repeater::make('members')
->schema([
TextInput::make('name')->required(),
// ...
])

如果你想要使用可以以任何顺序重复的多个 Schema 块来定义 Repeater,请使用 Builder

设置空默认项

使用 defaultItems() 方法,Repeater 可以创建特定数量的默认项:

use Filament\Forms\Components\Repeater;
 
Repeater::make('members')
->schema([
// ...
])
->defaultItems(3)

请注意,默认项只会表单中没有加载已有数据时创建。在面板资源内,它只会在创建页面生效,因为编辑页面始终回从模型中填充数据。

添加项目

Repeater 下面有一个 Action 按钮,用来添加新项目。

设置添加按钮标签

使用 addActionLabel() 方法,你可以设置添加按钮上显示的标签文本:

use Filament\Forms\Components\Repeater;
 
Repeater::make('members')
->schema([
// ...
])
->addActionLabel('Add member')

阻止用户添加项目

使用 addable(false) 方法,你可以阻止用户添加项目到 Repeater:

use Filament\Forms\Components\Repeater;
 
Repeater::make('members')
->schema([
// ...
])
->addable(false)

删除项目

每个项目上都有个 Action 按钮,用以删除项目。

防止用户删除项目

使用 deletable(false) 方法,你可以阻止用户删除 Repeater 中的项目:

use Filament\Forms\Components\Repeater;
 
Repeater::make('members')
->schema([
// ...
])
->deletable(false)

项目重新排序

每个项目上都有一个按钮,用于允许用户拖拽来对项目进行重新排序。

防止用户重新排序

使用 reorderable(false) 方法,你可以阻止用户在 Repeater 中对项目进行重新排序:

use Filament\Forms\Components\Repeater;
 
Repeater::make('members')
->schema([
// ...
])
->reorderable(false)

使用按钮重新排列项目

使用 reorderableWithButtons() 方法,你可以启用通过按钮重新排序,将项目上下移动:

use Filament\Forms\Components\Repeater;
 
Repeater::make('members')
->schema([
// ...
])
->reorderableWithButtons()
Repeater that is reorderable with buttons

阻止使用拖拽进行重新排序

使用 reorderableWithDragAndDrop(false) 方法,可以阻止项目通过拖拽进行排序:

use Filament\Forms\Components\Repeater;
 
Repeater::make('members')
->schema([
// ...
])
->reorderableWithDragAndDrop(false)

可折叠的项目

使用 collapsible() 方法,可以将 Repeater 设为可折叠的,使其在长表单中隐藏内容:

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->schema([
// ...
])
->collapsible()

你可以将所有项目设置成默认折叠:

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->schema([
// ...
])
->collapsed()
Collapsed repeater

克隆项目

使用 cloneable() 方法,可以将 Repeater 项目设为可复制的。

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->schema([
// ...
])
->cloneable()
Cloneable repeater

集成 Eloquent 关联

如果你在 Livewire 组件内创建表单,请确保设置好表单模型。否则,Filament 不知道应该使用哪个模型去检索关联。

你可以使用 Repeater 上的 relationship() 方法去配置 HasMany 关联。Filament 将从关联中加载项目数据,并在表单提交后将其保存回关联。如果没有将自定义关联名传入到 relationship(),Filament 将回使用字段名作为关联名:

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->relationship()
->schema([
// ...
])

disabled()relationship() 一起使用时,请确保 disabled() 的调用在 relationship() 之前。这可以确保在 relationship() 中进行 dehydrated() 调用不会被 disabled() 调用覆盖:

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->disabled()
->relationship()
->schema([
// ...
])

在关联中重排项目

默认情况下,Repeater 项目重新排序关联的 Repeater 项目是禁用的。这是因为关联模型需要有一个 sort 字段用来存储关联记录的排序。要启用重新排序功能,你需要使用 orderColumn() 方法,传入关联模型的用于排序的字段名:

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->relationship()
->schema([
// ...
])
->orderColumn('sort')

如果你使用了诸如 spatie/eloquent-sortable 这样使用 order_column 作为排序字段的包,你可以将其传入到 orderColumn() 中:

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->relationship()
->schema([
// ...
])
->orderColumn('order_column')

集成 BelongsToMany Eloquent 关联

有一种常见的误解是,在 Repeater 中使用 BelongsToMany 关联与使用 HasMany 关联一样简单。事实并非如此,BelongsToMany 关联需要中间表来存储关联数据。Repeater 将数据保存到关联模型,而非中间表。因此,如果你想将每个 Repeater 项目映射到中间表行数据,你必须使用 HasMany 关联中间模型,才可以在 Repeater 中使用 BelongsToMany 关联。

假设你有一个新建 Order 模型的表单。每个订单属于多个 Product 模型,并且每个产品也属于多个订单。你使用了 order_product 中间表来存储关联数据。你不应该在 Repeater 中使用 products 关联,而应该在 Order 模型上创建一个 orderProducts 关联,并在 Repeater 上使用:

use Illuminate\Database\Eloquent\Relations\HasMany;
 
public function orderProducts(): HasMany
{
return $this->hasMany(OrderProduct::class);
}

如果你没有 OrderProduct 中间模型,你应该创建一个,将其反转关联到 OrderProduct 上:

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\Pivot;
 
class OrderProduct extends Pivot
{
public function order(): BelongsTo
{
return $this->belongsTo(Order::class);
}
 
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
}

请确保中间模型有一个主键,比如 id,以允许 Filament 跟踪哪个 Repeater 项创建、更新及删除。

现在,你可以在 Repeater 上使用 orderProducts 关联,并将数据保存到 order_product 中间表中:

use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
 
Repeater::make('orderProducts')
->relationship()
->schema([
Select::make('product_id')
->relationship('product', 'name')
->required(),
// ...
])

填充字段前更改关联项目数据

使用 mutateRelationshipDataBeforeFillUsing() 方法,你可以在数据填充到该字段之前修改关联项目的数据。该方法接收一个闭包,闭包使用 $data 变量接收当前项目的数据。你必须返回修改过的数据数组:

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->relationship()
->schema([
// ...
])
->mutateRelationshipDataBeforeFillUsing(function (array $data): array {
$data['user_id'] = auth()->id();
 
return $data;
})

新建前修改关联项目数据

使用 mutateRelationshipDataBeforeCreateUsing() 方法,你可以在数据库中新建记录之前修改关联项数据。该方法接收一个闭包,闭包使用 $data 变量接收当前项目的数据。你可以返回修改过的数据数组,或者用于避免该项被创建的 null

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->relationship()
->schema([
// ...
])
->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
$data['user_id'] = auth()->id();
 
return $data;
})

保存前修改关联项目数据

使用 mutateRelationshipDataBeforeSaveUsing() 方法,你可以在数据保存到数据库之前修改已有关联项目的数据。该方法接收一个闭包,闭包使用 $data 变量接收当前项目的数据。你可以返回修改过的数据数组,或者用于避免该项被保存的 null::

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->relationship()
->schema([
// ...
])
->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
$data['user_id'] = auth()->id();
 
return $data;
})

网格布局

使用 grid() 方法,你可以将 Repeater 项目组织到网格列中:

use Filament\Forms\Components\Repeater;
 
Repeater::make('qualifications')
->schema([
// ...
])
->grid(2)
Repeater with a 2 column grid of items

该方法接收的参数与 gridcolumns() 相同。它允许你在不同的临界点中响应式地自定义网格列数。

基于内容为 Repeater 项目添加标签

使用 itemLabel() 方法,你可以为 Repeater 项目添加标签。该方法接收一个闭包,闭包使用 $state 变量接收当前项目的数据。你必须返回字符串用作项目标签:

use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Select;
 
Repeater::make('members')
->schema([
TextInput::make('name')
->required()
->live(onBlur: true),
Select::make('role')
->options([
'member' => 'Member',
'administrator' => 'Administrator',
'owner' => 'Owner',
])
->required(),
])
->columns(2)
->itemLabel(fn (array $state): ?string => $state['name'] ?? null),

如果你希望在使用表单时实时更新项目标签,你在 $state 中使用的任何字段都应该是 live() 实时的。

Repeater with item labels

单个字段的简单 Repeater

你可以使用 simple() 方法,使用单个字段来创建 Repeater,最小化设计

use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\TextInput;
 
Repeater::make('invitations')
->simple(
TextInput::make('email')
->email()
->required(),
)
Simple repeater design with only one field

不再使用嵌套数组存储数据,简单 Repeater 使用平面数组值。这意味着,上例的数据结构像这样:

[
'invitations' => [
'dan@filamentphp.com',
'ryan@filamentphp.com',
],
],

使用 $get() 访问父级字段值

所有表单组件都可以使用 $get()$set() 去访问另一个字段的值。不过,当在 Repeater 的 Schema 中使用时,你可能回遇到非预期的行为。

这是因为 $get()$set() 默认作用于当前的 Repeater 项目。这意味着,你可以在该 Repeater 内和其他字段交互,而不必知晓当前表单组件属于哪个 Repeater 项目。

后果是,当你无法与 Repeater 之外的字段进行交互时,你可能会感到疑惑。我们使用 ../ 语法来解决这个问题 —— $get('../../parent_field_name')

假设你的表单数据结构如下:

[
'client_id' => 1,
 
'repeater' => [
'item1' => [
'service_id' => 2,
],
],
]

你要在 Repeater 项目中检索 client_id

$get() 是相对当前 Repeater 项目,因此 $get('client_id') 查找的是 $get('repeater.item1.client_id')

你可以使用 ../ 回到该数据结构的上一级,因此 $get('../client_id') 相当于 $get('repeater.client_id')$get('../../client_id') 相当于 $get('client_id')

The special case of $get() with no arguments, or $get('') or $get('./'), will always return the full data array for the current repeater item.

Repeater 验证

除了验证页面中列出的所有规则,还有一些其他专用于 Repeater 的验证规则。

验证项目数量

设置 minItems()maxItems() 方法,你可以验证 Repeater 中项目的最小和最大数量:

use Filament\Forms\Components\Repeater;
 
Repeater::make('members')
->schema([
// ...
])
->minItems(2)
->maxItems(5)

不同状态验证

在许多情况下,你需要确保不同的 Repeater 项目之间有有种唯一性。通用的一些示例为:

使用 distinct() 方法,可以验证一个字段的状态在 Repeater 内所有的子项中是否是唯一的:

use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Repeater;
 
Repeater::make('answers')
->schema([
// ...
Checkbox::make('is_correct')
->distinct(),
])

distinct() 验证的行为取决于字段所处理的数据类型:

自动修复非 distinct 状态

如果你像修复非 distinct 状态,可以使用 fixIndistinctState() 方法:

use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Repeater;
 
Repeater::make('answers')
->schema([
// ...
Checkbox::make('is_correct')
->fixIndistinctState(),
])

该方法将自动启用字段中的 distinct()live() 方法。

取决于该字段所处理的数据类型,fixIndistinctState() 的行为适配:

禁用其他项目选中的选项

如果你想在下拉列表单选框复选框列表切换按钮组中禁用已被其他项目选中的选项,可以使用 disableOptionsWhenSelectedInSiblingRepeaterItems() 方法:

use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
 
Repeater::make('members')
->schema([
Select::make('role')
->options([
// ...
])
->disableOptionsWhenSelectedInSiblingRepeaterItems(),
])

该方法将在字段中自动启用 distinct()live() 方法。

自定义 Repeater 子项 Action

该字段使用 Action 对象,在其中对按钮进行自定义。通过传递一个函数到 action 注册方法中,可以自定义这些按钮。该函数可以访问 $action 对象,用于自定义。下面是一些可以用于自定义 action 的方法:

  • addAction()
  • cloneAction()
  • collapseAction()
  • collapseAllAction()
  • deleteAction()
  • expandAction()
  • expandAllAction()
  • moveDownAction()
  • moveUpAction()
  • reorderAction()

下面是一个自定义 action 的示例:

use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Repeater;
 
Repeater::make('members')
->schema([
// ...
])
->collapseAllAction(
fn (Action $action) => $action->label('Collapse all members'),
)

使用模态框确认 Repeater Action

在 Action 对象中使用 requiresConfirmation() 方法,你可以使用模态框确认 Action。你可以使用所有模态框自定义方法来修改其内容和行为:

use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Repeater;
 
Repeater::make('members')
->schema([
// ...
])
->deleteAction(
fn (Action $action) => $action->requiresConfirmation(),
)

collapseAction()collapseAllAction()expandAction()expandAllAction()reorderAction() 方法不支持确认模态框,因为点击这些按钮不会发起要求显示模态框的网络请求。

将额外的项目 Action 添加到 Repeater 中

通过将 Action 对象传入到 extraItemActions() 方法,你可以将新的 Action 按钮添加到每个 Repeater 项目中:

use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\TextInput;
use Illuminate\Support\Facades\Mail;
 
Repeater::make('members')
->schema([
TextInput::make('email')
->label('Email address')
->email(),
// ...
])
->extraItemActions([
Action::make('sendEmail')
->icon('heroicon-m-envelope')
->action(function (array $arguments, Repeater $component): void {
$itemData = $component->getItemState($arguments['item']);
 
Mail::to($itemData['email'])
->send(
// ...
);
}),
])

本例中,$arguments['item'] 提供了当前 Repeater 项目的 ID。你可以在 Repeater 组件中使用 getItemState() 方法验证该 Repeater 项目中的数据。该方法返回该项目已验证的数据。如果数据无效,它会取消该操作并在表单中显示该项目的错误消息。

如果你想获取当前项目的原始数据,而不对其进行验证,你可以使用 $component->getRawItemState($arguments['item'])

如果你想操作整个 Repeater 的原始数据,比如,添加、删除或修改项目,你可以使用 $component->getState() 获取数据,并 $component->state($state) 对其重新设置:

use Illuminate\Support\Str;
 
// Get the raw data for the entire repeater
$state = $component->getState();
 
// Add an item, with a random UUID as the key
$state[Str::uuid()] = [
'email' => auth()->user()->email,
];
 
// Set the new data for the repeater
$component->state($state);

测试 Repeater

在内部,Repeater 为其子项目生成 UUID,以便更容易地在 Livewire HTML 中跟踪它们。这意味着,当测试带有 Repeater 的表单时,需要确保表单和测试之间的 UUID 一致。这可能很棘手,如果操作不正确,测试可能会失败,因为测试需要的是 UUID,而不是数字键。

但是,由于 Livewire 不需要跟踪测试中的 UUID,你可以在测试开始时使用 Repeater::fake() 方法禁用 UUID 生成并用数字键替换它们:

use Filament\Forms\Components\Repeater;
use function Pest\Livewire\livewire;
 
$undoRepeaterFake = Repeater::fake();
 
livewire(EditPost::class, ['record' => $post])
->assertFormSet([
'quotes' => [
[
'content' => 'First quote',
],
[
'content' => 'Second quote',
],
],
// ...
]);
 
$undoRepeaterFake();

你可能还发现,通过将函数传递到 assertFormSet() 方法来访问测试 Repeater 子项数量也是很有用的:

use Filament\Forms\Components\Repeater;
use function Pest\Livewire\livewire;
 
$undoRepeaterFake = Repeater::fake();
 
livewire(EditPost::class, ['record' => $post])
->assertFormSet(function (array $state) {
expect($state['quotes'])
->toHaveCount(2);
});
 
$undoRepeaterFake();
Edit on GitHub

Still need help? Join our Discord community or open a GitHub discussion

下一页
Builder