跳到主要内容
版本:3.x

Export action

概述

Filament v3.2 引入了一个预构建的 Action,可以将行导出到 CSV 或 XLSX 文件。当单击触发器按钮时,模态框会询问要导出的列以及它们应该的标签。此功能使用作业批次数据库迁移,因此你需要从 Laravel 发布这些迁移。此外,你还需要发布 Filament 用于存储导出信息的表的迁移:

php artisan queue:batches-table
php artisan notifications:table
php 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 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_existsavg(), max(), min() and 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))

此外,你可以重写 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'],
]),
]);
}

配置导出文件系统

自定义存储磁盘

默认情况下,导出文件将会被上传到配置文件定义的存储磁盘中。你也可以设置 FILAMENT_FILESYSTEM_DISK 环境变量对此进行修改。

如果你想为指定导出使用不同磁盘,你可以传递磁盘名称到该 Action 的 disk() 方法中:

ExportAction::make()
->exporter(ProductExporter::class)
->fileDisk('s3')

此外,你也可以重写导出类的 getFileDisk() 方法,使之返回磁盘名:

public function getFileDisk(): string
{
return 's3';
}

配置导出文件名

默认情况下,导出文件会有一个基于 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);
}