Laravel Nova/Filament的自定义字段与组件开发:扩展后台管理界面的能力

Laravel Nova/Filament 的自定义字段与组件开发:扩展后台管理界面的能力

大家好,今天我们来深入探讨 Laravel Nova 和 Filament 这两个强大的后台管理框架的自定义字段与组件开发。我们将分析它们各自的架构,并通过实际的代码示例,展示如何通过自定义字段和组件来扩展它们的能力,从而构建更符合项目需求的后台管理界面。

Nova 自定义字段开发

Laravel Nova 提供了强大的字段自定义能力。通过自定义字段,我们可以实现各种复杂的数据输入和展示需求。Nova 的字段本质上是 Vue.js 组件的封装,因此我们需要熟悉 Vue.js 的基础知识。

1. 创建自定义字段类

首先,我们需要创建一个继承自 LaravelNovaFieldsField 的自定义字段类。这个类负责定义字段的行为,例如如何存储数据、如何展示数据等等。

<?php

namespace AppNovaFields;

use LaravelNovaFieldsField;

class CustomTextField extends Field
{
    /**
     * The field's component.
     *
     * @var string
     */
    public $component = 'custom-text-field';

    /**
     * Resolve the given attribute from the given resource.
     *
     * @param  mixed  $resource
     * @param  string  $attribute
     * @return mixed
     */
    public function resolveAttribute($resource, $attribute)
    {
        return $resource->{$attribute};
    }

    /**
     * Hydrate the given attribute on the model based on the incoming request.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  string  $requestAttribute
     * @param  object  $model
     * @param  string  $attribute
     * @return void
     */
    public function fillAttributeFromRequest(Request $request, $requestAttribute, $model, $attribute)
    {
        if ($request->exists($requestAttribute)) {
            $model->{$attribute} = $request->input($requestAttribute);
        }
    }

    /**
     * Define any extra serialization data for the field.
     *
     * @return array
     */
    public function jsonSerialize(): array
    {
        return array_merge(parent::jsonSerialize(), [
            'foo' => 'bar', // 传递给 Vue 组件的额外数据
        ]);
    }
}
  • $component: 定义了该字段使用的 Vue 组件的名称。
  • resolveAttribute(): 定义了如何从模型中获取字段的值。
  • fillAttributeFromRequest(): 定义了如何将请求中的数据保存到模型中。
  • jsonSerialize(): 定义了传递给 Vue 组件的额外数据。

2. 创建 Vue 组件

接下来,我们需要创建一个 Vue 组件,用于渲染自定义字段。这个组件需要处理数据的显示和编辑。

<template>
  <div>
    <label :for="id">{{ label }}</label>
    <input
      :id="id"
      type="text"
      class="w-full form-control form-input form-input-bordered"
      :value="value"
      @input="updateValue($event.target.value)"
    />
    <p v-if="foo">{{ foo }}</p>
  </div>
</template>

<script>
export default {
  props: ['resource', 'resourceName', 'resourceId', 'field', 'value'],

  mounted() {
    console.log('CustomTextField mounted');
  },

  computed: {
    id() {
      return `custom-text-field-${this.field.attribute}`;
    },
    label() {
      return this.field.name;
    },
    foo() {
      return this.field.foo;
    }
  },

  methods: {
    updateValue(value) {
      this.$emit('input', value);
    },
  },
};
</script>
  • props: 接收来自 Nova 的数据,例如 resource, resourceName, resourceId, field, value
  • computed: 用于计算组件的属性,例如 id, label
  • methods: 用于定义组件的方法,例如 updateValue,用于将用户输入的值传递给 Nova。

3. 注册 Vue 组件

我们需要将 Vue 组件注册到 Nova 中。可以在 app/Providers/NovaServiceProvider.php 文件的 boot 方法中进行注册。

public function boot()
{
    parent::boot();

    Nova::script('custom-text-field', asset('js/components/CustomTextField.js'));
}

4. 使用自定义字段

现在,我们可以在 Nova 资源中使用自定义字段了。

<?php

namespace AppNova;

use AppNovaFieldsCustomTextField;
use LaravelNovaResource;
use LaravelNovaFieldsID;
use IlluminateHttpRequest;

class Post extends Resource
{
    /**
     * The model the resource corresponds to.
     *
     * @var string
     */
    public static $model = AppModelsPost::class;

    /**
     * The single value that should be used to represent the resource when being displayed.
     *
     * @var string
     */
    public static $title = 'title';

    /**
     * The columns that should be searched.
     *
     * @var array
     */
    public static $search = [
        'title',
    ];

    /**
     * Get the fields displayed by the resource.
     *
     * @param  IlluminateHttpRequest  $request
     * @return array
     */
    public function fields(Request $request)
    {
        return [
            ID::make(__('ID'), 'id')->sortable(),
            CustomTextField::make('Title', 'title'),
        ];
    }
}

表格总结 Nova 自定义字段开发步骤:

步骤 描述 代码示例
1. 创建自定义字段类 继承 LaravelNovaFieldsField,定义字段行为。 class CustomTextField extends Field { ... }
2. 创建 Vue 组件 创建 Vue 组件,用于渲染字段的 UI。 <template>...</template><script>...</script>
3. 注册 Vue 组件 NovaServiceProvider 中注册 Vue 组件。 Nova::script('custom-text-field', asset('js/components/CustomTextField.js'));
4. 使用自定义字段 在 Nova 资源中使用自定义字段。 CustomTextField::make('Title', 'title')

Filament 自定义字段开发

Filament 也提供了强大的自定义字段能力,称为 Custom Form Components。与 Nova 类似,Filament 的自定义字段也是基于 Livewire 和 Alpine.js 构建的。

1. 创建自定义字段组件

首先,我们需要创建一个 Livewire 组件,用于渲染自定义字段。

<?php

namespace AppLivewireForms;

use FilamentFormsComponentsComponent;
use FilamentFormsContractsHasForms;
use FilamentFormsGet;
use FilamentFormsSet;
use LivewireComponent;

class CustomTextField extends Component implements HasForms
{
    use FilamentFormsConcernsInteractsWithForms;

    public ?string $state = null;

    public string $fieldId = 'custom-text-field';

    public string $label = 'Custom Text Field';

    public function mount(): void
    {
        $this->form->fill([
            'state' => $this->state,
        ]);
    }

    public function render()
    {
        return view('livewire.forms.custom-text-field');
    }

    public function getStatePath(): string
    {
        return 'state';
    }
}
  • state: 用于存储字段的值。
  • fieldId: 字段的 ID,用于关联 label 和 input。
  • label: 字段的标签。
  • mount(): 在组件挂载时执行,用于初始化表单。
  • render(): 渲染组件的视图。
  • getStatePath(): 用于返回组件状态的路径,对于使用 ->live() 很有用。

2. 创建 Blade 视图

接下来,我们需要创建一个 Blade 视图,用于渲染自定义字段的 UI。

<div>
    <label for="{{ $fieldId }}">{{ $label }}</label>
    <input
        type="text"
        id="{{ $fieldId }}"
        wire:model.live="state"
        class="filament-input block w-full border-gray-300 focus:border-primary-500 focus:ring-primary-500 rounded-md shadow-sm"
    />
</div>
  • wire:model.live="state": 将 input 的值绑定到组件的 state 属性。

3. 在 Filament 资源中使用自定义字段

现在,我们可以在 Filament 资源中使用自定义字段了。

<?php

namespace AppFilamentResources;

use AppFilamentResourcesPostResourcePages;
use AppModelsPost;
use FilamentForms;
use FilamentFormsForm;
use FilamentResourcesResource;
use FilamentTables;
use FilamentTablesTable;
use AppLivewireFormsCustomTextField;

class PostResource extends Resource
{
    protected static ?string $model = Post::class;

    protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                FormsComponentsTextInput::make('title')
                    ->required()
                    ->maxLength(255),
                CustomTextField::make('custom_field') // 使用自定义字段
                ->label('Custom Field Label')
                ->statePath('custom_field'), //指定state存储的字段
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                TablesColumnsTextColumn::make('title'),
                TablesColumnsTextColumn::make('created_at')
                    ->dateTime(),
                TablesColumnsTextColumn::make('updated_at')
                    ->dateTime(),
            ])
            ->filters([
                //
            ])
            ->actions([
                TablesActionsEditAction::make(),
            ])
            ->bulkActions([
                TablesActionsBulkActionGroup::make([
                    TablesActionsDeleteBulkAction::make(),
                ]),
            ]);
    }

    public static function getRelations(): array
    {
        return [
            //
        ];
    }

    public static function getPages(): array
    {
        return [
            'index' => PagesListPosts::route('/'),
            'create' => PagesCreatePost::route('/create'),
            'edit' => PagesEditPost::route('/{record}/edit'),
        ];
    }
}
  • CustomTextField::make('custom_field'): 创建自定义字段实例,custom_field 是字段的名称,用于存储数据。
  • label('Custom Field Label'): 设置字段的标签。
  • statePath('custom_field'): 绑定组件的 state 属性到模型的 custom_field 属性。

表格总结 Filament 自定义字段开发步骤:

步骤 描述 代码示例
1. 创建 Livewire 组件 创建 Livewire 组件,用于渲染字段的 UI。 class CustomTextField extends Component implements HasForms { ... }
2. 创建 Blade 视图 创建 Blade 视图,用于渲染字段的 UI。 <div>...</div>
3. 在 Filament 资源中使用自定义字段 在 Filament 资源中使用自定义字段。 CustomTextField::make('custom_field')->label('Custom Field Label')

Nova 自定义组件开发

除了自定义字段,Nova 还允许我们开发自定义组件,用于扩展后台管理界面的功能。例如,我们可以创建一个自定义面板,用于展示统计数据。

1. 创建自定义组件

首先,我们需要创建一个 Vue 组件,用于渲染自定义组件。

<template>
  <div class="card">
    <h3 class="font-bold mb-2">{{ title }}</h3>
    <p>{{ description }}</p>
  </div>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      required: true,
    },
    description: {
      type: String,
      required: true,
    },
  },

  mounted() {
    console.log('CustomComponent mounted');
  },
};
</script>
  • props: 接收来自 Nova 的数据,例如 title, description

2. 注册 Vue 组件

我们需要将 Vue 组件注册到 Nova 中。可以在 app/Providers/NovaServiceProvider.php 文件的 boot 方法中进行注册。

public function boot()
{
    parent::boot();

    Nova::component('custom-component', asset('js/components/CustomComponent.js'));
}

3. 创建 Nova 工具

接下来,我们需要创建一个 Nova 工具,用于将自定义组件添加到 Nova 界面中。

<?php

namespace AppNovaTools;

use LaravelNovaTool;

class CustomTool extends Tool
{
    /**
     * Perform any tasks that need to happen when the tool is booted.
     *
     * @return void
     */
    public function boot()
    {
        Nova::script('custom-tool', asset('js/tool.js'));
    }

    /**
     * Build the view that renders the navigation links for the tool.
     *
     * @return IlluminateViewView
     */
    public function renderNavigation()
    {
        return view('nova.tools.custom-tool');
    }
}
  • boot(): 在工具启动时执行,用于注册 JavaScript 文件。
  • renderNavigation(): 渲染工具的导航链接。

4. 创建 Blade 视图

我们需要创建一个 Blade 视图,用于渲染工具的导航链接。

<router-link to="/custom-tool" class="block py-3 px-6 text-sm font-semibold text-gray-800 hover:bg-gray-100">
    Custom Tool
</router-link>

5. 创建 Nova 路由

我们需要创建一个 Nova 路由,用于渲染自定义组件。

<?php

namespace AppNova;

use IlluminateSupportFacadesRoute;
use LaravelNovaNova;
use LaravelNovaTool;

class CustomTool extends Tool
{
    /**
     * Perform any tasks that need to happen when the tool is booted.
     *
     * @return void
     */
    public function boot()
    {
        Nova::script('custom-tool', asset('js/tool.js'));
    }

    /**
     * Build the view that renders the navigation links for the tool.
     *
     * @return IlluminateViewView
     */
    public function renderNavigation()
    {
        return view('nova.tools.custom-tool');
    }

    /**
     * Register the tool's routes.
     *
     * @param  array  $routes
     * @return array
     */
    public static function routes()
    {
        Route::get('/custom-tool', function () {
            return Nova::render('custom-tool');
        });
    }
}

6. 创建自定义工具视图

我们需要创建一个自定义工具视图,它使用自定义组件。

<template>
  <div>
    <h1>Custom Tool</h1>
    <custom-component title="Welcome" description="This is a custom component in Nova." />
  </div>
</template>

<script>
export default {
  mounted() {
    console.log('CustomTool view mounted');
  },
};
</script>

7. 注册 Nova 工具

我们需要将 Nova 工具注册到 config/nova.php 文件中。

<?php

return [

    // ...

    'tools' => [
        new AppNovaToolsCustomTool,
    ],

];

表格总结 Nova 自定义组件开发步骤:

步骤 描述 代码示例
1. 创建 Vue 组件 创建 Vue 组件,用于渲染自定义组件的 UI。 <template>...</template><script>...</script>
2. 注册 Vue 组件 NovaServiceProvider 中注册 Vue 组件。 Nova::component('custom-component', asset('js/components/CustomComponent.js'));
3. 创建 Nova 工具 创建 Nova 工具,用于将自定义组件添加到 Nova 界面中。 class CustomTool extends Tool { ... }
4. 创建 Blade 视图 创建 Blade 视图,用于渲染工具的导航链接。 <router-link to="/custom-tool">...</router-link>
5. 创建 Nova 路由 创建 Nova 路由,用于渲染自定义组件。 Route::get('/custom-tool', function () { ... });
6. 创建自定义工具视图 创建自定义工具视图,它使用自定义组件。 <custom-component title="Welcome" description="This is a custom component in Nova." />
7. 注册 Nova 工具 将 Nova 工具注册到 config/nova.php 文件中。 'tools' => [ new AppNovaToolsCustomTool, ],

Filament 定制化组件开发

Filament 提供了许多可定制的组件,例如 actions, columns, filters 等。我们可以通过扩展这些组件来满足特定的需求。

1. 定制化 Actions

可以创建自定义 actions 用于在 Filament 的 table 和 form 中执行特定的操作。

<?php

namespace AppFilamentActions;

use FilamentActionsAction;

class CustomAction extends Action
{
    public static function make(string $name): static
    {
        return parent::make($name)
            ->label('Custom Action')
            ->icon('heroicon-o-pencil-square')
            ->requiresConfirmation()
            ->action(function () {
                // 执行自定义操作
                Log::info('Custom action executed!');
            });
    }
}

然后在 resource 的 Table 或 Form 中使用:

public static function table(Table $table): Table
{
    return $table
        ->columns([
            // ...
        ])
        ->actions([
            AppFilamentActionsCustomAction::make('custom'),
        ])
        ->bulkActions([
            // ...
        ]);
}

2. 定制化 Columns

定制化 Columns 允许您自定义表格中数据的展示方式。

<?php

namespace AppFilamentColumns;

use FilamentTablesColumnsColumn;

class CustomColumn extends Column
{
    protected function setUp(): void
    {
        $this->label('Custom Column')
            ->formatStateUsing(function ($state) {
                return "Custom: " . $state;
            });
    }
}

在 resource 的 Table 中使用:

public static function table(Table $table): Table
{
    return $table
        ->columns([
            AppFilamentColumnsCustomColumn::make('name'),
        ])
        ->actions([
            // ...
        ])
        ->bulkActions([
            // ...
        ]);
}

3. 定制化 Filters

通过定制 Filters,可以为表格添加更灵活的筛选功能。

<?php

namespace AppFilamentFilters;

use FilamentTablesFiltersFilter;
use IlluminateDatabaseEloquentBuilder;

class CustomFilter extends Filter
{
    public static function make(string $name): static
    {
        return parent::make($name)
            ->label('Custom Filter')
            ->query(function (Builder $query): Builder {
                return $query->where('status', 'active');
            });
    }
}

在 resource 的 Table 中使用:

public static function table(Table $table): Table
{
    return $table
        ->columns([
            // ...
        ])
        ->filters([
            AppFilamentFiltersCustomFilter::make('custom'),
        ])
        ->actions([
            // ...
        ])
        ->bulkActions([
            // ...
        ]);
}

表格总结 Filament 定制化组件开发步骤:

步骤 描述 代码示例
1. 定制化 Actions 创建自定义 actions 用于在 Filament 的 table 和 form 中执行特定的操作。 class CustomAction extends Action { ... } AppFilamentActionsCustomAction::make('custom')
2. 定制化 Columns 定制化 Columns 允许您自定义表格中数据的展示方式。 class CustomColumn extends Column { ... } AppFilamentColumnsCustomColumn::make('name')
3. 定制化 Filters 通过定制 Filters,可以为表格添加更灵活的筛选功能。 class CustomFilter extends Filter { ... } AppFilamentFiltersCustomFilter::make('custom')

总结:灵活扩展,构建强大后台

通过以上示例,我们可以看到 Laravel Nova 和 Filament 都提供了强大的自定义字段和组件开发能力。无论是自定义字段,组件,actions, columns, filters,我们都可以根据项目需求,灵活地扩展后台管理界面的功能,构建更符合业务需求的后台管理系统。

编写可维护的自定义组件

编写可维护的自定义字段和组件需要遵循一些最佳实践,例如清晰的代码结构、详细的文档、以及充分的测试。

持续学习与探索

Laravel Nova 和 Filament 都在不断发展,保持学习和探索的热情,才能更好地利用它们的功能,构建出更优秀的后台管理界面。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注