Filament仪表板Stats Overview Widget布局样式问题求助
Filament Stats Overview Widget 布局异常问题解决
问题详情
在Filament仪表板中添加Stats Overview Widget时遇到布局样式异常:
- 自定义仪表板页面配置了响应式列网格:
public function getColumns(): int | string | array { return [ 'sm' => 1, 'md' => 2, 'xl' => 3, ]; }
- 每个Stats Overview Widget设置了
protected int | string | array $columnSpan = '1'; - 浏览器检查器显示Widget占据了正确的网格位置,但内部内容布局错乱;临时手动设置
grid-column: span 4;后显示正常,刷新后恢复异常 - 将
columnSpan设置为整行时布局正常,但需求是每行放置3个Widget;尝试自定义Blade文件但无法适配Widget的筛选器功能,希望找到无需重构的方案
相关代码
OrderDeliveredWidget 代码
<?php namespace App\Filament\Widgets; use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget\Stat; use App\Models\Order; use Filament\Widgets\Concerns\InteractsWithPageFilters; use Carbon\Carbon; use Filament\Widgets\StatsOverviewWidget\Card; class OrderDeliveredWidget extends BaseWidget { use InteractsWithPageFilters; protected int | string | array $columnSpan = '1'; protected function getStats(): array { $query = Order::query(); if ($this->filters['Date']) { $query->where('created_at', '>=', $this->getFilterDate($this->filters['Date'])); } $currentCount = $query->where('status', 'delivered')->count(); // Get the count for the previous period $previousPeriodStart = $this->getPreviousPeriodStart($this->filters['Date']); $previousCount = Order::where('status', 'delivered') ->where('created_at', '>=', $previousPeriodStart) ->where('created_at', '<', $this->getFilterDate($this->filters['Date'])) ->count(); $percentageChange = $this->calculatePercentageChange($previousCount, $currentCount); $description = $this->getDescription($percentageChange); $icon = $this->getDescriptionIcon($percentageChange); $color = $this->getColor($percentageChange); return [ Stat::make('Delivered Orders', $currentCount) ->description($description) ->descriptionIcon($icon) ->color($color), // Add more Stat::make() calls for other metrics ]; } private function getFilterDate(?string $filter): Carbon { return match($filter) { 'today' => now()->startOfDay(), 'week' => now()->startOfWeek(), 'month' => now()->startOfMonth(), 'year' => now()->startOfYear(), default => Carbon::createFromTimestamp(0), }; } protected function getFilters(): ?array { return [ 'today' => 'Today', 'week' => 'This week', 'month' => 'This month', 'year' => 'This year', ]; } private function calculatePercentageChange($oldValue, $newValue): float { if ($oldValue == 0) { return $newValue > 0 ? 100 : 0; } return (($newValue - $oldValue) / $oldValue) * 100; } private function getDescription(float $percentageChange): string { $formattedChange = number_format(abs($percentageChange), 1); $direction = $percentageChange >= 0 ? 'increase' : 'decrease'; return "{$formattedChange}% {$direction}"; } private function getDescriptionIcon(float $percentageChange): string { return $percentageChange >= 0 ? 'heroicon-m-arrow-trending-up' : 'heroicon-m-arrow-trending-down'; } private function getColor(float $percentageChange): string { if ($percentageChange > 10) { return 'success'; } elseif ($percentageChange < -10) { return 'danger'; } else { return 'warning'; } } private function getPreviousPeriodStart(?string $filter): Carbon { return match($filter) { 'today' => now()->subDay()->startOfDay(), 'week' => now()->subWeek()->startOfWeek(), 'month' => now()->subMonth()->startOfMonth(), 'year' => now()->subYear()->startOfYear(), default => Carbon::createFromTimestamp(0), }; } }
仪表板筛选器代码
public function filtersForm(Form $form): Form { return $form ->schema([ Section::make() ->schema([ Select::make('Date') ->options([ "all" => "All Time", 'today' => "Today", "week" => "Week", "month" => "Month", "year" => "Year" ]) ->default('all'), ]) ->columns(1), ]); }
解决方案
方案1:自定义Widget视图修复内部网格
Stats Overview Widget默认内部使用grid-cols-4布局,当Widget仅占页面1列时,内部网格会因空间不足导致布局错乱。通过自定义视图修改内部布局,同时保留筛选器交互:
- 在Widget类中添加视图声明:
protected static string $view = 'filament.widgets.order-delivered-widget';
- 创建视图文件
resources/views/filament/widgets/order-delivered-widget.blade.php,复制Filament默认的Stats Overview Widget视图代码,修改内部网格为单列:
<div class="grid gap-6"> @foreach($stats as $stat) <x-filament-widgets::stats-overview-widget.card :color="$stat->getColor()" :description="$stat->getDescription()" :description-icon="$stat->getDescriptionIcon()" :icon="$stat->getIcon()" :icon-color="$stat->getIconColor()" :label="$stat->getLabel()" :value="$stat->getValue()" :extra-attributes="$stat->getExtraAttributes()" /> @endforeach </div>
这里移除了默认的grid-cols-4,改为自适应单列布局,每个Stat卡片会占满Widget宽度,解决挤压问题。
方案2:自定义CSS覆盖内部样式
如果不想修改视图,可通过自定义CSS强制修改Widget内部网格:
.filament-widgets-stats-overview-widget .grid { grid-template-columns: 1fr !important; }
将这段CSS添加到Filament自定义CSS文件(如resources/css/filament.css),并在Filament配置中引入该文件。
方案3:排查筛选器参数冲突
检查页面全局筛选器与Widget筛选器的参数是否冲突:
- 页面筛选器的
Date选项包含all,但Widget的getFilterDate方法未处理all值,会默认返回时间戳0,可能导致数据异常间接影响布局 - 修改Widget的
getFilterDate方法,添加all分支:
private function getFilterDate(?string $filter): Carbon { return match($filter) { 'today' => now()->startOfDay(), 'week' => now()->startOfWeek(), 'month' => now()->startOfMonth(), 'year' => now()->startOfYear(), 'all' => Carbon::createFromTimestamp(0), // 新增处理all选项 default => Carbon::createFromTimestamp(0), }; }
同时在getStats方法中调整判断逻辑,避免all时添加无效条件:
if ($this->filters['Date'] && $this->filters['Date'] !== 'all') { $query->where('created_at', '>=', $this->getFilterDate($this->filters['Date'])); }
内容的提问来源于stack exchange,提问作者Pierre Sameh
相关产品推荐
相关产品推荐

