导出操作
简介
Filament 包含一个能够将行导出为 CSV 或 XLSX 文件的操作。点击触发按钮后,模态框会询问用户要导出哪些列,以及它们应如何标记。此功能使用 任务批处理 和 数据库通知,因此你需要发布 Laravel 的这些迁移。此外,还需要发布 Filament 用于存储导出信息的表迁移:
php artisan make:queue-batches-table
php artisan make:notifications-table
php artisan vendor:publish --tag=filament-actions-migrations
php artisan migrate若希望在面板中接收导出通知,可以在 面板配置 中启用。
INFO
若使用 PostgreSQL,请确保通知迁移中的 data 列使用 json():$table->json('data')。
INFO
若 User 模型使用 UUID,请确保通知迁移中的 notifiable 列使用 uuidMorphs():$table->uuidMorphs('notifiable')。
你可以这样使用 ExportAction:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)

若要将其添加到表格页眉,可以这样做:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->headerActions([
ExportAction::make()
->exporter(ProductExporter::class),
]);
}或者,若要将其添加为表格批量操作,以便用户选择要导出的行,可以使用 Filament\Actions\ExportBulkAction:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportBulkAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->toolbarActions([
ExportBulkAction::make()
->exporter(ProductExporter::class),
]);
}需要 创建「exporter」类 来告诉 Filament 如何导出每一行。
创建 exporter
若要为模型创建 exporter 类,可使用 make:filament-exporter 命令并传入模型名称:
php artisan make:filament-exporter Product这会在 app/Filament/Exports 目录中创建新类。接下来需要定义可导出的 列。
自动生成 exporter 列
若想节省时间,Filament 可以根据模型的数据库列,使用 --generate 自动生成 列:
php artisan make:filament-exporter Product --generate定义 exporter 列
若要定义可导出的列,需要在 exporter 类上覆盖 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)你可以在 ExportAction 上使用 enableVisibleTableColumnsByDefault() 方法,默认仅启用表格中当前可见的列。使用 enabledByDefault(false) 的列默认也会被禁用:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->enableVisibleTableColumnsByDefault()隐藏导出列
你可以使用 hidden() 或 visible() 方法完全隐藏某列。隐藏的列不会出现在列选择表单中,也不会写入导出文件:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('sku')
->hidden()
ExportColumn::make('sku')
->visible()若要有条件地隐藏列,可以向任一方法传入布尔值:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('cost_price')
->hidden(fn (): bool => ! auth()->user()->isAdmin())
ExportColumn::make('cost_price')
->visible(fn (): bool => auth()->user()->isAdmin())INFO
与表格列不同,导出列在解析时没有记录,因此 hidden() 或 visible() 闭包不能依赖行数据。请将其用于 schema 级别的条件,例如已认证用户、功能开关或配置。
若要让列保持可选但默认不勾选,而不是完全隐藏,请使用 enabledByDefault(false)。
配置列选择表单布局
默认情况下,列选择表单使用单列布局。你可以使用 columnMappingColumns() 方法更改,传入大屏幕上希望使用的列数:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->columnMappingColumns(3)这会以 3 列布局显示列选择复选框和标签输入,在有许多可导出列时更好地利用可用空间。请注意,虽然大屏幕上布局为三列,但布局仍是响应式的,较小屏幕上会显示更少的列。
禁用列选择
默认情况下,会询问用户要导出哪些列。你可以使用 columnMapping(false) 禁用此功能:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
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);
})TIP
除了 $record 之外,state() 函数还可以将各种实用工具作为参数注入。
格式化导出列的值
你也可以向 formatStateUsing() 传入自定义格式化回调,它接受单元格的 $state,以及可选的 Eloquent $record:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('status')
->formatStateUsing(fn (string $state): string => __("statuses.{$state}"))TIP
除了 $state 之外,formatStateUsing() 函数还可以将各种实用工具作为参数注入。
若列中有 多个值,该函数会对每个值调用。
限制文本长度
你可以使用 limit() 限制单元格值的长度:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->limit(50)TIP
除了允许静态值外,limit() 方法也接受一个函数来动态计算值。你可以将各种实用工具作为参数注入该函数。
限制字词数量
你可以限制单元格中显示的 words() 数量:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->words(10)TIP
除了允许静态值外,words() 方法也接受一个函数来动态计算值。你可以将各种实用工具作为参数注入该函数。
添加前缀或后缀
你可以为单元格的值添加 prefix() 或 suffix():
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('domain')
->prefix('https://')
->suffix('.com')TIP
除了允许静态值外,prefix() 和 suffix() 方法也接受函数来动态计算值。你可以将各种实用工具作为参数注入这些函数。
在单元格中导出多个值
默认情况下,若列中有多个值,它们会以逗号分隔。你可以使用 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,这是 Laravel 用于存储结果的约定。
若要在检查存在性前对关联关系进行约束,可以向该方法传入数组,键为关联关系名称,值为用于约束 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')配置导出格式
默认情况下,导出操作会生成 CSV 和 XLSX 两种格式,并允许用户在通知中选择。你可以使用 ExportFormat 枚举自定义,将格式数组传给操作上的 formats() 方法:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
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 的操作设置默认格式:
use Filament\Actions\Exports\Enums\ExportFormat;
public function getFormats(): array
{
return [
ExportFormat::Csv,
];
}自定义导出文件的下载方式
默认情况下,每种导出格式使用各自的 downloader 返回流式响应。你可以通过覆盖 getDownloader() 方法,为 exporter 自定义 downloader:
use App\Filament\Exports\Downloaders\CustomCsvDownloader;
use App\Filament\Exports\Downloaders\CustomXlsxDownloader;
use Filament\Actions\Exports\Downloaders\Contracts\Downloader;
use Filament\Actions\Exports\Enums\Contracts\ExportFormat as ExportFormatInterface;
use Filament\Actions\Exports\Enums\ExportFormat;
public static function getDownloader(ExportFormatInterface $format): Downloader
{
return match ($format) {
ExportFormat::Csv => app(CustomCsvDownloader::class),
ExportFormat::Xlsx => app(CustomXlsxDownloader::class),
default => $format->getDownloader(),
};
}Downloader 是一个可调用类,接受 Export 模型并返回 Symfony Response。该响应可以流式下载、返回文件,或将用户重定向到远程文件系统上的临时 URL。
Filament 的内置下载路由仅解析 ExportFormat::Csv 和 ExportFormat::Xlsx 格式。若使用自定义 ExportFormatInterface 实现,其 getDownloadNotificationAction() 方法必须链接到处理该自定义格式的路由。
若自定义 downloader 仅更改生成内容的交付方式,可以使用 CsvExportContentGenerator 迭代生成的 CSV 分块,或使用 XlsxExportContentGenerator 将生成的行写入 OpenSpout Writer。Filament 从容器解析这两个类,以便你复用内置内容生成而不必重复实现。必须在将 Writer 传给 XlsxExportContentGenerator 之前打开它,之后再关闭。XlsxExportContentGenerator 镜像 Filament 的按需 XLSX 下载,不会应用队列生成 XLSX 文件时使用的 writer 选项、样式、自定义行创建或 writer 生命周期钩子。
修改导出查询
默认情况下,若将 ExportAction 与表格一起使用,该操作会使用表格当前已筛选和排序的查询来导出数据。若没有表格,则使用模型的默认查询。若要在导出前修改查询构建器,可以在操作上使用 modifyQueryUsing() 方法:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
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 的操作修改查询:
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。你可以设置 FILESYSTEM_DISK 环境变量来更改。
虽然 public 磁盘对 Filament 的许多部分来说是不错的默认值,但将其用于导出会导致导出文件存储在公开位置。因此,若默认文件系统磁盘是 public,且 config/filesystems.php 中存在 local 磁盘,Filament 会改用 local 磁盘进行导出。若你在 ExportAction 或 exporter 类中将磁盘覆盖为 public,Filament 会使用该磁盘。
在生产环境中,应使用带有私有访问策略的磁盘(如 s3),以防止未经授权访问导出文件。
若要为特定导出使用不同磁盘,可以将磁盘名称传给操作上的 disk() 方法:
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->fileDisk('s3')你可以在服务提供者(如 AppServiceProvider)的 boot() 方法中一次性为所有导出操作设置磁盘:
use Filament\Actions\ExportAction;
ExportAction::configureUsing(fn (ExportAction $action) => $action->fileDisk('s3'));或者,你可以在 exporter 类上覆盖 getFileDisk() 方法,返回磁盘名称:
public function getFileDisk(): string
{
return 's3';
}创建的导出文件若需删除,由开发者负责。Filament 不会删除这些文件,以防之后需要再次下载导出。
配置导出文件名
默认情况下,导出文件的名称根据导出的 ID 和类型生成。你可以使用操作上的 fileName() 方法自定义文件名:
use Filament\Actions\ExportAction;
use Filament\Actions\Exports\Models\Export;
ExportAction::make()
->exporter(ProductExporter::class)
->fileName(fn (Export $export): string => "products-{$export->getKey()}")或者,你可以在 exporter 类上覆盖 getFileName() 方法并返回自定义字符串:
use Filament\Actions\Exports\Models\Export;
public function getFileName(Export $export): string
{
return "products-{$export->getKey()}";
}使用导出选项
导出操作可以渲染用户在导出 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(),
];
}或者,你可以通过操作上的 options() 方法向 exporter 传入一组静态选项:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->options([
'descriptionLimit' => 250,
])TIP
除了允许静态值外,options() 方法也接受一个函数来动态计算值。你可以将各种实用工具作为参数注入该函数。
现在,你可以在 exporter 类中通过将 $options 参数注入任何闭包函数来访问这些选项的数据。例如,你可能希望在 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);若你的可认证模型使用的不是 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 文件可导出的最大行数。可以在操作上调用 maxRows() 方法:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->maxRows(100000)更改导出分块大小
Filament 会将 CSV 分块,并在不同的队列任务中处理每个分块。默认每次分块为 100 行。你可以在操作上调用 chunkSize() 方法更改:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->chunkSize(250)TIP
除了允许静态值外,chunkSize() 方法也接受一个函数来动态计算值。你可以将各种实用工具作为参数注入该函数。
TIP
若在导入大型 CSV 文件时遇到内存或超时问题,可以考虑减小分块大小。
更改 CSV 分隔符
CSV 的默认分隔符是逗号(,)。若要使用其他分隔符导出,可以在 exporter 类上覆盖 getCsvDelimiter() 方法并返回新分隔符:
public static function getCsvDelimiter(): string
{
return ';';
}TIP
除了允许静态值外,csvDelimiter() 方法也接受一个函数来动态计算值。你可以将各种实用工具作为参数注入该函数。
只能指定单个字符,否则会抛出异常。
自定义 XLSX 文件
为 XLSX 行设置样式
若要为 XLSX 文件的单元格设置样式,可以在 exporter 类上覆盖 getXlsxCellStyle() 方法,返回 OpenSpout Style 对象:
use OpenSpout\Common\Entity\Style\Style;
public function getXlsxCellStyle(): ?Style
{
return (new Style())
->setFontSize(12)
->setFontName('Consolas');
}若要仅为 XLSX 文件的表头单元格使用不同样式,可以在 exporter 类上覆盖 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);
}为 XLSX 列设置样式
exporter 类上的 makeXlsxRow() 和 makeXlsxHeaderRow() 方法允许你自定义行内各个单元格的样式。默认情况下,这些方法的实现如下:
use OpenSpout\Common\Entity\Row;
use OpenSpout\Common\Entity\Style\Style;
/**
* @param array<mixed> $values
*/
public function makeXlsxRow(array $values, ?Style $style = null): Row
{
return Row::fromValues($values, $style);
}用户导出时可以选择要导出哪些列。因此,可以使用 $this->columnMap 属性确定正在导出哪些列以及顺序。你可以将 Row::fromValues() 替换为 Cell 对象数组,使用 OpenSpout Style 对象 单独为它们设置样式。可以使用 StyleMerger 将默认样式与单元格的自定义样式合并,从而在默认样式之上应用额外样式:
use OpenSpout\Common\Entity\Cell;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Common\Entity\Style\Style;
use OpenSpout\Writer\Common\Manager\Style\StyleMerger;
/**
* @param array<mixed> $values
*/
public function makeXlsxRow(array $values, ?Style $style = null): Row
{
$styleMerger = new StyleMerger();
$cells = [];
foreach (array_keys($this->columnMap) as $columnIndex => $column) {
$cells[] = match ($column) {
'name' => Cell::fromValue(
$values[$columnIndex],
$styleMerger->merge(
(new Style())->setFontUnderline(),
$style,
),
),
'price' => Cell::fromValue(
$values[$columnIndex],
(new Style())->setFontSize(12),
),
default => Cell::fromValue($values[$columnIndex]),
},
}
return new Row($cells, $style);
}自定义 XLSX writer
若要将「选项」传给 OpenSpout XLSX Writer,可以从 exporter 类的 getXlsxWriterOptions() 方法返回 OpenSpout\Writer\XLSX\Options 实例:
use OpenSpout\Writer\XLSX\Options;
public function getXlsxWriterOptions(): ?Options
{
$options = new Options();
$options->setColumnWidth(10, 1);
$options->setColumnWidthForRange(12, 2, 3);
return $options;
}若要在 XLSX writer 打开后、写入任何行之前立即自定义它,可以在 exporter 类上覆盖 configureXlsxWriterAfterOpen() 方法。该方法接收 Writer 实例作为参数,你可以在写入表头和数据行之前修改它。这对于在导出表格上方添加自定义行(如标题或副标题)很有用:
use OpenSpout\Common\Entity\Row;
use OpenSpout\Common\Entity\Style\Style;
use OpenSpout\Writer\XLSX\Writer;
public function configureXlsxWriterAfterOpen(Writer $writer): Writer
{
$writer->addRow(Row::fromValues(
['This is a custom header added after opening the XLSX writer.'],
(new Style())->setShouldWrapText(false),
));
return $writer;
}WARNING
此处添加的任何行都会出现在表头行上方,使导出表格下移。若还使用 configureXlsxWriterBeforeClose() 冻结行,请记得在 setFreezeRow() 中计入这些额外行。
若要在关闭 XLSX writer 之前自定义它,可以在 exporter 类上覆盖 configureXlsxWriterBeforeClose() 方法。该方法接收 Writer 实例作为参数,你可以在关闭前修改它:
use OpenSpout\Writer\XLSX\Entity\SheetView;
use OpenSpout\Writer\XLSX\Writer;
public function configureXlsxWriterBeforeClose(Writer $writer): Writer
{
$sheetView = new SheetView();
$sheetView->setFreezeRow(2);
$sheetView->setFreezeColumn('B');
$sheet = $writer->getCurrentSheet();
$sheet->setSheetView($sheetView);
$sheet->setName('export');
return $writer;
}自定义完成通知
导出完成后,Filament 会向启动导出的用户发送通知。你可以通过在 exporter 上覆盖 getCompletedNotificationTitle() 和 getCompletedNotificationBody() 来自定义通知的标题和正文:
use Filament\Actions\Exports\Models\Export;
public static function getCompletedNotificationTitle(Export $export): string
{
return 'Your product export is ready';
}
public static function getCompletedNotificationBody(Export $export): string
{
return $export->successful_rows . ' products were exported.';
}若要自定义标题和正文以外的内容——例如更改通知颜色、添加额外操作或替换图标——请覆盖 modifyCompletedNotification()。你可以修改传入的 Notification 并返回它,也可以构建并返回一个全新的通知:
use Filament\Actions\Action;
use Filament\Actions\Exports\Models\Export;
use Filament\Notifications\Notification;
public static function modifyCompletedNotification(Notification $notification, Export $export): Notification
{
$notification->icon('heroicon-o-shopping-bag');
if ($export->getOptions()['notifyTeam'] ?? false) {
$notification->actions([
...$notification->getActions(),
Action::make('shareWithTeam')
->url(route('exports.share', $export)),
]);
}
return $notification;
}Export 模型通过 $export->getColumnMap() 和 $export->getOptions() 暴露用户选择的列映射和选项,因此你可以根据用户导出的内容定制通知。
自定义导出任务
处理导出的默认任务是 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() 方法,以自定义特定导出的任务:
use App\Filament\Exports\ProductExporter;
use App\Jobs\PrepareCsvExport;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->job(PrepareCsvExport::class)自定义导出队列与连接
默认情况下,导出系统使用默认队列和连接。若要自定义某个 exporter 的任务所用队列,可以在 exporter 类中覆盖 getJobQueue() 方法:
public function getJobQueue(): ?string
{
return 'exports';
}你也可以通过在 exporter 类中覆盖 getJobConnection() 方法,自定义某个 exporter 的任务所用连接:
public function getJobConnection(): ?string
{
return 'sqs';
}自定义导出任务中间件
默认情况下,导出系统对每次导出一次只处理一个任务。这是为了防止服务器过载,以及避免其他任务被大型导出拖慢。该功能在 exporter 类上的 WithoutOverlapping 中间件中定义:
public function getJobMiddleware(): array
{
return [
(new WithoutOverlapping("export{$this->export->getKey()}"))->expireAfter(600),
];
}若要自定义某个 exporter 任务所应用的中间件,可以在 exporter 类中覆盖此方法。关于任务中间件的更多信息,请参阅 Laravel 文档。
自定义导出任务重试
默认情况下,导出系统会在 24 小时内重试任务,或直到因 5 次未处理异常失败(以先发生者为准)。这是为了让数据库不可用等临时问题有时间解决。你可以更改任务重试的时间段,该时间段在 exporter 类的 getJobRetryUntil() 方法中定义:
use Carbon\CarbonInterface;
public function getJobRetryUntil(): ?CarbonInterface
{
return now()->addHours(12);
}关于任务重试的更多信息,请参阅 Laravel 文档。
自定义导出任务退避策略
默认情况下,导出系统会在重试任务前等待 1 分钟、然后 2 分钟、然后 5 分钟、之后每次 10 分钟。这是为了防止反复失败的任务过载服务器。该功能在 exporter 类的 getJobBackoff() 方法中定义:
/**
* @return int | array<int> | null
*/
public function getJobBackoff(): int | array | null
{
return [60, 120, 300, 600];
}关于任务退避(包括如何配置指数退避)的更多信息,请参阅 Laravel 文档。
自定义导出任务标签
默认情况下,导出系统会用导出 ID 标记每个任务。这便于你轻松找到与某次导出相关的所有任务。该功能在 exporter 类的 getJobTags() 方法中定义:
public function getJobTags(): array
{
return ["export{$this->export->getKey()}"];
}若要自定义某个 exporter 任务所应用的标签,可以在 exporter 类中覆盖此方法。
自定义导出任务批处理名称
默认情况下,导出系统不会为任务批处理定义任何名称。若要自定义某个 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);
}安全性
按记录授权
导出系统不会执行按记录的授权检查。触发导出时,匹配表格查询的所有记录(若在表格外使用,则为模型的完整数据集)都会包含在导出中,而不会咨询应用的 Laravel 策略。这意味着若用户被允许触发导出,他们可能会收到通常无权通过应用 UI 查看的记录。
若需要限制导出哪些记录,应使用 modifyQueryUsing() 方法 约束查询:
use Illuminate\Database\Eloquent\Builder;
ExportAction::make()
->exporter(ProductExporter::class)
->modifyQueryUsing(fn (Builder $query) => $query->whereBelongsTo(auth()->user()))你也可以对模型应用 全局作用域,确保只查询已授权的记录。
DANGER
若应用有按记录的可见性规则,应约束导出查询,确保用户只收到他们有权查看的记录。
CSV 公式注入
Filament 的导出系统会将数据原样写入 CSV 和 XLSX 文件,与数据库中存储的完全一致,不做任何转换。这意味着若数据库包含以 =、+、- 或 @ 等字符开头的值,它们会原样出现在导出文件中。在 Microsoft Excel 或 Google Sheets 等电子表格软件中打开时,这些值可能被解释为公式;若数据包含不受信任或用户提交的内容,这可能构成安全风险。你应确保用户了解此风险,或在导出前使用每列上的 formatStateUsing() 方法 清理数据,例如在值前加上单引号(')以防止公式解释。
或者,你可以选择启用 Filament 的内置保护。在列上启用后,任何以公式触发字符(=、+、-、@、制表符或回车符)开头的值都会自动加上单引号(')前缀,使电子表格软件将其视为纯文本。使用列上的 preventFormulaInjection() 方法启用:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->preventFormulaInjection()若要为应用中的每个导出列启用此保护,可以在服务提供者的 boot() 方法中使用 configureUsing() 方法。由于这会应用于所有列,你可以通过向 preventFormulaInjection() 传入 false 让单个列退出:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::configureUsing(function (ExportColumn $column): void {
$column->preventFormulaInjection();
});
// Opt a specific column back out:
ExportColumn::make('temperature')
->preventFormulaInjection(false)WARNING
此保护为 选择性启用,默认关闭,因为加上单引号前缀会改变合法数据。例如 -5 或电话号码 +44 1234 567890 都是有效的公式触发值,会被改写为 '-5 和 '+44 1234 567890。仅在导出不受信任或用户提交的内容时启用,并确保该转换对你启用它的列是可接受的。