Filament + Laravel 12 项目骨架
目标:在 Laravel 12 + MySQL 上,快速搭建一套 Filament Admin 后台,内置权限(Spatie + Shield)、Dashboard、商品 CRUD(ProductResource)、中文本地化、基础种子数据。
1) 新建项目 & 依赖安装
# 1. 创建项目(Laravel 12)
composer create-project laravel/laravel:"^12.0" filament-skeleton
cd filament-skeleton
# 2. 配置 .env (数据库、缓存、队列等)
cp .env.example .env
php artisan key:generate
# 3. 安装 Filament(v3)
composer require filament/filament:"^3.2" -W
php artisan filament:install
# 4. 安装权限(Spatie)+ Filament Shield(自动生成资源权限)
composer require spatie/laravel-permission:"^6.7" -W
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
php artisan migrate
composer require bezhansalleh/filament-shield:"^8.0"
php artisan vendor:publish --tag=filament-shield-config
# 5.(可选)中文语言包
composer require filament/laravel-translations:"^3.0"
php artisan vendor:publish --tag=filament-translations
.env建议:APP_NAME="Admin" APP_LOCALE=zh_CN APP_FALLBACK_LOCALE=zh_CN APP_TIMEZONE=Asia/Shanghai DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=filament_skeleton DB_USERNAME=root DB_PASSWORD=secret
2) 用户模型启用权限 Trait
编辑 app/Models/User.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
    use HasFactory, Notifiable, HasRoles; // ← 权限 Trait
    protected $fillable = [
        'name', 'email', 'password',
    ];
    protected $hidden = [
        'password', 'remember_token',
    ];
}注意:Spatie 使用webguard;如需自定义 guard,请同步在config/auth.php和config/permission.php中调整。
3) 基础数据表:Products
3.1 迁移
php artisan make:model Product -mfs生成的迁移(database/migrations/xxxx_xx_xx_create_products_table.php)示例:
public function up(): void
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('sku')->unique();
        $table->unsignedInteger('stock')->default(0);
        $table->decimal('price', 10, 2)->default(0);
        $table->enum('status', ['draft', 'active', 'disabled'])->default('draft');
        $table->json('images')->nullable();
        $table->timestamps();
        $table->softDeletes();
        $table->index(['status']);
    });
}3.2 模型(app/Models/Product.php)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends Model
{
    use HasFactory, SoftDeletes;
    protected $fillable = [
        'name', 'sku', 'stock', 'price', 'status', 'images',
    ];
    protected $casts = [
        'images' => 'array',
        'price' => 'decimal:2',
    ];
}3.3 工厂(database/factories/ProductFactory.php)
public function definition(): array
{
    return [
        'name' => fake()->words(3, true),
        'sku' => strtoupper(fake()->bothify('SKU-####-??')),
        'stock' => fake()->numberBetween(0, 999),
        'price' => fake()->randomFloat(2, 10, 9999),
        'status' => fake()->randomElement(['draft','active','disabled']),
        'images' => [],
    ];
}4) Filament 基础安装 & 用户
# 安装向导已完成(前文第 1 步),这里创建后台用户
php artisan make:filament-user
# 依提示输入 name/email/password 生成后台登录账号默认后台路径:/admin(可在config/filament.php调整)
5) 生成 Product 资源(Resource)
php artisan make:filament-resource Product --generate会生成:
app/Filament/Resources/ProductResource.php
app/Filament/Resources/ProductResource/Pages/
  ├── ListProducts.php
  ├── CreateProduct.php
  └── EditProduct.php5.1 ProductResource 表单 & 列表示例
app/Filament/Resources/ProductResource.php:
<?php
namespace App\Filament\Resources;
use App\Models\Product;
use Filament\Forms;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\FileUpload;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\BadgeColumn;
class ProductResource extends Resource
{
    protected static ?string $model = Product::class;
    protected static ?string $navigationGroup = '商品管理';
    protected static ?string $navigationIcon = 'heroicon-o-cube';
    protected static ?string $navigationLabel = '商品';
    public static function form(Form $form): Form
    {
        return $form->schema([
            Section::make('基础信息')->schema([
                TextInput::make('name')->label('名称')->required()->maxLength(120),
                TextInput::make('sku')->label('SKU')->required()->unique(ignoreRecord: true),
                TextInput::make('price')->label('价格')->numeric()->required()->minValue(0),
                TextInput::make('stock')->label('库存')->numeric()->required()->minValue(0),
                Select::make('status')->label('状态')
                    ->options([
                        'draft' => '草稿',
                        'active' => '上架',
                        'disabled' => '下架',
                    ])->default('draft')->required(),
            ])->columns(2),
            Section::make('图片')->schema([
                FileUpload::make('images')
                    ->label('图片')
                    ->image()
                    ->multiple()
                    ->directory('products/'.date('Ymd'))
                    ->reorderable()
                    ->imageEditor(),
            ]),
        ]);
    }
    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                TextColumn::make('id')->sortable()->toggleable(isToggledHiddenByDefault: true),
                TextColumn::make('name')->label('名称')->searchable()->sortable(),
                TextColumn::make('sku')->label('SKU')->searchable()->copyable(),
                TextColumn::make('price')->label('价格')->money('CNY', 2),
                TextColumn::make('stock')->label('库存')->sortable(),
                BadgeColumn::make('status')->label('状态')
                    ->colors([
                        'secondary' => 'draft',
                        'success' => 'active',
                        'danger' => 'disabled',
                    ])
                    ->sortable(),
                TextColumn::make('created_at')->label('创建时间')->dateTime()->sortable(),
            ])
            ->filters([
                Tables\Filters\SelectFilter::make('status')->label('状态')
                    ->options([
                        'draft' => '草稿',
                        'active' => '上架',
                        'disabled' => '下架',
                    ]),
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
                Tables\Actions\DeleteAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\BulkActionGroup::make([
                    Tables\Actions\DeleteBulkAction::make(),
                ]),
            ]);
    }
    public static function getPages(): array
    {
        return [
            'index' => Pages\ListProducts::route('/'),
            'create' => Pages\CreateProduct::route('/create'),
            'edit' => Pages\EditProduct::route('/{record}/edit'),
        ];
    }
}6) Dashboard 小组件(统计卡)
php artisan make:filament-widget ProductStats --type=stats-overviewapp/Filament/Widgets/ProductStats.php:
<?php
namespace App\Filament\Widgets;
use App\Models\Product;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Card;
class ProductStats extends BaseWidget
{
    protected static ?string $heading = '数据总览';
    protected function getCards(): array
    {
        return [
            Card::make('商品总数', Product::query()->count()),
            Card::make('上架商品', Product::query()->where('status','active')->count()),
            Card::make('总库存', (int) Product::query()->sum('stock')),
        ];
    }
}将该组件加入默认面板(app/Filament/Pages/Dashboard.php 的 getWidgets() 中追加):
protected function getWidgets(): array
{
    return [
        Widgets\AccountWidget::class,
        \App\Filament\Widgets\ProductStats::class,
    ];
}7) 权限:Shield 一键生成
7.1 生成资源权限
# 根据现有 Filament 资源生成对应权限(view/list/create/update/delete)
php artisan shield:generate --all
# 创建默认角色(可在 config/filament-shield.php 里配置角色名,如 Super Admin / Admin / Editor)
php artisan shield:install这会创建roles/permissions并把 Super Admin 赋权(含*能力)。
7.2 为用户分配角色
# Tinker 示例
php artisan tinker
>>> $u = \App\Models\User::where('email','you@example.com')->first();
>>> $u->assignRole('Super Admin');之后登录 /admin,即可看到完整导航和权限受控菜单。8) Seeder(可选:快速造数)
创建 Seeder:
php artisan make:seeder ProductSeederdatabase/seeders/ProductSeeder.php:
public function run(): void
{
    \App\Models\Product::factory()->count(50)->create();
}注册到 DatabaseSeeder:
public function run(): void
{
    $this->call([ProductSeeder::class]);
}执行:
php artisan migrate --seed9) 中文化 & 主题
- config/app.php中设置:- 'locale' => 'zh_CN'
- 发布的语言包位于 lang/vendor/filament/*,可按需覆盖。
- 自定义主题可在 resources/css/filament/admin/theme.css定制 Tailwind 变量(php artisan filament:install已建立构建管线)。
10) 导航与分组
- 本示例在 - ProductResource里设置了:- protected static ?string $navigationGroup = '商品管理';
- protected static ?string $navigationLabel = '商品';
 
- 如需排序/图标统一,可在 config/filament.php的navigation设置中集中控制。
11) 常见问题(FAQ)
- 登录 403/无菜单:未分配角色或无权限。执行 Shield 安装并给用户 Super Admin角色。
- 图片上传失败:确认 filesystems.php配置及storage:link:php artisan storage:link。
- 权限缓存:修改权限后运行 php artisan permission:cache-reset。
- 多环境:在 .env中配置APP_URL、SESSION_、CACHE_、QUEUE_。
12) 一键脚本(可选)
将以下脚本保存为 scripts/quick-boot.sh:
#!/usr/bin/env bash
set -e
php artisan migrate --force
php artisan storage:link || true
# 生成资源权限并安装 Shield 角色
php artisan shield:generate --all || true
php artisan shield:install || true
# 如果没有管理员就创建一个(可改邮箱/密码)
php artisan tinker --execute='if(!\App\Models\User::where