think.studio / nova-flexible-content
Laravel Nova的灵活内容与重复字段。
Requires
- php: ^8.1
- laravel/framework: ^9.0|^10.0
- laravel/nova: ^4.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.16
- guzzlehttp/guzzle: ^7.7
- orchestra/testbench: ^8.5
- phpunit/phpunit: ^10.1
- psalm/plugin-laravel: ^2.8
- think.studio/nova-video-field: ^1.0
- vimeo/psalm: ^5.11
README
这是一个从优秀的包whitecube/nova-flexible-content
分叉而来,但重构时没有简单的方法将功能合并到主包中。
一个简单且完整的Laravel Nova灵活字段,非常适合重复和灵活的字段组。
安装
composer require think.studio/nova-flexible-content # optional publish configs php artisan vendor:publish --provider="NovaFlexibleContent\ServiceProvider" --tag="config"
使用方法
灵活字段允许轻松管理可重复和可排序的字段组。该包对这些组内允许使用的字段没有任何限制。这意味着您可以使用所有Laravel Nova字段类型,也可以使用任何社区制作的字段。
布局
布局表示一组可以在灵活字段内部重复的字段。您可以添加任意数量的不同布局。如果只定义了一个布局,字段将像简单的重复器一样表现。通过添加更多布局,您将获得灵活内容。
布局定义
namespace App\Nova\Flexible\Layouts; use Laravel\Nova\Fields\Text; use Laravel\Nova\Fields\Markdown; use NovaFlexibleContent\Layouts\Layout; class VideoLayout extends Layout { // Optionally you limit count of this layout in flexible groups // protected int $limit = 3; protected function linksPreset() { return Preset::withLayouts([ LinkLayout::class, ]); } /** * Get the fields displayed by the layout. */ public function fields(): array { return [ Text::make('Title', 'title') ->help('Optional'), FileForFlexible::make('Video', 'video') ->prunable() ->acceptedTypes('video/mp4') ->deletable() ->help('Aspect ratio 16:9. Please do not upload a large file.'), ImageForFlexible::make('Poster', 'poster') ->prunable() ->rules(['max:' . 1024 * 10]) ->deletable() ->help('Aspect ratio 16:9.'), // Recursive flexible \NovaFlexibleContent\Flexible::make('Links', 'links') ->preset($this->linksPreset()) ->layoutsMenuButton('Add link'), ]; } }
然后使用此布局
\NovaFlexibleContent\Flexible::make('Content') ->useLayout(\App\Nova\Flexible\Layouts\VideoLayout::class) ->useLayout(\App\Nova\Flexible\Layouts\FooLayout::class) ->useLayout(\App\Nova\Flexible\Layouts\BarLayout::class);
自定义显示
您可以通过调用方法来更改显示
\NovaFlexibleContent\Flexible::make('Content') ->fullWidth() ->useSearchableLayoutsMenu() ->layoutsMenuButton('Add Video') ->limit(3) ->withGroupRemovingConfirmation('Are you sure?', 'Yes :)', 'Ahh, no');
值解析器
默认情况下,字段利用模型表的JSON列
。在某些情况下,JSON属性并不是最佳选择。例如,您可能希望将值存储在另一个表中(这意味着您将使用灵活内容字段而不是传统的BelongsToMany或HasMany字段)。不用担心,我们已为您解决问题!
解析器定义
每个解析器必须实现NovaFlexibleContent\Value\Resolver
契约,因此至少具有两个方法:set
和get
。
namespace App\Nova\Flexible\Resolvers; use NovaFlexibleContent\Layouts\Collections\GroupsCollection; use NovaFlexibleContent\Layouts\Collections\LayoutsCollection; use NovaFlexibleContent\Value\Resolver; class WysiwygPageResolver implements Resolver { public function get(mixed $resource, string $attribute, LayoutsCollection $groups): GroupsCollection { return new GroupsCollection(); } public function set(mixed $resource, string $attribute, GroupsCollection $groups): string { return ''; } }
解析字段
用于解析字段内容的get
方法。它负责从某处检索内容并返回布局实例(组)的集合。例如,我们可能希望从blocks
表中检索值并将其转换为布局实例。
public function get(mixed $resource, string $attribute, LayoutsCollection $groups): GroupsCollection { $blocks = $resource->blocks()->orderBy('order')->get(); return $blocks->map( fn($block) => $layouts->find($block->name) ?->duplicate($block->id, ['value' => $block->value]); )->filter(); }
填充字段
负责保存灵活内容的set
方法。在我们的示例中,它应在blocks
表中存储数据。
public function set(mixed $resource, string $attribute, GroupsCollection $groups): string { if($resource instanceof \Illuminate\Database\Eloquent\Model) { $resource::saved(function ($model) use ($groups) { // This is a quick & dirty example, syncing the models is probably a better idea. $model->blocks()->delete(); $model->blocks() ->createMany($groups->map(function($group, $index) { return [ 'name' => $group->name(), 'value' => $group->toArray(), 'order' => $index ]; })); }); } return ''; }
预设
除了可重用的布局类之外,您还可以为您的灵活字段创建Preset
类。这允许您在需要的地方重复使用整个灵活字段。它们还使您的灵活字段更易于动态化,例如,如果您想有条件地添加布局。最重要的是,它们还有助于清理您的Nova资源类,如果您的灵活字段有很多useLayout
定义。
预设定义
namespace App\Nova\Flexible\Presets; use NovaFlexibleContent\Layouts\Preset; class WysiwygPagePreset extends Preset { /** * @var array */ protected array $usedLayouts = [ \App\Nova\Flexible\Layouts\SimpleWysiwygLayout::class, \App\Nova\Flexible\Layouts\FooLayout::class, ]; public function handle(\NovaFlexibleContent\Flexible $field) { parent::handle($field); $field->layoutsMenuButton('Add new block') ->setResolver(\App\Nova\Flexible\Resolvers\WysiwygPageResolver::class) ->help('Example help.'); } }
\NovaFlexibleContent\Flexible::make('Content') ->preset(\App\Nova\Flexible\Presets\WysiwygPagePreset::class);
显示灵活内容
字段将值存储为单个JSON字符串,这意味着在您的应用程序中使用之前需要解析此字符串。
namespace App; use Illuminate\Database\Eloquent\Model; use NovaFlexibleContent\Concerns\HasFlexible; class Post extends Model { use HasFlexible; // Collect basic `Layout` instances public function getCollectedFlexibleContentAttribute() { return $this->flexible('flexible-content'); } // Cast to specified classes public function getCastedFlexibleContentAttribute() { return $this->flexible('flexible-content', [ 'wysiwyg' => \App\Nova\Flexible\Layouts\WysiwygLayout::class, 'video' => \App\Nova\Flexible\Layouts\VideoLayout::class, ]); } }
IDE辅助工具
为您的应用程序中的灵活布局创建IDE辅助文件。
php artisan nova-flexible-content:ide-helper:layouts
# or
php artisan nova-flexible-content:ide-helper:layouts --filename custom-file.php