NOTE

You are currently viewing the documentation for Filament 4.x, which is a previous version of Filament.

Looking for the current stable version? Visit the 5.x documentation.

表格

自定义数据

简介

Filament 的表格构造器最初是设计成使用 Laravel 中的 Eloquent 模型直接从数据库获取渲染数据的。Filament 表格中的每一行对应数据库的每一条记录,即一个 Eloquent 实例。

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

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

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(),
        ]);
}

NOTE

数组键 (e.g., 1, 2, 3) 表示的是记录的 ID。请使用唯一的,有一致性的值以确保正确区分及状态跟踪。这有助于防止在Livewire 交互和更新过程中出现记录完整性问题。

列字段

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

当在列函数中使用当前记录时,请将 $record 类型设置为 array 而非 Model。比如,要使用 state() 函数定义列时,你应该这样:

use Filament\Tables\Columns\TextColumn;

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

排序

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

要获取当前的排序列字段和排序方向,你可以将 $sortColumn$sortDirection 注入到 records() 函数。如果没有使用排序,这些变量为 null

下例中,有一个集合用于根据键排序数据。其返回的是集合而非数组,而 Filament 处理的方式与数组相同。不过使用集合不一定非得使用该特性。

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(),
        ]);
}

NOTE

看起来像是应该由 Filament 来帮你排序,不过很多情况下,最好是由你的数据源(比如自定义查询或者 API 调用)来处理排序。

搜索

Filament 的内置搜索函数使用 SQL 来搜索数据。当使用自定义数据时,你需要自己处理搜索。

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

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

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() 方法。

NOTE

看起来像是应该由 Filament 来帮你搜索数据,不过很多情况下,最好是由你的数据源(比如自定义查询或者 API 调用)来处理搜索。

单列搜索

单列搜索功能提供了一种为每列单独渲染搜索字段的方法,从而实现更精确的筛选。使用自定义数据时,你需要自行实现此功能。

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

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),
        ]);
}

NOTE

看起来像是应该由 Filament 来帮你搜索数据,不过很多情况下,最好是由你的数据源(比如自定义查询或者 API 调用)来处理搜索。

过滤器

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

Filament 允许你通过将 $filters 注入 records() 函数来访问过滤器数据数组。该数组包含过滤器的名称作为键,以及过滤器表单本身的值。

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

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'] 直接访问。每个过滤器包含一个或多个表单字段,这些字段名称用作过滤器数据数组中的键。例如:

  • 复选框Toggle 过滤器不带自定义架构(例如,featured),使用 isActive 作为 key$filters['featured']['isActive']

  • 选择过滤器(例如,author)使用 value$filters['author']['value']

  • 自定义 Schema 过滤器(例如,creation_date)使用实际的表单字段名称。如果字段名为 date,则按如下方式访问:$filters['creation_date']['date']

NOTE

看起来像是应该由 Filament 来帮你过滤数据,不过很多情况下,最好是由你的数据源(比如自定义查询或者 API 调用)来处理过滤。

分页

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

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

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 对数据进行分页的最高效方法,但它可以作为一种简单的方法,演示如何通过自定义数组对数据进行分页。

NOTE

看起来像是应该由 Filament 来帮你分页数据,不过很多情况下,最好是由你的数据源(比如自定义查询或者 API 调用)来处理分页。

Action

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

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'])),
        ]);
}

批量操作

对于与单条记录交互的操作(Action),其记录始终存在于当前表格页面中,因此使用 records() 方法可以获取数据。但是,对于批量操作,记录的选择可以跨页面选择。如果你希望使用跨页面选择记录的批量操作,则需要为 Filament 提供跨页面获取记录的方法,否则它只会返回当前页面中的记录。resolveSelectedRecordsUsing() 方法应该接受一个带有 $keys 参数的函数,并返回一个记录数据数组:

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([
            // ...
        ])
        ->recordActions([
            BulkAction::make('feature')
                ->requiresConfirmation()
                ->action(function (Collection $records): void {
                    // Do something with the collection of `$records` data
                }),
        ]);
}

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

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

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([
            // ...
        ])
        ->recordActions([
            BulkAction::make('feature')
                ->requiresConfirmation()
                ->action(function (Collection $records): void {
                    // Do something with the collection of `$records` data
                }),
        ]);
}

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

Filament 的表格构造器允许你使用从任何外部来源获取的数据来填充表格,而不仅仅是 Eloquent 模型。当你需要显示来自 REST API 或第三方服务的数据时,此功能尤其有用。

从外部 API 获取数据

下面的示例演示了如何使用来自 DummyJSON(一个免费的用于占位符 JSON 的模拟 REST API)的数据,并将其显示在 Filament 表格中:

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', []) 从响应中检索产品数组。如果缺少键,则安全地返回一个空数组。

NOTE

这是一个仅用于演示的基本示例。开发者有责任在使用 API 时实施正确的身份验证、授权、验证、错误处理、速率限制和其他最佳实践。

NOTE

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

使用 API 数据设置列的状态

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

在列函数中处理当前记录时,请将 $record 类型设置为 array 而不是 Model。例如,要使用 state() 函数定义列,你可以执行以下操作:

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 如何处理它们。

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,指定排序依据的列(例如,类别)和 order,指定排序的方向(例如,升序或降序)。collect() 方法将 JSON 响应转换为 Laravel 集合。最后,get('products', []) 从响应中检索产品数组。如果缺少键,则会安全地返回一个空数组。

NOTE

这是一个仅用于演示的基本示例。开发者有责任在使用 API 时实施正确的身份验证、授权、验证、错误处理、速率限制和其他最佳实践。

NOTE

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

外部 API 搜索

即使使用外部 API 作为数据源,你也可以在表格列字段中启用搜索。以下示例演示了如何将 search 参数传递给 DummyJSON API,以及该 API 如何处理该参数。

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', []) 从响应中检索产品数组。如果缺少键,则安全地返回一个空数组。

NOTE

这是一个仅用于演示的基本示例。开发者有责任在使用 API 时实施正确的身份验证、授权、验证、错误处理、速率限制和其他最佳实践。

NOTE

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

外部 API 过滤

即使使用外部 API 作为数据源,你也可以在表格中启用过滤器。以下示例演示了如何将 filter 参数传递给 DummyJSON API,以及该 API 如何处理该参数。

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', []) 从响应中检索产品数组。如果缺少键,则安全地返回一个空数组。

NOTE

这是一个仅用于演示的基本示例。开发者有责任在使用 API 时实施正确的身份验证、授权、验证、错误处理、速率限制和其他最佳实践。

NOTE

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

外部 API 分页

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

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(),
        ]);
}

Filament 根据当前分页状态自动注入 $page$recordsPerPage。 计算出的 skip 值告知 API 在返回当前页面结果之前需要跳过多少条记录。 响应包含 products(已分页的项目)和 total(可用项目的总数)。 这些值会传递给 LengthAwarePaginator,Filament 会使用它来正确渲染分页控件。.

NOTE

这是一个仅用于演示的基本示例。开发者有责任在使用 API 时实施正确的身份验证、授权、验证、错误处理、速率限制和其他最佳实践。

外部 API Action

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

Filament 提供了各种内置操作,你可以在应用中使用它们。但并不局限于这些。可你以根据应用的需求创建自定义操作

以下示例演示了如何使用 DummyJSON 作为模拟 API 源,通过外部 API 创建和使用操作。

外部 API 创建操作示例

下例中的“创建”操作提供了一个模态框表单,允许用户使用外部 API 创建新产品。提交表单后,系统会向 API 发送一个 POST 请求来创建新产品。

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() 设置触发操作时显示的模态框标题。
  • schema() 定义模态框中显示的表单字段。
  • action() 定义用户提交表单时将执行的逻辑。

NOTE

这是一个仅用于演示的基本示例。开发者有责任在使用 API 时实施正确的身份验证、授权、验证、错误处理、速率限制和其他最佳实践。

NOTE

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

如果你不需要模态框,则可以在用户点击“创建”操作按钮时直接将用户重定向到指定的 URL。在这种情况下,你可以定义一个指向产品创建页面的自定义 URL:

use Filament\Actions\Action;

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

外部 API 编辑操作示例

本例中的 edit 操作提供了一个 模态框表单,用于编辑从外部 API 获取的产品详情。用户可以更新产品标题和类别等字段,这些更改将通过 PUT 请求发送到外部 API。

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() 设置触发操作时显示的模态框的标题。
  • fillForm() 自动使用所选记录的现有值填充表单字段。
  • schema() 定义模态框中显示的表单字段。
  • action() 定义用户提交表单时将执行的逻辑。

NOTE

这是一个仅用于演示的基本示例。开发者有责任在使用 API 时实施正确的身份验证、授权、验证、错误处理、速率限制和其他最佳实践。

NOTE

DummyJSON API 不会将其更新到服务器。它将模拟 PUT/PATCH 请求,并使用修改过的数据返回更新过的产品。

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

use Filament\Actions\Action;

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

外部 API 查看操作示例

本示例中的“查看”操作会打开一个模态框,其中显示从外部 API 获取的详细产品信息。这允许你使用各种组件(例如 文本条目图像)构建用户界面。

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() 设置操作(Action)按钮的颜色。
  • icon() 该操作在表格中显示的图标。
  • modalHeading() 设置触发操作时显示的模态框标题。
  • schema() 定义模态框中显示的表单字段。
  • modalSubmitAction(false) 禁用提交按钮,使其成为只读视图操作。
  • modalCancelActionLabel() 自定义关闭按钮的标签。

NOTE

这是一个仅用于演示的基本示例。开发者有责任在使用 API 时实施正确的身份验证、授权、验证、错误处理、速率限制和其他最佳实践。

NOTE

select 参数用于限制 API 返回的字段。这有助于减少负载大小并提高表格渲染的性能。

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

use Filament\Actions\Action;

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

外部 API 删除操作示例

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

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() 设置确认模态框中显示的图标。
  • modalHeading() 设置触发操作时显示的模态框的标题。
  • requiresConfirmation() 确保用户必须在执行删除操作之前确认。
  • action() 定义用户确认提交时将执行的逻辑。

NOTE

这是一个仅用于演示的基本示例。开发者有责任在使用 API 时实施正确的身份验证、授权、验证、错误处理、速率限制和其他最佳实践。

NOTE

DummyJSON API 不会将在服务器上删除数据。它只是模拟 DELETE 请求并返使用 isDeleteddeletedOn 键返回已删除的产品。

外部 API 完整示例

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

  • 搜索通过 /products/search 端点使用 q 参数实现。
  • 类别过滤使用 /products/category/{category} 端点。
  • 排序通过将 sortByorder 参数发送到 /products 端点来处理。

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

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();
}

NOTE

这是一个仅用于演示的基本示例。开发者有责任在使用 API 时实施正确的身份验证、授权、验证、错误处理、速率限制和其他最佳实践。

NOTE

DummyJSON API 不支持在单个请求中组合排序、搜索和类别过滤。

NOTE

select 参数用于限制 API 返回的字段。这有助于减少负载大小并提高表格渲染的性能。

Edit on GitHub

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

Previous
空状态