资源

Editing records

Resource edit page

填充表单前自定义数据

你可能希望在数据填充到表单之前对其进行修改。你可以在编辑页类中定义 mutateFormDataBeforeFill() 方法以修改 $data 数组,并将填入表单之前要修改的版本作为结果返回:

protected function mutateFormDataBeforeFill(array $data): array
{
    $data['user_id'] = auth()->id();

    return $data;
}

此外,如果你在模态框 Action 中编辑记录,请查阅 Action 文档

保存前自定义数据

有时候,你可能希望在最终存入到数据库之前修改表单数据。为此,你可以在编辑页面类中定义 mutateFormDataBeforeSave() 方法,使其接受 $data 数组,并返回修改后的版本:

protected function mutateFormDataBeforeSave(array $data): array
{
    $data['last_edited_by_id'] = auth()->id();

    return $data;
}

此外,如果你在模态框 Action 中编辑记录,请查阅 Action 文档

自定义保存过程

使用编辑页面类的 handleRecordUpdate() 方法,你可以调整记录的更新方式

use Illuminate\Database\Eloquent\Model;

protected function handleRecordUpdate(Model $record, array $data): Model
{
    $record->update($data);

    return $record;
}

此外,如果你在模态框 Action 中编辑记录,请查阅 Action 文档

自定义重定向

默认情况下,保存表单不会将用户重定向到其他页面。

通过重写编辑页类的 getRedirectUrl() 方法,你可以设置表单提交后的自定义重定向:

比如,表单重定向回资源的列表页

protected function getRedirectUrl(): string
{
    return $this->getResource()::getUrl('index');
}

或者,查看页:

protected function getRedirectUrl(): string
{
    return $this->getResource()::getUrl('view', ['record' => $this->getRecord()]);
}

如果你希望重定向到上一个页面,否则重定向到索引页:

protected function getRedirectUrl(): string
{
    return $this->previousUrl ?? $this->getResource()::getUrl('index');
}

你也可以使用配置,一次性自定义所有资源的默认重定向页面:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->resourceEditPageRedirect('index') // or
        ->resourceEditPageRedirect('view');
}

自定义保存通知

当记录成功更新后,会派发一个通知给用户,说明操作成功。

要自定义该通知的标题,请在编辑页面类上定义 getSavedNotificationTitle() 方法:

protected function getSavedNotificationTitle(): ?string
{
    return 'User updated';
}

此外,如果你在模态框 Action 中编辑记录,请查阅 Action 文档

通过重写编辑页类的 getSavedNotification() 方法,你可以自定义整个通知:

use Filament\Notifications\Notification;

protected function getSavedNotification(): ?Notification
{
    return Notification::make()
        ->success()
        ->title('User updated')
        ->body('The user has been saved successfully.');
}

要完全禁用通知,请在编辑页面类中的 getSavedNotification() 返回 null

use Filament\Notifications\Notification;

protected function getSavedNotification(): ?Notification
{
    return null;
}

生命周期钩子

钩子可用在页面生命周期的各种节点中执行代码,比如表单数据保存之前。要设置钩子,请使用钩子名在编辑页面类创建一个 protected 方法:

protected function beforeSave(): void
{
    // ...
}

本例中,beforeSave() 方法中的代码将会在表单中的数据保存到数据库之前调用。

编辑页面中有如下一些钩子可以使用:

use Filament\Resources\Pages\EditRecord;

class EditUser extends EditRecord
{
    // ...

    protected function beforeFill(): void
    {
        // Runs before the form fields are populated from the database.
    }

    protected function afterFill(): void
    {
        // Runs after the form fields are populated from the database.
    }

    protected function beforeValidate(): void
    {
        // Runs before the form fields are validated when the form is saved.
    }

    protected function afterValidate(): void
    {
        // Runs after the form fields are validated when the form is saved.
    }

    protected function beforeSave(): void
    {
        // Runs before the form fields are saved to the database.
    }

    protected function afterSave(): void
    {
        // Runs after the form fields are saved to the database.
    }
}

此外,如果你在模态框 Action 中编辑记录,请查阅 Action 文档

Defining lifecycle hooks in traits

To define a lifecycle hook in a trait, suffix the hook name with the trait’s name. This follows the boot{TraitName}() convention used by Eloquent and the mount{TraitName}() convention used by Livewire, allowing reusable traits to hook into the page lifecycle without colliding with hooks defined on the page itself:

use Filament\Resources\Pages\EditRecord;

trait HandlesDrafts
{
    protected function afterSaveHandlesDrafts(): void
    {
        // Runs after the form fields are saved to the database, in addition
        // to the hook on the page.
    }
}

class EditUser extends EditRecord
{
    use HandlesDrafts;

    protected function afterSave(): void
    {
        // Both lifecycle hooks are called.
    }
}

The page’s own hook is called first, followed by each trait hook. Hooks from traits used by other traits are also called. Trait hooks are called automatically, so you should not also call them from the page’s own hook.

单独保存表单的一部分

你可能希望允许用户单独保存表单的其中一部分。其中一种方式是,使用 Header 或 Footer 的 Section Action。在 action() 方法中,你可以调用 saveFormComponentOnly() 方法,并传入你想要保存的 Section 组件:

use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Filament\Schemas\Components\Section;

Section::make('Rate limiting')
    ->schema([
        // ...
    ])
    ->footerActions([
        fn (string $operation): Action => Action::make('save')
            ->action(function (Section $component, EditRecord $livewire) {
                $livewire->saveFormComponentOnly($component);
                
                Notification::make()
                    ->title('Rate limiting saved')
                    ->body('The rate limiting settings have been saved successfully.')
                    ->success()
                    ->send();
            })
            ->visible($operation === 'edit'),
    ])

$operation 用以确保该 Action 只在表单编辑时可见。

Resource edit page with section footer save action

中断保存过程

在任何时候,你都可以在生命周期钩子或者 mutation 方法中,调用 $action->halt(),中断整个保存处理过程:

use Filament\Actions\Action;
use Filament\Notifications\Notification;

protected function beforeSave(): void
{
    if (! $this->getRecord()->team->subscribed()) {
        Notification::make()
            ->warning()
            ->title('You don\'t have an active subscription!')
            ->body('Choose a plan to continue.')
            ->persistent()
            ->actions([
                Action::make('subscribe')
                    ->button()
                    ->url(route('subscribe'), shouldOpenInNewTab: true),
            ])
            ->send();

        $this->halt();
    }
}

此外,如果你在模态框 Action 中编辑记录,请查阅 Action 文档

授权

关于授权,Filament 会监测所有应用中注册的模型策略

如果模型策略的 update() 方法返回 true,用户可以访问编辑页面。

如果模型策略的 delete() 方法返回 true,用户有权限删除该记录。

自定义 Action

“Action” 是显示在页面的按钮,它允许用户在页面中运行 Livewire 方法或者访问 URL。

在资源页面中,Action 通常出现在 2 个地方:页面的右上方和表单下方。

比如,你可以在编辑页面的 “删除(Delete)” 按钮边上添加一个新按钮:

use Filament\Actions;
use Filament\Resources\Pages\EditRecord;

class EditUser extends EditRecord
{
    // ...

    protected function getHeaderActions(): array
    {
        return [
            Actions\Action::make('impersonate')
                ->action(function (): void {
                    // ...
                }),
            Actions\DeleteAction::make(),
        ];
    }
}
Resource edit page with custom header actions

或者,在表单下面的 “保存(Save)” 按钮旁边添加一个新按钮:

use Filament\Actions\Action;
use Filament\Resources\Pages\EditRecord;

class EditUser extends EditRecord
{
    // ...

    protected function getFormActions(): array
    {
        return [
            ...parent::getFormActions(),
            Action::make('close')->action('saveAndClose'),
        ];
    }

    public function saveAndClose(): void
    {
        // ...
    }
}

要查看整个 Action 的 API,请查阅页面章节

添加保存按钮到 Header

通过重写 getHeaderActions() 方法及使用 getSaveFormAction(),可以将保存按钮添加到页面的 Header 中。你可以将 formId() 传入到该 Action,以指定该 Action 使用 form 作为 ID 提交表单,该 ID 是页面视图的 <form> 的 ID:

protected function getHeaderActions(): array
{
    return [
        $this->getSaveFormAction()
            ->formId('form'),
    ];
}

你可以重写 getFormActions() 方法,使之返回空数组,移除表单的所有 Action:

protected function getFormActions(): array
{
    return [];
}
Resource edit page with save action in the header

创建另一个编辑页面

一个编辑页可能没有足够的空间允许用户导航太多的表单字段。你可以根据需要为一个资源创建尽可能多的编辑页面。如果你在使用资源子导航,这尤其有用,因为这样你将能游刃有余地切换到不同的编辑页面中。

要创建编辑页,你可以使用 make:filament-page 命令:

php artisan make:filament-page EditCustomerContact --resource=CustomerResource --type=EditRecord

你必须在资源的 getPages() 方法中注册该新页面:

public static function getPages(): array
{
    return [
        'index' => Pages\ListCustomers::route('/'),
        'create' => Pages\CreateCustomer::route('/create'),
        'view' => Pages\ViewCustomer::route('/{record}'),
        'edit' => Pages\EditCustomer::route('/{record}/edit'),
        'edit-contact' => Pages\EditCustomerContact::route('/{record}/edit/contact'),
    ];
}

现在,你可以为该页面定义表单 form(),该表单可以包含主编辑页面没有展示的其他字段:

use Filament\Schemas\Schema;

public function form(Schema $schema): Schema
{
    return $schema
        ->components([
            // ...
        ]);
}

将编辑页添加到资源子导航

如果你使用了资源子导航,你可以在资源的 getRecordSubNavigation() 中像平常那样注册该页面:

use App\Filament\Resources\Customers\Pages;
use Filament\Resources\Pages\Page;

public static function getRecordSubNavigation(Page $page): array
{
    return $page->generateNavigationItems([
        // ...
        Pages\EditCustomerContact::class,
    ]);
}

自定义页面内容

Filament 中的每个页面都有它们自己的 Schema,其定义整体结构和内容。通过定义页面的 content() 方法,你可以重写页面的 Shema。编辑页面的 content() 方法默认包含如下组件:

use Filament\Schemas\Schema;

public function content(Schema $schema): Schema
{
    return $schema
        ->components([
            $this->getFormContentComponent(), // This method returns a component to display the form that is defined in this resource
            $this->getRelationManagersContentComponent(), // This method returns a component to display the relation managers that are defined in this resource
        ]);
}

components() 数组中,你可以插入任何 Schema 组件。通过改变数组的顺序或者移除不需要的组件,你可以对这些组件重新排序。

使用自定义 Blade 视图

你可以重写页面类的静态 $view 属性,自定义视图:

protected string $view = 'filament.resources.users.pages.edit-user';

上述命令假定你已经创建了一个视图:resources/views/filament/resources/users/pages/edit-user.blade.php

<x-filament-panels::page>
    {{-- `$this->getRecord()` will return the current Eloquent record for this page --}}
    
    {{ $this->content }} {{-- This will render the content of the page defined in the `content()` method, which can be removed if you want to start from scratch --}}
</x-filament-panels::page>
Edit on GitHub

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

Previous
新建记录