Actions - 预制 Action
Export action
概述
Filament v3.2 引入了一个预构建的 Action,可以将行导出到 CSV 或 XLSX 文件。当单击触发器按钮时,模态框会询问要导出的列以及它们应该的标签。此功能使用作业批次和数据库迁移,因此你需要从 Laravel 发布这些迁移。此外,你还需要发布 Filament 用于存储导出信息的表的迁移:
# Laravel 11 and higherphp artisan make:queue-batches-tablephp artisan make:notifications-table # Laravel 10php artisan queue:batches-tablephp artisan notifications:table
# All appsphp artisan vendor:publish --tag=filament-actions-migrations php artisan migrate
如果你使用 PostgreSQL,请确保通知迁移的
data
字段使用json()
:$table->json('data')
。
如果在
User
模型中使用 UUID,请确保通知迁移中的notifiable
字段使用uuidMorphs()
:$table->uuidMorphs('notifiable')
。
你可以这样使用 ExportAction
:
use App\Filament\Exports\ProductExporter;use Filament\Actions\ExportAction; ExportAction::make() ->exporter(ProductExporter::class)
如果要将 Action 添加到表格头部,可以使用 Filament\Tables\Actions\ExportAction
:
use App\Filament\Exports\ProductExporter;use Filament\Tables\Actions\ExportAction;use Filament\Tables\Table; public function table(Table $table): Table{ return $table ->headerActions([ ExportAction::make() ->exporter(ProductExporter::class) ]);}
或者如果你想将其添加为表格的批量操作,使得用户可以选择导出那些行,可以使用 Filament\Tables\Actions\ExportBulkAction
:
use App\Filament\Exports\ProductExporter;use Filament\Tables\Actions\ExportBulkAction;use Filament\Tables\Table; public function table(Table $table): Table{ return $table ->bulkActions([ ExportBulkAction::make() ->exporter(ProductExporter::class) ]);}
需要创建 "exporter" 类以告知 Filament 如何导出每一行。
创建导出器
要为模型创建 exporter 类,可以使用 make:filament exporter
命令,传入模型名称:
php artisan make:filament-exporter Product
这将会在 app/Filament/Exports
目录中创建一个新类。现在你需要到定义可以导出的列字段。
自动生成导出列
如果你想要节省时间,Filament 可以使用 --generate
基于模型数据库字段自动生成列字段:
php artisan make:filament-exporter Product --generate
定义导出列
要定义导出的列,你需要在导出器类中重写 getColumns()
方法,使之返回 ExportColumn
对象数组:
use Filament\Actions\Exports\ExportColumn; public static function getColumns(): array{ return [ ExportColumn::make('name'), ExportColumn::make('sku') ->label('SKU'), ExportColumn::make('price'), ];}
自定义导出列标签
每个列的标签将根据其名称自动生成,但你可以通过调用 label()
方法来覆盖它:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('sku') ->label('SKU')
配置默认的列选中
默认情况下,当询问用户要导出哪些列时,所有的列都会被选中。你可以使用 enabledByDefault()
方法自定义列的默认选中状态:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('description') ->enabledByDefault(false)
禁用列选择
默认情况下,会询问用户想导出哪些列。你可以使用 columnMapping(false)
方法,禁用该功能:
use App\Filament\Exports\ProductExporter; ExportAction::make() ->exporter(ProductExporter::class) ->columnMapping(false)
计算导出列的状态
有时你需要计算列的状态,而不是直接从数据库列中读取它。
通过将回调函数传递给 state()
方法,可以根据 $record
自定义该列的返回状态:
use App\Models\Order;use Filament\Actions\Exports\ExportColumn; ExportColumn::make('amount_including_vat') ->state(function (Order $record): float { return $record->amount * (1 + $record->vat_rate); })
格式化导出列的值
相反,你可以将自定义格式化回调传递给 formatStateUsing()
,它接受单元格的 $state
,也可以选择 Eloquent 的 $record
:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('status') ->formatStateUsing(fn (string $state): string => __("statuses.{$state}"))
如果列中有多个值,它会为每个值调用该函数。
限制文本长度
使用 limit()
限制单元格值的长度:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('description') ->limit(50)
限制字数
使用 words()
可以限制单元格中显示的字数:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('description') ->words(10)
添加前后缀
你可以将前缀(prefix()
)或者后缀(suffix()
)添加到单元格的值:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('domain') ->prefix('https://') ->suffix('.com')
单元格中导出多个值
默认情况下,如果列中有多个值,则它们将以逗号分隔。你可以使用 listAsJson()
方法将它们列为 JSON 数组:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('tags') ->listAsJson()
在关联中展示数据
你可以使用"点语法"访问关联中的列。首先是关联名,然后是点号,最后是要显示的字段名:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('author.name')
计数关联
如果你想计算列中关联记录的数量,可以使用 counts()
方法:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('users_count')->counts('users')
本例中,users
是要计数的关联名。列名必须是 users_count
,因为这是 Laravel 用来存储计数结果的命名规范。
如果你想在计算之前设置关联的查询范围,你可以传递数组到该方法中,其中键为关联名、而值是用来设置 Eloquent 查询范围的函数:
use Filament\Actions\Exports\ExportColumn;use Illuminate\Database\Eloquent\Builder; ExportColumn::make('users_count')->counts([ 'users' => fn (Builder $query) => $query->where('is_active', true),])
判断关联是否存在
如果你想简单地说明关联记录是否存在于列字段中,你可以使用 exists()
方法:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('users_exists')->exists('users')
本例中,users
是用于检测是否存在的关联名。列名必须是 users_exists
、avg()
、max()
、min()
和 sum()
。
如果你想在计算之前设置关联的查询范围,你可以传递数组到该方法中,其中键为关联名、而值是用来设置 Eloquent 查询范围的函数:
use Filament\Actions\Exports\ExportColumn;use Illuminate\Database\Eloquent\Builder; ExportColumn::make('users_exists')->exists([ 'users' => fn (Builder $query) => $query->where('is_active', true),])
聚合关联
Filament 提供多种方法用于聚合关联字段,包括 avg()
、max()
、min()
和 sum()
。比如,你想在列中显示所有关联记录的字段平均值,可以使用 avg()
方法:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('users_avg_age')->avg('users', 'age')
本例中,users
是关联名,而 age
是需要被平均的字段。该列的名称必须为 users_avg_age
,因为这是 Laravel 用来存该数结果的命名规范。
如果你想在计算之前设置关联的查询范围,你可以传递数组到该方法中,其中键为关联名、而值是用来设置 Eloquent 查询范围的函数:
use Filament\Actions\Exports\ExportColumn;use Illuminate\Database\Eloquent\Builder; ExportColumn::make('users_avg_age')->avg([ 'users' => fn (Builder $query) => $query->where('is_active', true),], 'age')
配置导出格式
默认情况下,导出操作(Action)将允许用户在 CSV 和 XLSX 格式之间进行选择。通过传递格式数组到 Action 的 formats()
方法,你可以使用 ExportFormat
枚举对此进行自定义:
use App\Filament\Exports\ProductExporter;use Filament\Actions\Exports\Enums\ExportFormat; ExportAction::make() ->exporter(ProductExporter::class) ->formats([ ExportFormat::Csv, ]) // or ->formats([ ExportFormat::Xlsx, ]) // or ->formats([ ExportFormat::Xlsx, ExportFormat::Csv, ])
此外,你可以重写 Exporter 类的 getFormats()
方法,为使用该 Exporter 的所有 Action 设置默认格式:
use Filament\Actions\Exports\Enums\ExportFormat; public function getFormats(): array{ return [ ExportFormat::Csv, ];}
修改导出查询
默认情况下,如果你在表格上使用 ExportAction
,该 Action 会使用表格当前过滤和排序的查询来导出数据。如果没有表格,它会使用模型默认的查询。要在导出前修改该查询构造器,你可以在该 Action 上使用 modifyQueryUsing()
方法:
use App\Filament\Exports\ProductExporter;use Illuminate\Database\Eloquent\Builder; ExportAction::make() ->exporter(ProductExporter::class) ->modifyQueryUsing(fn (Builder $query) => $query->where('is_active', true))
你也可以将 $options
参数注入到函数中,它是该导出的选项数组:
use App\Filament\Exports\ProductExporter;use Illuminate\Database\Eloquent\Builder; ExportAction::make() ->exporter(ProductExporter::class) ->modifyQueryUsing(fn (Builder $query, array $options) => $query->where('is_active', $options['isActive'] ?? true))
此外,你可以重写 Exporter 类的 modifyQuery()
方法,该方法将修改使用该 Exporter 的所有 Action 的查询构造器:
use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Relations\MorphTo; public static function modifyQuery(Builder $query): Builder{ return $query->with([ 'purchasable' => fn (MorphTo $morphTo) => $morphTo->morphWith([ ProductPurchase::class => ['product'], ServicePurchase::class => ['service'], Subscription::class => ['plan'], ]), ]);}
配置导出文件系统
自定义存储磁盘
默认情况下,导出文件将会被上传到配置文件定义的存储磁盘中。默认为 public
。你也可以设置 FILAMENT_FILESYSTEM_DISK
环境变量对此进行修改。
虽然使用 public
磁盘是 Filament 许多部分的良好默认设置,但将其用于导出会导致导出的文件存储在公共位置。因此,如果默认的文件系统磁盘是 public
,并且你的 config/filesystems.php
文件中存在 local
磁盘,Filament 将使用 local
盘进行导出。如果你为 ExportAction
或在 Exporter 类中将磁盘重写为 public
,Filament 将使用该磁盘。
在生产中,你应该使用具有私有访问策略的磁盘,如 s3
,以防止未经授权访问导出文件。
如果你想为指定导出使用不同磁盘,你可以传递磁盘名称到该 Action 的 disk()
方法中:
ExportAction::make() ->exporter(ProductExporter::class) ->fileDisk('s3')
你可以在服务提供者(如 AppServiceProvider
)的 boot()
方法中一次性为私有导出 Action 设置磁盘:
use Filament\Actions\ExportAction; ExportAction::configureUsing(fn (ExportAction $action) => $action->fileDisk('s3'));
此外,你也可以重写导出类的 getFileDisk()
方法,使之返回磁盘名:
public function getFileDisk(): string{ return 's3';}
如果开发者愿意,他们有责任删除创建的导出文件。Filament 不会删除这些文件,以防之后需要再次下载导出。
配置导出文件名
默认情况下,导出文件会有一个基于 ID 及导出类型生成的名称。你也可以在该 Action 中使用 fileName()
方法自定义文件名:
use Filament\Actions\Exports\Models\Export; ExportAction::make() ->exporter(ProductExporter::class) ->fileName(fn (Export $export): string => "products-{$export->getKey()}.csv")
此外,你也可以在导出类中重写 getFileName()
方法,使之返回一个字符串:
use Filament\Actions\Exports\Models\Export; public function getFileName(Export $export): string{ return "products-{$export->getKey()}.csv";}
导出选项
导出操作可以渲染额外的表单组件,用户可以在导出 CSV 时与之交互。这对于允许用户自定义导出器(Exporter)的行为非常有用。例如,你可能希望用户能够在导出时选择特定列的格式。为此,你可以在 Exporter 类的 getOptionsFormComponents()
方法中返回选项的表单组件:
use Filament\Forms\Components\TextInput; public static function getOptionsFormComponents(): array{ return [ TextInput::make('descriptionLimit') ->label('Limit the length of the description column content') ->integer(), ];}
此外,你可以通过 Action 的 options()
将一组静态选项传递给该导出器(Exporter):
ExportAction::make() ->exporter(ProductExporter::class) ->options([ 'descriptionLimit' => 250, ])
现在,你可以通过将 $options
参数注入到任何闭包函数中,在导出器(Exporter)类内中访问这些选项数据。例如,你可能希望在formatStateUsing()
中使用它来[格式化列的值](#格式化导出列的值):
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('description') ->formatStateUsing(function (string $state, array $options): string { return (string) str($state)->limit($options['descriptionLimit'] ?? 100); })
此外,因为 $options
参数传递给所有闭包函数,你可以在 limit()
中访问它:
use Filament\Actions\Exports\ExportColumn; ExportColumn::make('description') ->limit(fn (array $options): int => $options['descriptionLimit'] ?? 100)
使用自定义用户模式
默认情况下,exports
表格有一个 user_id
字段。该字段被约束到 users
表:
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
在 Export
模型上,user()
关联被定义为 App\Models\User
模型的 BelongsTo
关联。如果 App\Models\User
模型不存在,或者你想使用其他模型,你可以在服务提供者的 register()
方法中将新的 Authenticatable
模型绑定到容器:
use App\Models\Admin;use Illuminate\Contracts\Auth\Authenticatable; $this->app->bind(Authenticatable::class, Admin::class);
如果 Authenticatable 模式使用不是 users
表,你应该将表名传递给 constrained()
:
$table->foreignId('user_id')->constrained('admins')->cascadeOnDelete();
使用多态用户关联
如果你想将导出关联到多个用户模型,可以使用多态的 MorphTo
关联。为此,你需要替换掉在 exports
表格的 user_id
列:
$table->morphs('user');
然后,在服务提供者的 boot()
方法中,你应该调用 Export::polymorphicUserRelationship()
,将 Export
模型的 user()
关联切换到 MorphTo
关联:
use Filament\Actions\Exports\Models\Export; Export::polymorphicUserRelationship();
限制可导出的最大行数
为防止服务器过载,你可能希望限制可以导出到 CSV 文件的最大行数。你可以在 Action 中调用 maxRows()
方法实现此功能:
ExportAction::make() ->exporter(ProductExporter::class) ->maxRows(100000)
修改导出块大小
Filament 会对 CSV 进行分块,并在不同的队列作业中处理每个分块。默认情况下,块一次是 100 行。你可以通过对 Action 调用 chunkSize()
方法来对此进行修改:
ExportAction::make() ->exporter(ProductExporter::class) ->chunkSize(250)
如果导出大的 CSV 文件时,你遇到内存问题或者超时问题,你可能需要减少块的大小。
修改 CSV 分隔符
CSV 的默认分隔符是英文逗号(,
)。如果你想使用不同分隔符导出,你可以重写导出器类上 getCsvDelimiter()
方法,使之返回新的分隔符:
public static function getCsvDelimiter(): string{ return ';';}
你只能指定单个字符,否则会抛出异常。
XLSX 单元格样式
如果你想要指定 XLSX 文件的样式,你可以重写导出器类上 getXlsxCellStyle()
方法,使之返回一个 OpenSpout Style
对象:
use OpenSpout\Common\Entity\Style\Style; public function getXlsxCellStyle(): ?Style{ return (new Style()) ->setFontSize(12) ->setFontName('Consolas');}
如果你只想为 XLSX 文件的标题单元格使用不同的样式,你可以重写导出器类上 getXlsxHeaderCellStyle()
方法,使之返回一个 OpenSpout Style
对象:
use OpenSpout\Common\Entity\Style\CellAlignment;use OpenSpout\Common\Entity\Style\CellVerticalAlignment;use OpenSpout\Common\Entity\Style\Color;use OpenSpout\Common\Entity\Style\Style; public function getXlsxHeaderCellStyle(): ?Style{ return (new Style()) ->setFontBold() ->setFontItalic() ->setFontSize(14) ->setFontName('Consolas') ->setFontColor(Color::rgb(255, 255, 77)) ->setBackgroundColor(Color::rgb(0, 0, 0)) ->setCellAlignment(CellAlignment::CENTER) ->setCellVerticalAlignment(CellVerticalAlignment::CENTER);}
自定义导出作业
处理导出的默认作业是 Filament\Actions\Exports\Jobs\PrepareCsvExport
。如果你想扩展该类并重写器方法,你可以在服务提供者的 register()
方法中替换其原始类:
use App\Jobs\PrepareCsvExport;use Filament\Actions\Exports\Jobs\PrepareCsvExport as BasePrepareCsvExport; $this->app->bind(BasePrepareCsvExport::class, PrepareCsvExport::class);
或者,你可以传递新的 Job 类到该 Action 的 job()
方法中,以为指定的导出自定义 Job:
use App\Jobs\PrepareCsvExport; ExportAction::make() ->exporter(ProductExporter::class) ->job(PrepareCsvExport::class)
自定义导出队列及连接
默认情况下,导出系统会使用默认队列和连接。如果你想为某个特定导出器的 Job 自定义队列,你可以在导出器类中重写 getJobQueue()
方法:
public function getJobQueue(): ?string{ return 'exports';}
重写 Exporter 类的 getJobConnection()
方法,你可以自定义用于某个特定导出器的 Job 的连接:
public function getJobConnection(): ?string{ return 'sqs';}
自定义导出 Job 中间件
默认情况下,导出系统每个导出一次只处理一个 Job。这是为了防止服务器过载,以及其他作业因大型导出而延迟。该功能在导出器类的 WithoutOverlapping
中间件中定义:
public function getJobMiddleware(): array{ return [ (new WithoutOverlapping("export{$this->export->getKey()}"))->expireAfter(600), ];}
如果你想自定义应用于某个导出器的作业的中间件,你可以在导出器类中重写此方法。你可以在 Laravel 文档中阅读更多关于作业中间件的信息。
自定义导出作业重试次数
默认情况下,导出系统将在 24 小时内重试作业。这是为了解决临时问题,例如数据库不可用。该功能在导出器类的 getJobRetryUntil()
方法中定义:
use Carbon\CarbonInterface; public function getJobRetryUntil(): ?CarbonInterface{ return now()->addDay();}
如果你想自定义特定导出器的作业重试次数,可以重写导出器的这一方法。更多作业重试信息,可以查阅 Laravel 文档。
自定义导出作业标签
默认情况下,导出系统将使用导出的 ID 标记每个作业。这是为了让你能够轻松地找到与某个导出相关的所有作业。该功能在导出器类的 getJobTags()
方法中定义:
public function getJobTags(): array{ return ["export{$this->export->getKey()}"];}
如果要自定义应用于某个导出器的作业的标签,可以在导出器类中重写此方法。
自定义导出作业的批次名称
默认情况下,导出系统不会定义作业批次的名称。如果你想为特定导出器(exporter)的作业批次名称,可以在导出器(Exporter)类中重写 getJobBatchName()
方法:
public function getJobBatchName(): ?string{ return 'product-export';}
授权
默认情况下,只有开启导出的用户才能下载生成的文件。如果你像自定义授权逻辑,可以创建 ExportPolicy
类,并且在 AuthServiceProvider
中注册它:
use App\Policies\ExportPolicy;use Filament\Actions\Exports\Models\Export; protected $policies = [ Export::class => ExportPolicy::class,];
该策略的 view()
用于授权下载权限。
请注意,如果你定义了策略,现有的逻辑(即只有开启导出的用户可以访问生成的文件)将被移除。如果你要保留该逻辑,你需要将其添加到策略中:
use App\Models\User;use Filament\Actions\Exports\Models\Export; public function view(User $user, Export $export): bool{ return $export->user()->is($user);}
Edit on GitHubStill need help? Join our Discord community or open a GitHub discussion