目标:在 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 使用 web guard;如需自定义 guard,请同步在 config/auth.phpconfig/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.php

5.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-overview

app/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.phpgetWidgets() 中追加):

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 ProductSeeder

database/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 --seed

9) 中文化 & 主题

  • 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.phpnavigation 设置中集中控制。

11) 常见问题(FAQ)

  1. 登录 403/无菜单:未分配角色或无权限。执行 Shield 安装并给用户 Super Admin 角色。
  2. 图片上传失败:确认 filesystems.php 配置及 storage:linkphp artisan storage:link
  3. 权限缓存:修改权限后运行 php artisan permission:cache-reset
  4. 多环境:在 .env 中配置 APP_URLSESSION_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

标签: Laravel, Filament

添加新评论