Skip to content
全部文档

自定义数据

简介

Filament 的表格构建器最初设计为在 Laravel 应用中使用 Eloquent 模型直接从 SQL 数据库渲染数据。Filament 表格中的每一行对应数据库中的一行,由一个 Eloquent 模型实例表示。

然而,这种设置并不总是可行或实用。你可能需要显示未存储在数据库中的数据——或者已存储但无法通过 Eloquent 访问的数据。

在这种情况下,你可以使用自定义数据。向表格构建器的 records() 方法传入一个返回数据数组的函数。该函数在表格渲染时调用,其返回值用于填充表格。

php
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;

public function table(Table $table): Table
{
    return $table
        ->records(fn (): array => [
            1 => [
                'title' => 'First item',
                'slug' => 'first-item',
                'is_featured' => true,
            ],
            2 => [
                'title' => 'Second item',
                'slug' => 'second-item',
                'is_featured' => false,
            ],
            3 => [
                'title' => 'Third item',
                'slug' => 'third-item',
                'is_featured' => true,
            ],
        ])
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('slug'),
            IconColumn::make('is_featured')
                ->boolean(),
        ]);
}

WARNING

数组键(例如 123)表示记录 ID。请使用唯一且一致的键,以确保正确的 diff 和状态跟踪。这有助于在 Livewire 交互和更新期间避免记录完整性问题。

表格中的与使用 Eloquent 模型时的工作方式类似,但有一个关键区别:列名不再指向模型属性或关联关系,而是表示 records() 函数返回数组中的键。

在列函数中处理当前记录时,将 $record 类型设为 array 而不是 Model。例如,要使用 state() 函数定义列,可以这样做:

php
use Filament\Tables\Columns\TextColumn;

TextColumn::make('is_featured')
    ->state(function (array $record): string {
        return $record['is_featured'] ? 'Featured' : 'Not featured';
    })

排序

Filament 内置的排序功能使用 SQL 对数据进行排序。使用自定义数据时,你需要自行处理排序。

要访问当前排序的列和方向,可以向 records() 函数注入 $sortColumn$sortDirection。如果未应用排序,这些变量为 null

在下面的示例中,使用集合按键对数据进行排序。返回的是集合而不是数组,Filament 会以相同方式处理。不过,使用此功能并不必须使用集合。

php
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Collection;

public function table(Table $table): Table
{
    return $table
        ->records(
            fn (?string $sortColumn, ?string $sortDirection): Collection => collect([
                1 => ['title' => 'First item'],
                2 => ['title' => 'Second item'],
                3 => ['title' => 'Third item'],
            ])->when(
                filled($sortColumn),
                fn (Collection $data): Collection => $data->sortBy(
                    $sortColumn,
                    SORT_REGULAR,
                    $sortDirection === 'desc',
                ),
            )
        )
        ->columns([
            TextColumn::make('title')
                ->sortable(),
        ]);
}

INFO

看起来 Filament 应该替你排序数据,但在许多情况下,更好的做法是让数据源——例如自定义查询或 API 调用——来处理排序。

搜索

Filament 内置的搜索功能使用 SQL 搜索数据。使用自定义数据时,你需要自行处理搜索。

要访问当前搜索查询,可以向 records() 函数注入 $search。如果当前未使用搜索查询,该变量为 null

在下面的示例中,使用集合按搜索查询过滤数据。返回的是集合而不是数组,Filament 会以相同方式处理。不过,使用此功能并不必须使用集合。

php
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

public function table(Table $table): Table
{
    return $table
        ->records(
            fn (?string $search): Collection => collect([
                1 => ['title' => 'First item'],
                2 => ['title' => 'Second item'],
                3 => ['title' => 'Third item'],
            ])->when(
                filled($search),
                fn (Collection $data): Collection => $data->filter(
                    fn (array $record): bool => str_contains(
                        Str::lower($record['title']),
                        Str::lower($search),
                    ),
                ),
            )
        )
        ->columns([
            TextColumn::make('title'),
        ])
        ->searchable();
}

在此示例中,像 title 这样的特定列不需要设为 searchable(),因为搜索逻辑在 records() 函数内部处理。不过,若想启用搜索字段而不为特定列启用搜索,可以在整个表格上使用 searchable() 方法。

INFO

看起来 Filament 应该替你搜索数据,但在许多情况下,更好的做法是让数据源——例如自定义查询或 API 调用——来处理搜索。

搜索单独的列

单独列搜索功能可为每列分别渲染搜索字段,从而实现更精确的过滤。使用自定义数据时,你需要自行实现此功能。

你可以向 records() 函数注入 $columnSearches 数组(包含每列的搜索查询),而不是注入 $search

php
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

public function table(Table $table): Table
{
    return $table
        ->records(
            fn (array $columnSearches): Collection => collect([
                1 => ['title' => 'First item'],
                2 => ['title' => 'Second item'],
                3 => ['title' => 'Third item'],
            ])->when(
                filled($columnSearches['title'] ?? null),
                fn (Collection $data) => $data->filter(
                    fn (array $record): bool => str_contains(
                        Str::lower($record['title']),
                        Str::lower($columnSearches['title'])
                    ),
                ),
            )
        )
        ->columns([
            TextColumn::make('title')
                ->searchable(isIndividual: true),
        ]);
}

INFO

看起来 Filament 应该替你搜索数据,但在许多情况下,更好的做法是让数据源——例如自定义查询或 API 调用——来处理搜索。

筛选器

Filament 还提供了使用筛选器过滤数据的方式。使用自定义数据时,你需要自行处理筛选。

Filament 通过向 records() 函数注入 $filters,让你访问筛选器数据数组。该数组以筛选器名称为键,值为筛选器表单本身的值。

在下面的示例中,使用集合过滤数据。返回的是集合而不是数组,Filament 会以相同方式处理。不过,使用此功能并不必须使用集合。

php
use Filament\Forms\Components\DatePicker;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Support\Collection;

public function table(Table $table): Table
{
    return $table
        ->records(fn (array $filters): Collection => collect([
            1 => [
                'title' => 'What is Filament?',
                'slug' => 'what-is-filament',
                'author' => 'Dan Harrin',
                'is_featured' => true,
                'creation_date' => '2021-01-01',
            ],
            2 => [
                'title' => 'Top 5 best features of Filament',
                'slug' => 'top-5-features',
                'author' => 'Ryan Chandler',
                'is_featured' => false,
                'creation_date' => '2021-03-01',
            ],
            3 => [
                'title' => 'Tips for building a great Filament plugin',
                'slug' => 'plugin-tips',
                'author' => 'Zep Fietje',
                'is_featured' => true,
                'creation_date' => '2023-06-01',
            ],
        ])
            ->when(
                $filters['is_featured']['isActive'] ?? false,
                fn (Collection $data): Collection => $data->where(
                    'is_featured', true
                ),
            )
            ->when(
                filled($author = $filters['author']['value'] ?? null),
                fn (Collection $data): Collection => $data->where(
                    'author', $author
                ),
            )
            ->when(
                filled($date = $filters['creation_date']['date'] ?? null),
                fn (Collection $data): Collection => $data->where(
                    'creation_date', $date
                ),
            )
        )
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('slug'),
            IconColumn::make('is_featured')
                ->boolean(),
            TextColumn::make('author'),
        ])
        ->filters([
            Filter::make('is_featured'),
            SelectFilter::make('author')
                ->options([
                    'Dan Harrin' => 'Dan Harrin',
                    'Ryan Chandler' => 'Ryan Chandler',
                    'Zep Fietje' => 'Zep Fietje',
                ]),
            Filter::make('creation_date')
                ->schema([
                    DatePicker::make('date'),
                ]),
        ]);
}

筛选器的值不能直接通过 $filters['filterName'] 访问。相反,每个筛选器包含一个或多个表单字段,这些字段名用作筛选器数据数组中的键。例如:

  • 没有自定义 Schema 的[复选框](/5.x/tables/filters/overview#introduction)或[开关筛选器](/5.x/tables/filters/overview#using-a-toggle-button-instead-of-a-checkbox)(例如 featured)使用 `isActive` 作为 `key`:`$filters['featured']['isActive']`
  • [选择筛选器](/5.x/tables/filters/select#introduction)(例如 author)使用 `value`:`$filters['author']['value']`
  • [自定义 Schema 筛选器](/5.x/tables/filters/custom#custom-filter-schemas)(例如 creation_date)使用实际的表单字段名。如果字段名为 `date`,则这样访问:`$filters['creation_date']['date']`

INFO

看起来 Filament 应该替你筛选数据,但在许多情况下,更好的做法是让数据源——例如自定义查询或 API 调用——来处理筛选。

分页

Filament 内置的分页功能使用 SQL 对数据进行分页。使用自定义数据时,你需要自行处理分页。

$page$recordsPerPage 参数会注入到 records() 函数中,你可以用它们对数据进行分页。records() 函数应返回 LengthAwarePaginator,Filament 会为你处理分页链接及其他分页功能:

php
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;

public function table(Table $table): Table
{
    return $table
        ->records(function (int $page, int $recordsPerPage): LengthAwarePaginator {
            $records = collect([
                1 => ['title' => 'What is Filament?'],
                2 => ['title' => 'Top 5 best features of Filament'],
                3 => ['title' => 'Tips for building a great Filament plugin'],
            ])->forPage($page, $recordsPerPage);

            return new LengthAwarePaginator(
                $records,
                total: 30, // Total number of records across all pages
                perPage: $recordsPerPage,
                currentPage: $page,
            );
        })
        ->columns([
            TextColumn::make('title'),
        ]);
}

在此示例中,使用 forPage() 方法对数据进行分页。这可能不是从查询或 API 分页数据的最高效方式,但可以简单地演示如何对自定义数组中的数据进行分页。

INFO

看起来 Filament 应该替你分页数据,但在许多情况下,更好的做法是让数据源——例如自定义查询或 API 调用——来处理分页。

WARNING

如果页面上有多个自定义表格,并使用 queryStringIdentifier('customIdentifier') 方法区分它们,则需要向 LengthAwarePaginator 添加带 pageNameoptions 参数,例如 options: ['pageName' => 'customIdentifierPage']。注意,此处必须在自定义标识符后加上 Page 后缀。

操作

表格中的操作与使用 Eloquent 模型时的工作方式类似。唯一的区别是,操作回调函数中的 $record 参数将是 array 而不是 Model

php
use Filament\Actions\Action;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Collection;

public function table(Table $table): Table
{
    return $table
        ->records(fn (): Collection => collect([
            1 => [
                'title' => 'What is Filament?',
                'slug' => 'what-is-filament',
            ],
            2 => [
                'title' => 'Top 5 best features of Filament',
                'slug' => 'top-5-features',
            ],
            3 => [
                'title' => 'Tips for building a great Filament plugin',
                'slug' => 'plugin-tips',
            ],
        ]))
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('slug'),
        ])
        ->recordActions([
            Action::make('view')
                ->color('gray')
                ->icon(Heroicon::Eye)
                ->url(fn (array $record): string => route('posts.view', $record['slug'])),
        ]);
}

WARNING

使用自定义数据时,执行操作后表格数据不会自动刷新。如果你的操作会更改当前分页页中表格记录的状态,应在操作函数中调用 $this->resetTable()

批量操作

对于与单条记录交互的操作,该记录始终存在于当前表格页上,因此可以使用 records() 方法获取数据。但对于批量操作,可以跨分页页选择记录。若要使用跨页选择记录的批量操作,需要给 Filament 一种跨页获取记录的方式,否则它只会返回当前页的记录。resolveSelectedRecordsUsing() 方法应接受一个带 $keys 参数的函数,并返回记录数据数组:

php
use Filament\Actions\BulkAction;
use Filament\Tables\Table;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;

public function table(Table $table): Table
{
    return $table
        ->records(function (): array {
            // ...
        })
        ->resolveSelectedRecordsUsing(function (array $keys): array {
            return Arr::only([
                1 => [
                    'title' => 'First item',
                    'slug' => 'first-item',
                    'is_featured' => true,
                ],
                2 => [
                    'title' => 'Second item',
                    'slug' => 'second-item',
                    'is_featured' => false,
                ],
                3 => [
                    'title' => 'Third item',
                    'slug' => 'third-item',
                    'is_featured' => true,
                ],
            ], $keys);
        })
        ->columns([
            // ...
        ])
        ->toolbarActions([
            BulkAction::make('feature')
                ->requiresConfirmation()
                ->action(function (Collection $records): void {
                    // Do something with the collection of `$records` data
                }),
        ]);
}

不过,如果用户使用「全选」(Select All)按钮跨分页页选择所有记录,Filament 会在内部改为跟踪取消选中的记录,而不是已选中的记录。这在数据集非常大时是一种高效机制。你可以向 resolveSelectedRecordsUsing() 方法注入两个额外参数来处理这种情况:$isTrackingDeselectedKeys$deselectedKeys

$isTrackingDeselectedKeys 是一个布尔值,表示用户是否正在跟踪取消选中的键。如果为 true$deselectedKeys 将包含当前取消选中记录的键。你可以使用此信息从 resolveSelectedRecordsUsing() 方法返回的记录数组中过滤掉取消选中的记录:

php
use Filament\Actions\BulkAction;
use Filament\Tables\Table;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;

public function table(Table $table): Table
{
    return $table
        ->records(function (): array {
            // ...
        })
        ->resolveSelectedRecordsUsing(function (
            array $keys,
            bool $isTrackingDeselectedKeys,
            array $deselectedKeys
        ): array {
            $records = [
                1 => [
                    'title' => 'First item',
                    'slug' => 'first-item',
                    'is_featured' => true,
                ],
                2 => [
                    'title' => 'Second item',
                    'slug' => 'second-item',
                    'is_featured' => false,
                ],
                3 => [
                    'title' => 'Third item',
                    'slug' => 'third-item',
                    'is_featured' => true,
                ],
            ];
            
            if ($isTrackingDeselectedKeys) {
                return Arr::except(
                    $records,
                    $deselectedKeys,
                );
            }
            
            return Arr::only(
                $records,
                $keys,
            );
        })
        ->columns([
            // ...
        ])
        ->toolbarActions([
            BulkAction::make('feature')
                ->requiresConfirmation()
                ->action(function (Collection $records): void {
                    // Do something with the collection of `$records` data
                }),
        ]);
}

使用外部 API 作为表格数据源

Filament 的表格构建器允许你用从任何外部源获取的数据填充表格——而不仅仅是 Eloquent 模型。当你想显示来自 REST API 或第三方服务的数据时,这特别有用。

从外部 API 获取数据

下面的示例演示如何从 DummyJSON(一个免费的假 REST API,用于占位 JSON)消费数据,并将其显示在 Filament 表格中:

php
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Http;

public function table(Table $table): Table
{
    return $table
        ->records(fn (): array => Http::baseUrl('https://dummyjson.com')
            ->get('products')
            ->collect()
            ->get('products', [])
        )
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('category'),
            TextColumn::make('price')
                ->money(),
        ]);
}

get('products')https://dummyjson.com/products 发出 GET 请求。collect() 方法将 JSON 响应转换为 Laravel 集合。最后,get('products', []) 从响应中检索产品数组。如果键缺失,则安全地返回空数组。

WARNING

这只是用于演示的基础示例。在使用 API 时,开发者有责任实现适当的身份验证、授权、验证、错误处理、速率限制以及其他最佳实践。

INFO

DummyJSON 默认返回 30 条。你可以使用 limit 和 skip 查询参数分页获取所有条目,或使用 limit=0 获取全部条目。

使用 API 数据设置列的状态

映射到 records() 函数返回的数组键。

在列函数中处理当前记录时,将 $record 类型设为 array 而不是 Model。例如,要使用 state() 函数定义列,可以这样做:

php
use Filament\Tables\Columns\TextColumn;
use Illuminate\Support\Str;

TextColumn::make('category_brand')
    ->label('Category - Brand')
    ->state(function (array $record): string {
        $category = Str::headline($record['category']);
        $brand = Str::title($record['brand'] ?? 'Unknown');

        return "{$category} - {$brand}";
    })

TIP

你可以使用 formatStateUsing() 方法格式化文本列的状态,而不改变状态本身。

外部 API 排序

即使使用外部 API 作为数据源,也可以在中启用排序。下面的示例演示如何将排序参数(sort_columnsort_direction)传递给 DummyJSON API,以及 API 如何处理它们。

php
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Http;

public function table(Table $table): Table
{
    return $table
        ->records(function (?string $sortColumn, ?string $sortDirection): array {
            $response = Http::baseUrl('https://dummyjson.com/')
                ->get('products', [
                    'sortBy' => $sortColumn,
                    'order' => $sortDirection,
                ]);

            return $response
                ->collect()
                ->get('products', []);
        })
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('category')
                ->sortable(),
            TextColumn::make('price')
                ->money(),
        ]);
}

get('products')https://dummyjson.com/products 发出 GET 请求。该请求包含两个参数:sortBy 指定要排序的列(例如 category),order 指定排序方向(例如 asc 或 desc)。collect() 方法将 JSON 响应转换为 Laravel 集合。最后,get('products', []) 从响应中检索产品数组。如果键缺失,则安全地返回空数组。

WARNING

这只是用于演示的基础示例。在使用 API 时,开发者有责任实现适当的身份验证、授权、验证、错误处理、速率限制以及其他最佳实践。

INFO

DummyJSON 默认返回 30 条。你可以使用 limit 和 skip 查询参数分页获取所有条目,或使用 limit=0 获取全部条目。

外部 API 搜索

即使使用外部 API 作为数据源,也可以在中启用搜索。下面的示例演示如何将 search 参数传递给 DummyJSON API,以及 API 如何处理它。

php
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Http;

public function table(Table $table): Table
{
    return $table
        ->records(function (?string $search): array {
            $response = Http::baseUrl('https://dummyjson.com/')
                ->get('products/search', [
                    'q' => $search,
                ]);

            return $response
                ->collect()
                ->get('products', []);
        })
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('category'),
            TextColumn::make('price')
                ->money(),
        ])
        ->searchable();
}

get('products/search')https://dummyjson.com/products/search 发出 GET 请求。该请求包含 q 参数,用于根据 search 查询过滤结果。collect() 方法将 JSON 响应转换为 Laravel 集合。最后,get('products', []) 从响应中检索产品数组。如果键缺失,则安全地返回空数组。

WARNING

这只是用于演示的基础示例。在使用 API 时,开发者有责任实现适当的身份验证、授权、验证、错误处理、速率限制以及其他最佳实践。

INFO

DummyJSON 默认返回 30 条。你可以使用 limit 和 skip 查询参数分页获取所有条目,或使用 limit=0 获取全部条目。

外部 API 筛选

即使使用外部 API 作为数据源,也可以在表格中启用筛选。下面的示例演示如何将 filter 参数传递给 DummyJSON API,以及 API 如何处理它。

php
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;

public function table(Table $table): Table
{
    return $table
        ->records(function (array $filters): array {
            $category = $filters['category']['value'] ?? null;

            $endpoint = filled($category)
                ? "products/category/{$category}"
                : 'products';

            $response = Http::baseUrl('https://dummyjson.com/')
                ->get($endpoint);

            return $response
                ->collect()
                ->get('products', []);
        })
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('category'),
            TextColumn::make('price')
                ->money(),
        ])
        ->filters([
            SelectFilter::make('category')
                ->label('Category')
                ->options(fn (): Collection => Http::baseUrl('https://dummyjson.com/')
                    ->get('products/categories')
                    ->collect()
                    ->pluck('name', 'slug')
                ),
        ]);
}

如果选择了分类筛选器,请求会发往 /products/category/{category};否则默认发往 /productsget() 方法向相应端点发送 GET 请求。collect() 方法将 JSON 响应转换为 Laravel 集合。最后,get('products', []) 从响应中检索产品数组。如果键缺失,则安全地返回空数组。

WARNING

这只是用于演示的基础示例。在使用 API 时,开发者有责任实现适当的身份验证、授权、验证、错误处理、速率限制以及其他最佳实践。

INFO

DummyJSON 默认返回 30 条。你可以使用 limit 和 skip 查询参数分页获取所有条目,或使用 limit=0 获取全部条目。

外部 API 分页

使用外部 API 作为表格数据源时,可以启用分页。Filament 会将当前页和每页记录数传递给你的 records() 函数。下面的示例演示如何手动构造 LengthAwarePaginator,并从使用 limitskip 参数进行分页的 DummyJSON API 获取分页数据:

php
public function table(Table $table): Table
{
    return $table
        ->records(function (int $page, int $recordsPerPage): LengthAwarePaginator {
            $skip = ($page - 1) * $recordsPerPage;

            $response = Http::baseUrl('https://dummyjson.com')
                ->get('products', [
                    'limit' => $recordsPerPage,
                    'skip' => $skip,
                ])
                ->collect();

            return new LengthAwarePaginator(
                items: $response['products'],
                total: $response['total'],
                perPage: $recordsPerPage,
                currentPage: $page
            );
        })
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('category'),
            TextColumn::make('price')
                ->money(),
        ]);
}

$page$recordsPerPage 由 Filament 根据当前分页状态自动注入。

计算出的 skip 值告诉 API 在返回当前页结果之前要跳过多少条记录。

响应包含 products(分页后的条目)和 total(可用条目的总数)。

这些值会传给 LengthAwarePaginator,Filament 用它来正确渲染分页控件。

WARNING

这只是用于演示的基础示例。在使用 API 时,开发者有责任实现适当的身份验证、授权、验证、错误处理、速率限制以及其他最佳实践。

外部 API 操作

在使用外部 API 的表格中使用操作时,过程与使用 Eloquent 模型几乎相同。主要区别是操作回调函数中的 $record 参数将是 array 而不是 Model 实例。

Filament 提供了多种可在应用中使用的内置操作。不过你并不限于这些。你可以创建针对应用需求的自定义操作

下面的示例演示如何使用 DummyJSON 作为模拟 API 源,创建并使用与外部 API 配合的操作。

外部 API 创建操作示例

此示例中的创建操作提供一个模态表单,允许用户使用外部 API 创建新产品。提交表单时,会向 API 发送 POST 请求以创建新产品。

php
use Filament\Actions\Action;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;

public function table(Table $table): Table
{
    $baseUrl = 'https://dummyjson.com';

    return $table
        ->records(fn (): array => Http::baseUrl($baseUrl)
            ->get('products')
            ->collect()
            ->get('products', [])
        )
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('category'),
        ])
        ->headerActions([
            Action::make('create')
                ->modalHeading('Create product')
                ->schema([
                    TextInput::make('title')
                        ->required(),
                    Select::make('category')
                        ->options(fn (): Collection => Http::get("{$baseUrl}/products/categories")
                            ->collect()
                            ->pluck('name', 'slug')
                        )
                        ->required(),
                ])
                ->action(function (array $data) use ($baseUrl) {
                    $response = Http::post("{$baseUrl}/products/add", [
                        'title' => $data['title'],
                        'category' => $data['category'],
                    ]);

                    if ($response->failed()) {
                        Notification::make()
                            ->title('Product failed to create')
                            ->danger()
                            ->send();
                            
                        return;
                    }
                    
                    Notification::make()
                        ->title('Product created')
                        ->success()
                        ->send();
                }),
        ]);
}
  • [`modalHeading()`](/5.x/actions/modals#customizing-the-modals-heading-description-and-submit-action-label) 设置触发操作时出现的模态框标题。
  • [`schema()`](/5.x/actions/modals#rendering-a-schema-in-a-modal) 定义模态框中显示的表单字段。
  • `action()` 定义用户提交表单时将执行的逻辑。

WARNING

这只是用于演示的基础示例。在使用 API 时,开发者有责任实现适当的身份验证、授权、验证、错误处理、速率限制以及其他最佳实践。

WARNING

DummyJSON API 不会将其添加到服务器。它会模拟 POST 请求,并返回带有新 id 的新创建产品。

如果不需要模态框,可以在用户点击创建操作按钮时直接将用户重定向到指定 URL。此时,你可以定义指向产品创建页面的自定义 URL:

php
use Filament\Actions\Action;

Action::make('create')
    ->url(route('products.create'))

外部 API 编辑操作示例

此示例中的编辑操作为从外部 API 获取的产品详情提供模态表单。用户可以更新产品标题和分类等字段,更改将通过 PUT 请求发送到外部 API。

php
use Filament\Actions\Action;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;

public function table(Table $table): Table
{
    $baseUrl = 'https://dummyjson.com';

    return $table
        ->records(fn (): array => Http::baseUrl($baseUrl)
            ->get('products')
            ->collect()
            ->get('products', [])
        )
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('category'),
        ])
        ->recordActions([
            Action::make('edit')
                ->icon(Heroicon::PencilSquare)
                ->modalHeading('Edit product')
                ->fillForm(fn (array $record) => $record)
                ->schema([
                    TextInput::make('title')
                        ->required(),
                    Select::make('category')
                        ->options(fn (): Collection => Http::get("{$baseUrl}/products/categories")
                            ->collect()
                            ->pluck('name', 'slug')
                        )
                        ->required(),
                ])
                ->action(function (array $data, array $record) use ($baseUrl) {
                    $response = Http::put("{$baseUrl}/products/{$record['id']}", [
                        'title' => $data['title'],
                        'category' => $data['category'],
                    ]);

                    if ($response->failed()) {
                        Notification::make()
                            ->title('Product failed to save')
                            ->danger()
                            ->send();
                            
                        return;
                    }
                    
                    Notification::make()
                        ->title('Product save')
                        ->success()
                        ->send();
                }),
        ]);
}
  • `icon()` 定义此操作在表格中显示的图标。
  • [`modalHeading()`](/5.x/actions/modals#customizing-the-modals-heading-description-and-submit-action-label) 设置触发操作时出现的模态框标题。
  • [`fillForm()`](/5.x/actions/modals#filling-the-form-with-existing-data) 使用所选记录的现有值自动填充表单字段。
  • [`schema()`](/5.x/actions/modals#rendering-a-schema-in-a-modal) 定义模态框中显示的表单字段。
  • `action()` 定义用户提交表单时将执行的逻辑。

WARNING

这只是用于演示的基础示例。在使用 API 时,开发者有责任实现适当的身份验证、授权、验证、错误处理、速率限制以及其他最佳实践。

WARNING

DummyJSON API 不会在服务器上更新它。它会模拟 PUT/PATCH 请求,并返回带有修改数据的已更新产品。

如果不需要模态框,可以在用户点击操作按钮时直接将用户重定向到指定 URL。你可以通过定义包含 record 参数的动态路由 URL 来实现:

php
use Filament\Actions\Action;

Action::make('edit')
    ->url(fn (array $record): string => route('products.edit', ['product' => $record['id']]))

外部 API 查看操作示例

此示例中的查看操作会打开一个模态框,显示从外部 API 获取的详细产品信息。这让你可以使用各种组件构建用户界面,例如文本条目图片

php
use Filament\Actions\Action;
use Filament\Infolists\Components\ImageEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Flex;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Http;

public function table(Table $table): Table
{
    $baseUrl = 'https://dummyjson.com';

    return $table
        ->records(fn (): array => Http::baseUrl($baseUrl)
            ->get('products', [
                'select' => 'id,title,description,brand,category,thumbnail,price',
            ])
            ->collect()
            ->get('products', [])
        )
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('category'),
        ])
        ->recordActions([
            Action::make('view')
                ->color('gray')
                ->icon(Heroicon::Eye)
                ->modalHeading('View product')
                ->schema([
                    Section::make()
                        ->schema([
                            Flex::make([
                                Grid::make(2)
                                    ->schema([
                                        TextEntry::make('title'),
                                        TextEntry::make('category'),
                                        TextEntry::make('brand'),
                                        TextEntry::make('price')
                                            ->money(),
                                    ]),
                                ImageEntry::make('thumbnail')
                                    ->hiddenLabel()
                                    ->grow(false),
                            ])->from('md'),
                            TextEntry::make('description')
                                ->prose(),
                        ]),
                ])
                ->modalSubmitAction(false)
                ->modalCancelActionLabel('Close'),
        ]);
}
  • `color()` 设置操作按钮的颜色。
  • `icon()` 定义此操作在表格中显示的图标。
  • [`modalHeading()`](/5.x/actions/modals#customizing-the-modals-heading-description-and-submit-action-label) 设置触发操作时出现的模态框标题。
  • [`schema()`](/5.x/actions/modals#rendering-a-schema-in-a-modal) 定义模态框中显示的表单字段。
  • [`modalSubmitAction(false)`](/5.x/actions/modals#modifying-the-default-modal-footer-action-button) 禁用提交按钮,使此操作为只读查看操作。
  • [`modalCancelActionLabel()`](/5.x/actions/modals#modifying-the-default-modal-footer-action-button) 自定义关闭按钮的标签。

WARNING

这只是用于演示的基础示例。在使用 API 时,开发者有责任实现适当的身份验证、授权、验证、错误处理、速率限制以及其他最佳实践。

INFO

select 参数用于限制 API 返回的字段。这有助于减小载荷大小,并在渲染表格时提升性能。

如果不需要模态框,可以在用户点击操作按钮时直接将用户重定向到指定 URL。你可以通过定义包含 record 参数的动态路由 URL 来实现:

php
use Filament\Actions\Action;

Action::make('view')
    ->url(fn (array $record): string => route('products.view', ['product' => $record['id']]))

外部 API 删除操作示例

此示例中的删除操作允许用户删除从外部 API 获取的产品。

php
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Http;

public function table(Table $table): Table
{
    $baseUrl = 'https://dummyjson.com';

    return $table
        ->records(fn (): array => Http::baseUrl($baseUrl)
            ->get('products')
            ->collect()
            ->get('products', [])
        )
        ->columns([
            TextColumn::make('title'),
            TextColumn::make('category'),
            TextColumn::make('price')
                ->money(),
        ])
        ->recordActions([
            Action::make('delete')
                ->color('danger')
                ->icon(Heroicon::Trash)
                ->modalIcon(Heroicon::OutlinedTrash)
                ->modalHeading('Delete Product')
                ->requiresConfirmation()
                ->action(function (array $record) use ($baseUrl) {
                    $response = Http::baseUrl($baseUrl)
                        ->delete("products/{$record['id']}");

                    if ($response->failed()) {
                        Notification::make()
                            ->title('Product failed to delete')
                            ->danger()
                            ->send();
                            
                        return;
                    }
                    
                    Notification::make()
                        ->title('Product deleted')
                        ->success()
                        ->send();
                }),
        ]);
}
  • `color()` 设置操作按钮的颜色。
  • `icon()` 定义此操作在表格中显示的图标。
  • [`modalIcon()`](/5.x/actions/modals#adding-an-icon-inside-the-modal) 设置将出现在确认模态框中的图标。
  • [`modalHeading()`](/5.x/actions/modals#customizing-the-modals-heading-description-and-submit-action-label) 设置触发操作时出现的模态框标题。
  • [`requiresConfirmation()`](/5.x/actions/modals#confirmation-modals) 确保用户必须在执行删除前确认。
  • `action()` 定义用户确认提交时将执行的逻辑。

WARNING

这只是用于演示的基础示例。在使用 API 时,开发者有责任实现适当的身份验证、授权、验证、错误处理、速率限制以及其他最佳实践。

WARNING

DummyJSON API 不会在服务器上删除它。它会模拟 DELETE 请求,并返回带有 isDeleteddeletedOn 键的已删除产品。

外部 API 完整示例

此示例演示在使用外部 API 作为数据源时,如何组合排序搜索分类筛选分页。此处使用的 API 是 DummyJSON,它分别支持这些功能,但不允许在单个请求中组合所有这些功能。这是因为每个功能使用不同的端点:

  • [搜索](#external-api-searching)通过 `/products/search` 端点使用 `q` 参数执行。
  • [分类筛选](#external-api-filtering)使用 `/products/category/{category}` 端点。
  • [排序](#external-api-sorting)通过向 `/products` 端点发送 `sortBy` 和 `order` 参数来处理。

唯一可以与上述各项组合的功能是分页,因为所有三个端点都支持 limitskip 参数。

php
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

public function table(Table $table): Table
{
    $baseUrl = 'https://dummyjson.com/';

    return $table
        ->records(function (
            ?string $sortColumn,
            ?string $sortDirection,
            ?string $search,
            array $filters,
            int $page,
            int $recordsPerPage
        ) use ($baseUrl): LengthAwarePaginator {
            // Get the selected category from filters (if any)
            $category = $filters['category']['value'] ?? null;

            // Choose endpoint depending on search or filter
            $endpoint = match (true) {
                filled($search) => 'products/search',
                filled($category) => "products/category/{$category}",
                default => 'products',
            };

            // Determine skip offset
            $skip = ($page - 1) * $recordsPerPage;

            // Base query parameters for all requests
            $params = [
                'limit' => $recordsPerPage,
                'skip' => $skip,
                'select' => 'id,title,brand,category,thumbnail,price,sku,stock',
            ];

            // Add search query if applicable
            if (filled($search)) {
                $params['q'] = $search;
            }

            // Add sorting parameters
            if ($endpoint === 'products' && $sortColumn) {
                $params['sortBy'] = $sortColumn;
                $params['order'] = $sortDirection ?? 'asc';
            }

            $response = Http::baseUrl($baseUrl)
                ->get($endpoint, $params)
                ->collect();

            return new LengthAwarePaginator(
                items: $response['products'],
                total: $response['total'],
                perPage: $recordsPerPage,
                currentPage: $page
            );
        })
        ->columns([
            ImageColumn::make('thumbnail')
                ->label('Image'),
            TextColumn::make('title')
                ->sortable(),
            TextColumn::make('brand')
                ->state(fn (array $record): string => Str::title($record['brand'] ?? 'Unknown')),
            TextColumn::make('category')
                ->formatStateUsing(fn (string $state): string => Str::headline($state)),
            TextColumn::make('price')
                ->money(),
            TextColumn::make('sku')
                ->label('SKU'),
            TextColumn::make('stock')
                ->label('Stock')
                ->sortable(),
        ])
        ->filters([
            SelectFilter::make('category')
                ->label('Category')
                ->options(fn (): Collection => Http::baseUrl($baseUrl)
                    ->get('products/categories')
                    ->collect()
                    ->pluck('name', 'slug')
                ),
        ])
        ->searchable();
}

WARNING

这只是用于演示的基础示例。在使用 API 时,开发者有责任实现适当的身份验证、授权、验证、错误处理、速率限制以及其他最佳实践。

WARNING

DummyJSON API 不支持在单个请求中组合排序、搜索和分类筛选。

INFO

select 参数用于限制 API 返回的字段。这有助于减小载荷大小,并在渲染表格时提升性能。