beholdr/filament-trilist

Filament插件,用于处理树形数据:treeselect和treeview

v0.5.2 2024-08-01 14:31 UTC

This package is auto-updated.

Last update: 2024-09-01 14:35:13 UTC


README

Latest Version on Packagist Total Downloads

Filament插件,用于处理树形数据:treeselect输入treeview页面。基于Trilist包

支持

你喜欢Filament Trilist吗?请通过Boosty支持我。

功能

  • treeselect输入和treeview页面
  • 树项可以有多个父级
  • 与关系或自定义层次结构数据一起使用

安装

您可以通过composer安装此包

composer require beholdr/filament-trilist

可选地,您可以使用以下方式发布视图

php artisan vendor:publish --tag="filament-trilist-views"

树形数据

当遵循格式时,您可以使用任何来源的层次数据

[
    ['id' => 'ID', 'label' => 'Item label', 'children' => [
        ['id' => 'ID', 'label' => 'Item label', 'children' => [...]],
        ...
    ]
]

例如,您可以使用类似staudenmeir/laravel-adjacency-list的特殊库来获取树形数据

Category::tree()->get()->toTree()

或使用自定义关系模式和方法,甚至使用ManyToMany(多对多)关系。

自引用实体的示例

迁移

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('professions', function (Blueprint $table) {
            $table->id();
            $table->string('label');
        });

        Schema::create('profession_profession', function (Blueprint $table) {
            $table->primary(['parent_id', 'child_id']);
            $table->foreignId('parent_id')->constrained('professions')->cascadeOnDelete();
            $table->foreignId('child_id')->constrained('professions')->cascadeOnDelete();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('professions');
        Schema::dropIfExists('profession_profession');
    }
};

模型

namespace App\Models;

use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class Profession extends Model
{
    protected $with = ['children'];

    public function parents()
    {
        return $this->belongsToMany(Profession::class, 'profession_profession', 'child_id', 'parent_id');
    }

    public function children()
    {
        return $this->belongsToMany(Profession::class, 'profession_profession', 'parent_id', 'child_id');
    }

    public function scopeRoot(Builder $builder)
    {
        $builder->doesntHave('parents');
    }
}

使用给定的模型,您可以生成如下所示的树形数据

Profession::root()->get();

treeselect输入

Treeselect input

导入TrilistSelect类并在您的Filament表单中使用它

use Beholdr\FilamentTrilist\Components\TrilistSelect

// with custom tree data
TrilistSelect::make('category_id')
    ->options($treeData),

// or with relationship
TrilistSelect::make('categories')
    ->relationship('categories')
    ->options($treeData)
    ->multiple(),

完整选项列表

TrilistSelect::make(string $fieldName)
    ->label(string $fieldLabel)
    ->placeholder(string | Closure $placeholder)
    ->disabled(bool | Closure $condition)

    // array of tree items
    ->options(array | Closure $options),

    // first argument defines name of the relationship, second can be used to modify relationship query
    ->relationship(string | Closure $relationshipName, ?Closure $modifyQueryUsing = null)

    // array of ids (or single id) of disabled items
    ->disabledOptions(string | int | array | Closure $value)

    // multiple selection mode, default: false
    ->multiple(bool | Closure $condition)

    // animate expand/collapse, default: true
    ->animated(bool | Closure $condition)

    // expand initial selected options, default: true
    ->expandSelected(bool | Closure $condition)

    // in independent mode children auto selected when parent is selected, default: false
    ->independent(bool | Closure $condition)

    // in leafs mode, the selected value is not grouped as the parent when all child elements are selected, default: false
    ->leafs(bool | Closure $condition)

    // tree item id field name, default: 'id'
    ->fieldId(string | Closure $value)

    // tree item label field name, default: 'label'
    ->fieldLabel(string | Closure $value)

    // tree item children field name, default: 'children'
    ->fieldChildren(string | Closure $value)

    // hook for generating custom labels, default: '(item) => item.label'
    ->labelHook(string | Closure $value)

    // enable filtering of items, default: false
    ->searchable(bool | Closure $condition)

    // enable autofocus on filter field, default: false
    ->autofocus(bool | Closure $condition)

    // search input placeholder
    ->searchPrompt(string | Htmlable | Closure $message)

    // select button label
    ->selectButton(string | Htmlable | Closure $message)

    // cancel button label
    ->cancelButton(string | Htmlable | Closure $message)

自定义标签

如果您想自定义标签,可以使用labelHook方法。它应返回一个字符串,该字符串将被处理为JS(请注意转义引号和特殊字符)

TrilistSelect::make('parent_id')
    ->labelHook(fn () => <<<JS
        (item) => `\${item.label} \${item.data?.description ? '<div style=\'font-size: 0.85em; opacity: 0.5\'>' + item.data.description + '</div>' : ''}`
        JS)

在过滤器中使用

您可以在自定义过滤器中使用treeselect

use App\Models\Category;
use Filament\Tables\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;

Filter::make('category')
    ->form([
        TrilistSelect::make('category_id')
            ->multiple()
            ->independent()
            ->options(Category::tree()->get()->toTree())
    ])
    ->query(function (Builder $query, array $data) {
        $query->when(
            $data['category_id'],
            function (Builder $query, $values) {
                $ids = Category::whereIn('id', $values)
                    ->get()
                    ->map(fn (Category $category) => $category
                        ->descendantsAndSelf()
                        ->pluck('id')
                        ->toArray()
                    )->flatten();
                $query->whereIn('category_id', $ids);
            }
        );
    })
    ->indicateUsing(function (array $data) {
        if (! $data['category_id']) return null;

        return Category::whereIn('id', $data['category_id'])->pluck('name')->toArray();
    }),

treeview页面

Treeview page

在您的资源目录的Pages目录内创建自定义页面类。注意,页面类扩展自Beholdr\FilamentTrilist\Components\TrilistPage

namespace App\Filament\Resources\PostResource\Pages;

use App\Filament\Resources\PostResource;
use App\Models\Post;
use Beholdr\FilamentTrilist\Components\TrilistPage;

class TreePosts extends TrilistPage
{
    protected static string $resource = PostResource::class;

    // optional page and tab title
    protected static ?string $title = 'Posts Tree';

    // return array of tree items (see below about tree data)
    public function getTreeOptions(): array
    {
        return Post::root()->get()->toArray();
    }
}

在您的资源的静态getPages()方法中注册创建的页面

public static function getPages(): array
{
    return [
        // ...
        'tree' => Pages\TreePosts::route('/tree'),
    ];
}

将新创建的页面的链接添加到您的面板导航中

use App\Filament\Resources\PostResource\Pages\TreePosts;
use Beholdr\FilamentTrilist\FilamentTrilistPlugin;
use Filament\Navigation\NavigationItem;

class AdminPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->navigationItems(TreePosts::getNavigationItems())
    }
}

treeview选项

您可以通过覆盖自定义页面类中的静态方法来设置一些树形选项

class TreeCategories extends TrilistPage
{
    public static function getFieldLabel(): string
    {
        return 'name';
    }
}
  • getFieldId():树项id字段名称
  • getFieldLabel():树项标签字段名称
  • getFieldChildren():树项子项字段名称
  • isAnimated():启用展开/折叠动画,默认:true
  • isSearchable():启用项目过滤,默认:false
  • getSearchPrompt():搜索输入占位符

自定义标签

如果您想自定义树项的标签,可以覆盖TrilistPagegetLabelHook()方法。

示例

例如,如果您希望模型具有一个description字段,并希望在项目名称下方输出描述。您的模型的所有其他属性都在item.data属性下,所以描述将在item.data.description

public function getLabelHook(): string
{
    if (! $editRoute = $this->getEditRoute()) {
        return 'undefined';
    }

    $template = route($editRoute, ['record' => '#ID#'], false);

    return <<<JS
    (item) => `<a href='\${'{$template}'.replace('#ID#', item.id)}'>\${item.label}</a> \${item.data?.description ? '<div style=\'font-size: 0.85em; opacity: 0.5\'>' + item.data.description + '</div>' : ''}`
    JS;
}

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可证文件