Filament 资源页 Widget 集成实战:在 Resource 页面中展示与联动仪表盘组件
2026/9/11 13:25:09 网站建设 项目流程

Filament 资源页 Widget 集成实战:在 Resource 页面中展示与联动仪表盘组件

【免费下载链接】filamentA powerful open-source UI framework for Laravel • Build and ship apps & admin panels fast with Livewire项目地址: https://gitcode.com/GitHub_Trending/fi/filament

本篇技术指南围绕 Filament 面板框架的资源(Resource)页面 Widget 能力展开:你将学会如何通过make:filament-widget命令为某个资源创建专属 Widget,如何把已有的仪表盘 Widget 挂载到 List / Edit / View 等资源页面的头部与底部,并借助ExposesTableToWidgetsInteractsWithPageTable两个核心 Trait 让 Widget 与页面的表格、当前记录深度联动。读完即可在实战项目中构建“统计卡片 + 表格”的资源详情页与列表页。

一、在资源页面中使用 Widget:基本思路

Filament 允许你在页面中展示 Widget,位置在页面的头部(header)与底部(footer)之间:

  • header widgets:渲染在页面内容上方,适合放统计概览、图表等“概览型”信息;
  • footer widgets:渲染在页面内容下方,适合放补充说明、关联数据等。

你可以直接复用已有的仪表盘 Widget(如StatsOverviewWidgetChartWidgetTableWidget),也可以为某个资源单独创建一个专属 Widget。从源码看,页面渲染 Widget 的逻辑集中在 Page.php 中:headerWidgets()/footerWidgets()会分别用一个Grid布局容器把 Widget 包起来,并在网格前后注入PanelsRenderHook::PAGE_HEADER_WIDGETS_START / ENDPAGE_FOOTER_WIDGETS_START / END渲染钩子,方便你进一步定制。

二、创建资源专属 Widget

为某个资源创建 Widget,使用 Filament 提供的脚手架命令:

php artisan make:filament-widget CustomerOverview --resource=CustomerResource

执行后会生成两个文件:

  • Widget 类:位于app/Filament/Resources/Customers/Widgets目录;
  • Blade 视图:位于resources/views/filament/resources/customers/widgets目录。

从命令实现 MakeWidgetCommand.php 可以看到,该命令的完整签名是make:filament-widget,并带有两个别名filament:make-widgetfilament:widget。除--resource(别名-R)外,它还支持以下常用选项:

选项别名说明
--resource-R指定所属资源,Widget 会生成到该资源目录下的Widgets子目录
--chart-C生成图表 Widget(Chart Widget)
--stats-overview-S生成统计概览 Widget(Stats Overview Widget)
--table-T生成表格 Widget(Table Widget)
--cluster指定资源所属的 Cluster(仅资源 Widget 有效)
--panel指定 Widget 所在的 Panel
--resource-namespace资源类的命名空间,默认如App\Filament\Resources
--force-F若文件已存在则覆盖写入

不指定--chart/--stats-overview/--table时,命令会生成一个继承自Filament\Widgets\Widget的自定义 Widget(configureType()的默认分支,见 MakeWidgetCommand.php)。

生成后,你必须在新 Widget 的getWidgets()方法中注册它,才能被资源感知到:

use App\Filament\Resources\Customers\Widgets\CustomerOverview; public static function getWidgets(): array { return [ CustomerOverview::class, ]; }

getWidgets()的默认实现为空数组(见 Resource.php),返回类型为array<class-string<Widget>>,即资源级注册的 Widget 类名列表。命令执行成功后,终端也会提示你“请记得在getWidgets()中注册 Widget,并将其添加到资源页面中”。

关于 Widget 本身的构建与定制(自定义视图、懒加载、列跨度等),请参阅仪表盘 Widget 文档。需要说明的是,Widget 本质上是一个独立的 Livewire 组件——基类 Widget.php 直接继承Livewire\Component,并内置了CanAuthorizeAccess(通过canView()控制可见性)与CanBeLazy(懒加载)两个能力。

三、在资源页面上展示 Widget

资源页面(如ListCustomers)继承自ListRecords等内置页面类。要在页面上展示 Widget,重写页面的getHeaderWidgets()getFooterWidgets()方法:

<?php namespace App\Filament\Resources\Customers\Pages; use App\Filament\Resources\Customers\CustomerResource; class ListCustomers extends ListRecords { public static string $resource = CustomerResource::class; protected function getHeaderWidgets(): array { return [ CustomerResource\Widgets\CustomerOverview::class, ]; } }

其中:

  • getHeaderWidgets():返回要在页面内容上方展示的 Widget 数组;
  • getFooterWidgets():返回要在页面内容下方展示的 Widget 数组。

两个方法的默认实现均为空数组(见 Page.php 与 Page.php),返回元素可以是 Widget 类名,也可以是WidgetConfiguration实例(见下文“向 Widget 传递属性”)。

自定义 Widget 网格列数

Widget 默认在每行 2 列的网格中排列(getHeaderWidgetsColumns()getFooterWidgetsColumns()默认都返回2,见 Page.php 与 Page.php)。你可以通过覆写这两个方法来调整列数:

public function getHeaderWidgetsColumns(): int | array { return 3; }

甚至可以根据响应式断点返回数组,让不同屏幕宽度使用不同的列数:

public function getHeaderWidgetsColumns(): int | array { return [ 'md' => 4, 'xl' => 5, ]; }

这与自定义页面文档中介绍的机制完全一致,可以配合响应式 Widget 宽度(基于columnSpan/columnStart属性)实现精细的栅格布局。

页面如何把 Widget 渲染出去

从底层实现看,页面并非直接在 Blade 中硬编码 Widget,而是通过getWidgetsSchemaComponents()把 Widget 数组转换为 Schema 组件(见 Page.php)。该过程中有两个值得注意的细节:

  1. 可见性过滤:每个 Widget 都会经过canView()检查,不可见的 Widget 会被自动过滤掉,无需手动处理权限逻辑;
  2. 数据注入:每个 Widget 都会收到页面的getWidgetData()数据、make()传入的属性以及getDefaultProperties()(如懒加载标志)的合并结果,并通过Livewire::make(...)以唯一 key 挂载。

这也解释了为什么在资源页面上注册的 Widget 能自动感知页面状态——数据链路在渲染阶段就已打通。

四、在 Widget 中访问当前记录(Edit / View 页面)

如果你的 Widget 被用在资源的Edit(编辑)或View(查看)页面上,并且需要访问当前正在编辑/查看的记录,只需在 Widget 类中声明一个$record公共属性:

use Illuminate\Database\Eloquent\Model; public ?Model $record = null;

页面框架会自动把当前记录注入到这个属性中。之后你就可以在 Widget 的getStats()getData()或 Blade 视图中通过$this->record读取该 Eloquent 模型,例如展示订单金额、用户等级等与该记录强相关的统计信息。相关页面行为可参考编辑记录与查看记录文档。

五、在 Widget 中访问页面表格数据(List 页面)

如果你想把 Widget 用在资源的List(列表)页面上,并希望 Widget 直接读取表格的查询结果(如“当前筛选条件下的记录数”“当前页记录集合”),需要分别在页面与 Widget 两侧各加一个 Trait。

第一步:页面侧暴露表格数据

在 List 页面类上引入ExposesTableToWidgetsTrait:

use Filament\Pages\Concerns\ExposesTableToWidgets; use Filament\Resources\Pages\ListRecords; class ListProducts extends ListRecords { use ExposesTableToWidgets; // ... }

该 Trait 的实现见 ExposesTableToWidgets.php,它通过getWidgetData()方法把页面表格的完整“活状态”打包成数组:

  • activeTab:当前激活的表格 Tab;
  • paginators:分页状态;
  • parentRecord:父级记录(用于嵌套资源);
  • tableColumnSearches:列搜索关键词;
  • tableFilters:当前应用的表格筛选器;
  • tableGrouping:当前分组方式;
  • tableRecordsCount全部记录的计数(来自getAllTableRecordsCount());
  • tableRecordsPerPage:每页记录数;
  • tableSearch:全局搜索关键词;
  • tableSort:当前排序字段。

也就是说,页面的搜索、筛选、分页、排序等状态都会随 Widget 渲染一并传递给挂载的 Widget。

第二步:Widget 侧接入页面表格

在 Widget 类上引入InteractsWithPageTableTrait,并覆写getTablePage()返回页面类名:

use App\Filament\Resources\Products\Pages\ListProducts; use Filament\Widgets\Concerns\InteractsWithPageTable; use Filament\Widgets\Widget; class ProductStats extends Widget { use InteractsWithPageTable; protected function getTablePage(): string { return ListProducts::class; } // ... }

该 Trait 的实现见 InteractsWithPageTable.php,其工作方式如下:

  1. 声明与页面同步的#[Reactive]公共属性paginatorstableRecordsCounttableColumnSearchestableGroupingtableFilterstableRecordsPerPagetableSearchtableSortactiveTabparentRecord等。借助 Livewire 的响应式属性机制,当用户在页面上切换筛选、翻页、搜索时,Widget 会自动收到最新状态并重新计算——这是“Widget 与表格保持同步”的关键所在。
  2. getTablePageInstance():按需实例化目标页面组件并触发其mount,把上述状态回填进去,再调用bootedInteractsWithTable(),从而获得一个与真实页面等价的表格实例(结果会缓存复用)。
  3. getPageTableQuery():返回getFilteredSortedTableQuery()的结果,即经过筛选与排序后的 Eloquent 查询构造器
  4. getPageTableRecords():返回getTableRecords()的结果,即当前页的记录集合(分页后)。

如果在 Widget 中未定义getTablePage(),Trait 会抛出LogicException,提示你必须返回一个 Livewire 组件的类名(见 InteractsWithPageTable.php)。

使用表格查询

在 Widget 中,用getPageTableQuery()拿到查询构造器后,即可基于当前筛选条件做任意聚合统计:

use Filament\Widgets\StatsOverviewWidget\Stat; Stat::make('Total Products', $this->getPageTableQuery()->count()),

这样做的好处是:统计值会跟随用户在表格上的筛选、搜索、排序实时变化,例如“筛选出价格为空的商品后,总数卡片立即更新”。

使用当前页记录

如果你只想统计当前页(而非全部筛选结果)的记录,可以使用getPageTableRecords()

use Filament\Widgets\StatsOverviewWidget\Stat; Stat::make('Total Products', $this->getPageTableRecords()->count()),

该方法返回Collection | Paginator——当表格启用分页时返回分页器,否则返回 Eloquent 集合,可直接遍历或count()

六、直接获取表格总记录数

如果你需要的是“该表格查询的全部记录总数”,但又不想额外执行一次 count 查询,可以直接使用 Trait 提供的$tableRecordsCount属性:

use Filament\Widgets\StatsOverviewWidget\Stat; Stat::make('Total Products', $this->tableRecordsCount),

该属性在 InteractsWithPageTable.php 中被声明为#[Reactive] public ?int $tableRecordsCount = null;,其数据来源是页面侧ExposesTableToWidgets::getWidgetData()中的getAllTableRecordsCount()——即忽略分页、只考虑搜索与筛选的全量计数,且复用页面已有的查询结果,不会产生重复查询开销。

七、向资源页面上的 Widget 传递属性

在页面上注册 Widget 时,可以通过make()方法向它传递一组 Livewire 属性:

protected function getHeaderWidgets(): array { return [ CustomerResource\Widgets\CustomerOverview::make([ 'status' => 'active', ]), ]; }

这组属性会被映射为 Widget 类上的公共 Livewire 属性

use Filament\Widgets\Widget; class CustomerOverview extends Widget { public string $status; // ... }

之后你就可以在 Widget 类中使用$this->status读取该值,实现“同一个 Widget 在不同页面以不同参数复用”的效果。

从源码看,make()的签名是Widget::make(array $properties = []),返回的是一个WidgetConfiguration实例(见 Widget.php 与 WidgetConfiguration.php),内部仅保存 Widget 类名与属性数组。页面在渲染时会通过getWidgetsSchemaComponents()WidgetConfiguration中的属性(合并getDefaultProperties()后)注入 Livewire 组件(见 Page.php)。

页面级数据透传:getWidgetData()

除了用make()按 Widget 传参,页面还支持通过getWidgetData()所有挂载的 Widget 批量透传数据(机制见自定义页面文档):

public function getWidgetData(): array { return [ 'stats' => [ 'total' => 100, ], ]; }

对应的 Widget 上声明同名公共属性即可自动接收:

public $stats = [];

注意,资源页面默认的getWidgetData()返回空数组(见 Page.php),而ExposesTableToWidgets会覆盖它来提供表格状态;这两种机制可以共存,它们最终会与make()属性合并后一起注入 Widget。

八、完整示例:一个联动表格的统计头部

把以上能力组合起来,一个典型的“列表页 + 统计卡片头部”完整实现如下。

Widget 类app/Filament/Resources/Products/Widgets/ProductStats.php):

<?php namespace App\Filament\Resources\Products\Widgets; use App\Filament\Resources\Products\Pages\ListProducts; use Filament\Widgets\Concerns\InteractsWithPageTable; use Filament\Widgets\StatsOverviewWidget; use Filament\Widgets\StatsOverviewWidget\Stat; class ProductStats extends StatsOverviewWidget { use InteractsWithPageTable; protected function getTablePage(): string { return ListProducts::class; } protected function getStats(): array { return [ Stat::make('Total Products', $this->tableRecordsCount), Stat::make('Filtered Products', $this->getPageTableQuery()->count()), ]; } }

List 页面app/Filament/Resources/Products/Pages/ListProducts.php):

<?php namespace App\Filament\Resources\Products\Pages; use App\Filament\Resources\Products\ProductResource; use App\Filament\Resources\Products\Widgets\ProductStats; use Filament\Pages\Concerns\ExposesTableToWidgets; use Filament\Resources\Pages\ListRecords; class ListProducts extends ListRecords { use ExposesTableToWidgets; public static string $resource = ProductResource::class; protected function getHeaderWidgets(): array { return [ ProductStats::class, ]; } }

这样,列表页头部会渲染两张统计卡片:Total Products显示全部商品数,Filtered Products则随用户的搜索与筛选实时变化。配合 Stat 组件的description()color()icon()等方法,即可快速搭建具备运营价值的资源页概览区。

九、小结与更多参考

资源页 Widget 的核心脉络可以概括为三条链路:

  1. 创建与注册make:filament-widget --resource=...生成类与视图 → 在资源的getWidgets()中注册 → 在页面的getHeaderWidgets()/getFooterWidgets()中挂载;
  2. 记录联动:Edit / View 页面通过$record属性把当前模型注入 Widget;
  3. 表格联动:List 页面通过ExposesTableToWidgets+InteractsWithPageTable把搜索、筛选、分页、排序状态同步给 Widget,并通过getPageTableQuery()/getPageTableRecords()/$tableRecordsCount提供三种粒度的数据访问方式。

继续深入可参考以下仓库资源:

  • Widget 基类与 make() 属性注入
  • 页面表格数据暴露 Trait
  • Widget 与页面表格交互 Trait
  • 页面 Widget 渲染与网格逻辑
  • make:filament-widget 命令实现
  • 自定义页面中的 Widget 与响应式网格
  • 仪表盘 Widget 构建指南
  • 列表记录页面、编辑记录页面、查看记录页面

【免费下载链接】filamentA powerful open-source UI framework for Laravel • Build and ship apps & admin panels fast with Livewire项目地址: https://gitcode.com/GitHub_Trending/fi/filament

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询