Skip to content
全部文档

单例资源

概述

资源并非在 Filament 面板中操作 Eloquent 记录的唯一方式。尽管资源能满足许多需求,但资源的「index」(根)页面包含一张列出该资源记录的表格。

有时并不需要列出资源记录的表格,用户只与单条记录交互。若用户访问页面时记录尚不存在,则在首次提交表单保存时创建;若记录已存在,则在页面首次加载时填入表单,并在提交时更新。

例如,CMS 可能有 Page Eloquent 模型与 PageResource,但你也可能想在 PageResource 之外创建单例页面,用于编辑网站「首页」。这样用户可直接编辑首页,而无需进入 PageResource 并在表格中查找首页记录。

其他例子包括「设置」页,或当前登录用户的「个人资料」页。不过对于这些场景,我们建议使用 Spatie Settings 插件 以及 Filament 的 个人资料 功能,实现所需代码更少。

用于管理首页的单例资源页面用于管理首页的单例资源页面

创建单例资源

尽管 Filament 没有专门的「单例资源」功能,但这是很常见的需求,用带 表单自定义页面 即可较简单地实现。本指南将说明做法。

首先,创建 自定义页面

bash
php artisan make:filament-page ManageHomepage

该命令会创建两个文件:资源目录下 /Filament/Pages 中的页面类,以及资源视图目录下 /filament/pages 中的 Blade 视图。

页面类应包含以下元素:

  • `$data` 属性,用于保存表单当前状态。
  • `mount()` 方法,从数据库加载当前记录并用其数据填充表单。若记录不存在,会向表单的 `fill()` 传入 `null`,从而为字段赋默认值。
  • `form()` 方法,定义表单 schema。表单字段写在 `components()` 中。应用 `record()` 指定从表单加载关联数据的记录,并用 `statePath()` 指定存放表单状态的属性名(`$data`)。
  • `save()` 方法,将表单数据保存到数据库。`getState()` 会运行校验并返回有效数据。该方法应检查记录是否已存在,不存在则新建。可用模型的 `wasRecentlyCreated` 判断是否刚创建,若是则还应保存关联。向用户发送通知以确认已保存。
  • `getRecord()` 方法虽非必须,但建议提供。它返回表单正在编辑的 Eloquent 记录,可在其他方法中复用以避免重复代码。
php
namespace App\Filament\Pages;

use App\Models\WebsitePage;
use Filament\Actions\Action;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Form;
use Filament\Schemas\Schema;

/**
 * @property-read Schema $form
 */
class ManageHomepage extends Page
{
    protected string $view = 'filament.pages.manage-homepage';

    /**
     * @var array<string, mixed> | null
     */
    public ?array $data = [];

    public function mount(): void
    {
        $this->form->fill($this->getRecord()?->attributesToArray());
    }

    public function form(Schema $schema): Schema
    {
        return $schema
            ->components([
                Form::make([
                    TextInput::make('title')
                        ->required()
                        ->maxLength(255),
                    RichEditor::make('content'),
                    // ...
                ])
                    ->livewireSubmitHandler('save')
                    ->footer([
                        Actions::make([
                            Action::make('save')
                                ->submit('save')
                                ->keyBindings(['mod+s']),
                        ]),
                    ]),
            ])
            ->record($this->getRecord())
            ->statePath('data');
    }

    public function save(): void
    {
        $data = $this->form->getState();
        
        $record = $this->getRecord();
        
        if (! $record) {
            $record = new WebsitePage();
            $record->is_homepage = true;
        }
        
        $record->fill($data);
        $record->save();
        
        if ($record->wasRecentlyCreated) {
            $this->form->record($record)->saveRelationships();
        }

        Notification::make()
            ->success()
            ->title('Saved')
            ->send();
    }
    
    public function getRecord(): ?WebsitePage
    {
        return WebsitePage::query()
            ->where('is_homepage', true)
            ->first();
    }
}

页面的 Blade 视图应渲染表单:

blade
<x-filament::page>
    {{ $this->form }}
</x-filament::page>