mcpField
W
widgets

widgets

github.com/mwguerra开发3675 次安装27 周前更新
代码仓库

关于 widgets

widgets 是用于生成 FilamentPHP v4 仪表板组件的技能,涵盖统计、图表、表格和自定义组件。它还涉及数据获取、配置、交互、布局和注册。

主要功能

  1. 01统计、图表、表格和自定义组件
  2. 02支持筛选的交互式图表
  3. 03组件数据获取与配置
  4. 04仪表板和资源页面注册
  5. 05布局与样式选项

适用场景

  1. 01在仪表板添加汇总指标
  2. 02通过图表或表格展示订单数据
  3. 03在资源页面嵌入组件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mwguerra/claude-code-plugins --skill widgets

安装后在 Claude Code 中输入/widgets确认已加载。

详细介绍

FilamentPHP 小组件生成技能

概述

此技能可生成 FilamentPHP v4 仪表板小组件,包括统计概览小组件、图表小组件、表格小组件和自定义小组件。

文档参考

重要: 生成小组件前,请先阅读:

  • /home/mwguerra/projects/mwguerra/claude-code-plugins/filament-specialist/skills/docs/references/widgets/

创建小组件

使用 Artisan 生成
# Basic widget
php artisan make:filament-widget StatsOverview

# Stats overview widget
php artisan make:filament-widget StatsOverview --stats-overview

# Chart widget
php artisan make:filament-widget RevenueChart --chart

# Table widget
php artisan make:filament-widget LatestOrders --table

# Resource widget
php artisan make:filament-widget PostStats --resource=PostResource

统计概览小组件

<?php

declare(strict_types=1);

namespace App\Filament\Widgets;

use App\Models\Order;
use App\Models\User;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;

class StatsOverview extends BaseWidget
{
    protected static ?int $sort = 1;

    protected function getStats(): array
    {
        return [
            // Basic stat
            Stat::make('Total Users', User::count())
                ->description('All registered users')
                ->descriptionIcon('heroicon-o-users')
                ->color('primary'),

            // Stat with trend
            Stat::make('New Users', User::whereMonth('created_at', now()->month)->count())
                ->description('32% increase')
                ->descriptionIcon('heroicon-m-arrow-trending-up')
                ->color('success')
                ->chart([7, 3, 4, 5, 6, 3, 5, 8])  // Sparkline data
                ->chartColor('success'),

            // Stat with decrease trend
            Stat::make('Bounce Rate', '21%')
                ->description('7% decrease')
                ->descriptionIcon('heroicon-m-arrow-trending-down')
                ->color('danger'),

            // Revenue stat with formatting
            Stat::make('Revenue', '$' . number_format(Order::sum('total'), 2))
                ->description('This month')
                ->descriptionIcon('heroicon-o-currency-dollar')
                ->color('success')
                ->chart([1200, 1400, 1100, 1800, 2200, 1900, 2400])
                ->chartColor('success'),

            // Stat with extra info
            Stat::make('Pending Orders', Order::where('status', 'pending')->count())
                ->description('Requires attention')
                ->descriptionIcon('heroicon-o-clock')
                ->color('warning')
                ->extraAttributes([
                    'class' => 'cursor-pointer',
                    'wire:click' => 'goToOrders',
                ]),
        ];
    }

    // Optional: Make stats live
    protected static ?string $pollingInterval = '15s';

    // Optional: Column span
    protected int | string | array $columnSpan = 'full';
}

图表小组件

折线图
<?php

declare(strict_types=1);

namespace App\Filament\Widgets;

use App\Models\Order;
use Filament\Widgets\ChartWidget;
use Illuminate\Support\Carbon;

class RevenueChart extends ChartWidget
{
    protected static ?string $heading = 'Revenue';
    protected static ?int $sort = 2;
    protected int | string | array $columnSpan = 'full';

    protected function getData(): array
    {
        $data = collect(range(1, 12))->map(function ($month) {
            return Order::whereMonth('created_at', $month)
                ->whereYear('created_at', now()->year)
                ->sum('total');
        });

        return [
            'datasets' => [
                [
                    'label' => 'Revenue',
                    'data' => $data->values()->toArray(),
                    'borderColor' => '#10b981',
                    'backgroundColor' => 'rgba(16, 185, 129, 0.1)',
                    'fill' => true,
                ],
            ],
            'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
        ];
    }

    protected function getType(): string
    {
        return 'line';
    }

    protected function getOptions(): array
    {
        return [
            'plugins' => [
                'legend' => [
                    'display' => false,
                ],
            ],
            'scales' => [
                'y' => [
                    'beginAtZero' => true,
                    'ticks' => [
                        'callback' => '(value) => "$" + value.toLocaleString()',
                    ],
                ],
            ],
        ];
    }
}
柱状图
<?php

declare(strict_types=1);

namespace App\Filament\Widgets;

use App\Models\Order;
use Filament\Widgets\ChartWidget;

class OrdersPerCategory extends ChartWidget
{
    protected static ?string $heading = 'Orders by Category';
    protected static ?int $sort = 3;

    protected function getData(): array
    {
        $categories = \App\Models\Category::withCount('orders')->get();

        return [
            'datasets' => [
                [
                    'label' => 'Orders',
                    'data' => $categories->pluck('orders_count')->toArray(),
                    'backgroundColor' => [
                        '#3b82f6',
                        '#10b981',
                        '#f59e0b',
                        '#ef4444',
                        '#8b5cf6',
                    ],
                ],
            ],
            'labels' => $categories->pluck('name')->toArray(),
        ];
    }

    protected function getType(): string
    {
        return 'bar';
    }
}
环形图/饼图
<?php

declare(strict_types=1);

namespace App\Filament\Widgets;

use App\Models\Order;
use Filament\Widgets\ChartWidget;

class OrderStatusChart extends ChartWidget
{
    protected static ?string $heading = 'Order Status Distribution';
    protected static ?int $sort = 4;

    protected function getData(): array
    {
        $statuses = Order::selectRaw('status, COUNT(*) as count')
            ->groupBy('status')
            ->pluck('count', 'status');

        return [
            'datasets' => [
                [
                    'data' => $statuses->values()->toArray(),
                    'backgroundColor' => [
                        '#f59e0b',  // pending - warning
                        '#3b82f6',  // processing - primary
                        '#10b981',  // completed - success
                        '#ef4444',  // cancelled - danger
                    ],
                ],
            ],
            'labels' => $statuses->keys()->map(fn ($s) => ucfirst($s))->toArray(),
        ];
    }

    protected function getType(): string
    {
        return 'doughnut';  // or 'pie'
    }

    protected function getOptions(): array
    {
        return [
            'plugins' => [
                'legend' => [
                    'position' => 'bottom',
                ],
            ],
        ];
    }
}
带筛选器的交互式图表
<?php

declare(strict_types=1);

namespace App\Filament\Widgets;

use App\Models\Order;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Widgets\ChartWidget;

class FilterableRevenueChart extends ChartWidget
{
    protected static ?string $heading = 'Revenue Over Time';

    public ?string $filter = 'week';

    protected function getFilters(): ?array
    {
        return [
            'today' => 'Today',
            'week' => 'Last 7 days',
            'month' => 'This month',
            'year' => 'This year',
        ];
    }

    protected function getData(): array
    {
        $data = match ($this->filter) {
            'today' => $this->getTodayData(),
            'week' => $this->getWeekData(),
            'month' => $this->getMonthData(),
            'year' => $this->getYearData(),
        };

        return [
            'datasets' => [
                [
                    'label' => 'Revenue',
                    'data' => $data['values'],
                    'borderColor' => '#3b82f6',
                ],
            ],
            'labels' => $data['labels'],
        ];
    }

    protected function getType(): string
    {
        return 'line';
    }

    private function getTodayData(): array
    {
        // Implementation
    }

    private function getWeekData(): array
    {
        // Implementation
    }

    private function getMonthData(): array
    {
        // Implementation
    }

    private function getYearData(): array
    {
        // Implementation
    }
}

表格小组件

<?php

declare(strict_types=1);

namespace App\Filament\Widgets;

use App\Models\Order;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as BaseWidget;

class LatestOrders extends BaseWidget
{
    protected static ?string $heading = 'Latest Orders';
    protected static ?int $sort = 5;
    protected int | string | array $columnSpan = 'full';

    public function table(Table $table): Table
    {
        return $table
            ->query(
                Order::query()
                    ->latest()
                    ->limit(10)
            )
            ->columns([
                Tables\Columns\TextColumn::make('number')
                    ->searchable(),
                Tables\Columns\TextColumn::make('customer.name')
                    ->label('Customer')
                    ->searchable(),
                Tables\Columns\BadgeColumn::make('status')
                    ->colors([
                        'warning' => 'pending',
                        'primary' => 'processing',
                        'success' => 'completed',
                        'danger' => 'cancelled',
                    ]),
                Tables\Columns\TextColumn::make('total')
                    ->money('usd')
                    ->sortable(),
                Tables\Columns\TextColumn::make('created_at')
                    ->dateTime()
                    ->sortable(),
            ])
            ->actions([
                Tables\Actions\Action::make('view')
                    ->url(fn (Order $record): string => route('filament.admin.resources.orders.view', $record))
                    ->icon('heroicon-o-eye'),
            ])
            ->paginated(false);
    }
}

自定义小组件

<?php

declare(strict_types=1);

namespace App\Filament\Widgets;

use App\Models\Task;
use Filament\Widgets\Widget;

class TasksWidget extends Widget
{
    protected static string $view = 'filament.widgets.tasks-widget';
    protected static ?int $sort = 6;
    protected int | string | array $columnSpan = 1;

    public array $tasks = [];

    public function mount(): void
    {
        $this->tasks = Task::where('user_id', auth()->id())
            ->whereNull('completed_at')
            ->orderBy('due_date')
            ->limit(5)
            ->get()
            ->toArray();
    }

    public function completeTask(int $taskId): void
    {
        Task::find($taskId)->update(['completed_at' => now()]);
        $this->mount(); // Refresh tasks
    }
}

Blade 视图(resources/views/filament/widgets/tasks-widget.blade.php):

<x-filament-widgets::widget>
    <x-filament::section>
        <x-slot name="heading">
            My Tasks
        </x-slot>

        <ul class="divide-y divide-gray-200 dark:divide-gray-700">
            @forelse ($tasks as $task)
                <li class="py-3 flex items-center justify-between">
                    <div>
                        <p class="text-sm font-medium text-gray-900 dark:text-white">
                            {{ $task['title'] }}
                        </p>
                        <p class="text-xs text-gray-500">
                            Due: {{ \Carbon\Carbon::parse($task['due_date'])->format('M j, Y') }}
                        </p>
                    </div>
                    <x-filament::icon-button
                        icon="heroicon-o-check"
                        wire:click="completeTask({{ $task['id'] }})"
                        color="success"
                    />
                </li>
            @empty
                <li class="py-3 text-sm text-gray-500">
                    No pending tasks
                </li>
            @endforelse
        </ul>
    </x-filament::section>
</x-filament-widgets::widget>

小组件注册

仪表板小组件
// In AdminPanelProvider.php
->widgets([
    Widgets\AccountWidget::class,  // Default
    Widgets\FilamentInfoWidget::class,  // Default
    \App\Filament\Widgets\StatsOverview::class,
    \App\Filament\Widgets\RevenueChart::class,
    \App\Filament\Widgets\LatestOrders::class,
])
资源页面小组件
// In resource class
public static function getWidgets(): array
{
    return [
        Widgets\PostStatsOverview::class,
    ];
}

// In ListRecords page
protected function getHeaderWidgets(): array
{
    return [
        Widgets\PostStatsOverview::class,
    ];
}

protected function getFooterWidgets(): array
{
    return [
        Widgets\RecentPosts::class,
    ];
}

小组件配置

class MyWidget extends Widget
{
    // Sort order
    protected static ?int $sort = 1;

    // Column span (1, 2, 'full', or responsive array)
    protected int | string | array $columnSpan = [
        'md' => 2,
        'xl' => 3,
    ];

    // Polling interval
    protected static ?string $pollingInterval = '10s';

    // Visibility
    public static function canView(): bool
    {
        return auth()->user()->isAdmin();
    }

    // Lazy loading
    protected static bool $isLazy = true;
}

输出

生成的小组件包括:

  1. 正确选择小组件类型
  2. 数据获取方法
  3. 图表配置
  4. 样式和布局
  5. 交互功能(筛选器、操作)
  6. 性能优化

评论

支持 Markdown