guava / calendar
为 Filament PHP 添加对 vkurko/calendar 的支持。
Requires
- php: ^8.1|^8.2
- filament/filament: ^3.2
- illuminate/contracts: ^10.0|^11.0
- spatie/laravel-package-tools: ^1.14.0
Requires (Dev)
- laravel/pint: ^1.0
- nunomaduro/collision: ^7.8|^8.0
- nunomaduro/larastan: ^2.0.1
- orchestra/testbench: ^8.8|^9.0
- pestphp/pest: ^2.0
- pestphp/pest-plugin-arch: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-phpunit: ^1.0
README
为 Filament PHP 添加对 vkurko/calendar 的支持。
此包为 FilamentPHP 控板添加了对 vkurko/calendar(FullCalendar 的免费开源替代品)的支持。
展示
Screen.Recording.2024-07-15.at.9.31.47.mp4
支持我们
您的支持是我们插件不断进步的关键。我们感谢每一位至今为止为我们的旅程做出贡献的用户。
虽然我们的插件对所有用户都免费开放,但如果您正在将其用于商业目的,并认为它为您的事业增添了显著价值,我们恳请您考虑通过 GitHub Sponsors 支持我们。这种赞助将帮助我们进行持续的开发和维护,以保持我们的插件强大和更新。您提供的任何金额都将大大帮助我们实现目标。加入我们,让这个插件变得更好,推动进一步的创新。
安装
您可以通过 composer 安装此包。
composer require guava/calendar
确保使用以下命令发布包资源
php artisan filament:assets
确保您有一个自定义的 Filament 主题(阅读这里了解如何创建一个)并将以下内容添加到主题的 tailwind.config.js 中的 content
属性
{ content: [ //... './vendor/guava/calendar/resources/**/*.blade.php', ] }
这确保了 CSS 被正确构建。
使用
创建日历小部件
首先,您需要创建一个自定义小部件并扩展 CalendarWidget
类。请确保从生成的小部件类中删除 view
属性!
您可以使用 artisan 命令或简单地创建一个空类并扩展 CalendarWidget
php artisan make:filament-widget
小部件类应如下所示
use \Guava\Calendar\Widgets\CalendarWidget; class MyCalendarWidget extends CalendarWidget { }
将小部件像常规小部件一样添加到您喜欢的任何 Filament 页面,例如您的 Dashboard
。
自定义日历视图
默认情况下,我们显示 dayGridMonth
视图。您可以通过在小部件类上覆盖 calendarView
属性来自定义视图
protected string $calendarView = 'resourceTimeGridWeek';
所有可用的视图都在 日历文档 中列出。
添加事件
默认情况下,日历将是空的。要添加事件,只需覆盖 getEvents
方法
public function getEvents(array $fetchInfo = []): Collection | array { return [ // Chainable object-oriented variant Event::make() ->title('My first event') ->start(today()) ->end(today()), // Array variant ['title' => 'My second event', 'start' => today()->addDays(3), 'end' => today()->addDays(3)], // Eloquent model implementing the `Eventable` interface MyEvent::find(1), ]; }
创建事件
如示例所示,有多种创建事件的方式。至少需要一个包含 title
、start
和 end
属性的数组对象。
为了帮助您创建事件,我们提供了一个包含所有事件可能具有的属性的 Event
值对象。
这是可能的,因为 Event
类实现了 Eventable
接口,该接口返回数组对象。您可以将此接口添加到任何您希望将其视为事件的类中,例如您的 eloquent 模型。
以下是一个示例
使用 Eloquent 模型作为事件
class Foo extends Model implements Eventable { // ... public function toEvent(): Event|array { return Event::make($this) ->title($this->name) ->start($this->starts_at) ->end($this->ends_at); } }
请注意,在 make
方法中,模型被传递给 Event
构造函数。这设置了事件的 key
和 model
属性,以便它可以用来触发动作。
事件对象
事件对象接受所有可用选项,例如底层日历包,更多信息请在此处阅读。
以下是在事件对象中可用的方法列表
设置标题
设置在日历中渲染的事件的标题。
Event::make()->title('My event');
自定义开始/结束日期
设置日历中事件的开始或结束日期(以及时间)。
Event::make() ->start(today()) ->end(today()->addDays(3));
使事件为全天事件
设置事件是否为全天事件。
Event::make()->allDay();
自定义背景/文本颜色
设置事件的背景颜色(默认为面板的主色)。
Event::make() ->backgroundColor('#ff0000') ->textColor('#ffffff');
自定义显示方式
默认情况下,事件以块
的形式渲染。这是当显示设置为auto
时,默认就是这样。您还可以将事件设置为以背景事件的形式渲染,这样就会填充整个日期单元格。要做到这一点,您可以在事件上设置display
为background
但这并不总是有效,它仅在全天事件和特定视图中有效。如果background
事件不受支持,则事件将完全无法渲染。
Event::make() ->display('background') // or 'auto' ->displayAuto() // short-hand for ->display('auto') ->displayBackground(); // short-hand for ->display('background')
设置点击时的动作
这设置当点击事件时应执行的动作。它可以是你定义在widget中的任何filament动作的名称,如edit
或view
。
默认情况下,所有CalendarWidget
类已包括view
和edit
动作。
Event::make()->action('edit');
设置模型和记录键
为了使用正确的记录安装动作,我们需要传递记录的模型类型和主键。
如果想要显示多种类型的事件并让每种类型以不同的方式渲染(请参阅自定义事件内容),则模型也是必需的。
$record = MyModel::find(1); // 1. variant Event::make($record); // 2. variant Event::make() ->model($record::class) ->key($record->getKey());
传递自定义数据
您可以将任何自定义数据传递给您希望的事件。
Event::make() ->extendedProp('foo', 'bar') // or ->extendedProps(['baz' => 'qux', 'quux' => 'corge']);
可用方法
刷新事件
如果您需要触发日历中事件的刷新,可以在widget上调用refreshRecords()
。
$this->refreshRecords();
刷新资源
如果您需要触发日历中资源的刷新,可以在widget上调用refreshResources()
。
$this->refreshResources();
设置选项
要更改运行时中的任何日历选项,您可以使用widget上的setOption()
方法。
例如,要使用编程方式更改日期,可以使用
$this->setOption('date', today()->addDay()->toIso8601String());
自定义事件内容
默认情况下,我们使用来自日历包的默认视图。然而,您可以通过在您的日历widget类上覆盖getEventContent
方法来使用自己的。
为了保持性能,blade视图在服务器上渲染一次
,然后为每个事件重复使用。因此,您不能
通过Blade或Laravel从服务器端访问事件数据,或进行任何服务器端操作。
然而,每个事件都被一个alpine组件包裹,这暴露了您可以使用AlpineJS自由使用的事件数据。
如果您只有一种事件类型或以相同方式渲染的事件,您可以从getEventContent方法简单返回一个视图或HtmlString。
public function getEventContent(): null|string|array { // return a blade view return view('calendar.event'); // return a HtmlString return new HtmlString('<div>My event</div>'); }
示例calendar.event
视图blade文件
<div class="flex flex-col items-start">
<span x-text="event.title"></span>
<template x-for="user in event.extendedProps.users">
<span x-text="user.name"></span>
</template>
</div>
如果您想根据模型类型以不同的方式渲染事件,可以返回一个类似数组的对象
public function getEventContent(): null|string|array { return [ MyModel::class => view('calendar.my-model-event'), AnotherModel::class => view('calendar.another-model-event'), ]; }
自定义资源标签内容
默认情况下,我们使用来自日历包的默认视图。然而,您可以通过在您的日历widget类上覆盖getResourceLabelContent
方法来使用自己的。
public function getResourceLabelContent(): null|string|array { // return a blade view return view('calendar.resource'); // return a HtmlString return new HtmlString('<div>My resource</div>'); }
自定义表单模式
当事件触发动作(如查看或编辑动作)时,会安装一个包含表单的模态框。
您可以通过覆盖widget类中的getSchema
方法来自定义表单模式。
public function getSchema(?string $model = null): ?array { // If you only work with one model type, you can ignore the $model parameter and simply return a schema return [ TextInput::make('title') ]; // If you have multiple model types on your calendar, you can return different schemas based on the $model property return match($model) { Foo::class => [ TextInput::make('name'), ], Bar::class => [ TextInput::make('title'), TextArea::make('description'), ], } }
资源
资源视图(名称以 resource
开头)允许您将事件分组到资源中(如房间、项目等)。
要使用资源视图,您需要创建资源并将您的活动分配给这些资源。
创建资源
要创建资源,您需要覆盖您的日历小部件类上的 getResources
方法。就像事件一样,您可以选择三种选项来创建资源
public function getResources(): Collection|array { return [ // Chainable object-oriented variant Resource::make('foo') ->title('Room 1'), // Array variant ['id' => 'bar', 'title' => 'Room 2'], // Eloquent model implementing the `Resourceable` interface MyRoom::find(1), ]; }
处理事件
默认情况下,日历是一个只读的事件集合。您可以通过配置各种事件来启用更多功能,如下所述。
事件点击事件
当在日历中点击事件时,会触发事件点击事件。默认情况下,点击事件挂载 view
操作。
要监听点击事件,只需覆盖 eventClickEnabled
属性
protected bool $eventClickEnabled = true;
您可以通过覆盖小部件的 defaultEventClickAction
属性来设置默认点击操作。这只需要是您可以自由定义在小部件中的操作的名称,就像常规 Filament 操作一样
protected ?string $defaultEventClickAction = 'edit';
就是这样!只要通过您的模型策略检查,当您点击事件时,就会挂载编辑模态。
如果您想完全自己处理事件点击逻辑,可以覆盖 onEventClick
方法
public function onEventClick(array $info = []): void { // do something on click // $info contains the event data: // $info['event'] - the event object // $info['view'] - the view object }
事件调整大小事件
当事件在事件的结束边缘调整大小时,会触发调整大小事件。这允许您快速修改事件的持续时间。
要监听调整大小事件,只需覆盖 eventResizeEnabled
属性
protected bool $eventResizeEnabled = true;
除了解析与事件相关的(事件)记录外,没有默认操作,您需要自己实现逻辑。为此,覆盖 onEventResize
方法
public function onEventResize(array $info = []): bool { // Don't forget to call the parent method to resolve the event record parent::onEventResize($info); // Validate the data // Update the record ($this->getEventRecord()) // $info contains the event data: // $info['event'] - the event object // $info['oldEvent'] - the event object before resizing // $info['endDelta'] - the difference in time between the old and new event // Return true if the event was resized successfully // Return false if the event was not resized and should be reverted on the client-side }
事件拖放事件
当事件被拖放到日历中的不同槽位时,会触发拖放事件。这允许您快速移动事件的开始(和结束)日期。
要监听拖放事件,只需覆盖 eventDragEnabled
属性
protected bool $eventDragEnabled = true;
除了解析与事件相关的(事件)记录外,没有默认操作,您需要自己实现逻辑。为此,覆盖 onEventDrop
方法
public function onEventDrop(array $info = []): bool { // Don't forget to call the parent method to resolve the event record parent::onEventDrop($info); // Validate the data // Update the record ($this->getEventRecord()) // $info contains the event data: // $info['event'] - the event object // $info['oldEvent'] - the event object before resizing // $info['oldResource'] - the old resource object // $info['newResource'] - the new resource object // $info['delta'] - the duration object representing the amount of time the event was moved by // $info['view'] - the view object // Return true if the event was moved successfully // Return false if the event was not moved and should be reverted on the client-side }
日期点击事件
当在日历中点击日期单元格时,会触发日期点击事件。
要监听日期点击事件,只需覆盖 dateClickEnabled
属性
protected bool $dateClickEnabled = true;
默认情况下,日期点击不会发生任何事情。您可以使用 日期点击上下文菜单功能
(更多信息见下文 上下文菜单
部分 此处)或通过覆盖 onDateClick
方法实现自己的逻辑
public function onDateClick(array $info = []): bool { // Validate the data // $info contains the event data: // $info['date'] - the date clicked on // $info['dateStr'] - the date clicked on as a UTC string // $info['allDay'] - whether the date is an all-day slot // $info['view'] - the view object // $info['resource'] - the resource object }
日期选择事件
当在日历中选择日期范围时,会触发日期选择事件。
要监听日期选择事件,只需覆盖 dateSelectEnabled
属性
protected bool $dateSelectEnabled = true;
默认情况下,日期选择不会发生任何事情。您可以使用 日期选择上下文菜单功能
(更多信息见下文 上下文菜单
部分 此处)或通过覆盖 onDateSelect
方法实现自己的逻辑
public function onDateSelect(array $info = []): bool { // Validate the data // $info contains the event data: // $info['start'] - the start date of the range // $info['startStr'] - the start date as an UTC string // $info['end'] - the end date of the range // $info['endStr'] - the end date as an UTC string // $info['allDay'] - whether the date is an all-day slot // $info['view'] - the view object // $info['resource'] - the resource object }
无事件点击事件
无事件点击事件仅适用于 list
视图,当用户点击 无事件
单元格时触发。默认情况下,此事件不会做任何事情,您需要自己实现逻辑。
要监听无事件点击事件,只需覆盖 noEventsClickEnabled
属性
protected bool $noEventsClickEnabled = true;
要处理无事件点击逻辑,覆盖 onNoEventsClick
方法
public function onNoEventsClick(array $info = []): void { // do something on click // $info contains the event data: // $info['view'] - the view object }
上下文菜单
您可以选择性地为您的日历添加上下文菜单,该菜单允许您通过点击日期单元格或通过拖动选择日期/时间范围来创建事件。
您可以在多个位置使用上下文菜单。
context_menu_preview.mp4
context_menu_preview_2.mp4
日期点击上下文菜单
当用户在日历中点击日期单元格时,将触发此上下文菜单。
要启用上下文菜单,只需启用日期点击并实现 getDateClickContextMenuActions
方法即可
例如
protected bool $dateClickEnabled = true; public function getDateClickContextMenuActions(): array { CreateAction::make('foo') ->model(Foo::class) ->mountUsing(fn ($arguments, $form) => $form->fill([ 'starts_at' => data_get($arguments, 'dateStr'), 'ends_at' => data_get($arguments, 'dateStr'), ])), }
使用挂载函数用于将日历的参数填充到表单中。它包含vkurko/calendar在 select
和 dateClick
事件中提供的所有信息,但最重要的是
startStr
和endStr
用于范围选择dateStr
用于日期点击
日期选择上下文菜单
当用户在日历中选择日期范围时,将触发此上下文菜单。
要启用上下文菜单,只需启用日期选择并实现 getDateSelectContextMenuActions
方法
例如
protected bool $dateSelectEnabled = true; public function getDateSelectContextMenuActions(): array { CreateAction::make('foo') ->model(Foo::class) ->mountUsing(fn ($arguments, $form) => $form->fill([ 'starts_at' => data_get($arguments, 'startStr'), 'ends_at' => data_get($arguments, 'endStr'), ])), }
事件点击上下文菜单
当用户在日历中点击事件时,将触发此上下文菜单。
要启用上下文菜单,只需启用事件点击并实现 getEventClickContextMenuActions
方法
例如
protected bool $eventClickEnabled = true; public function getEventClickContextMenuActions(): array { return [ $this->viewAction(), $this->editAction(), $this->deleteAction(), ]; }
无事件点击上下文菜单
此上下文菜单仅在 list
视图中渲染,并在用户在无事件时点击 无事件
单元格时触发。
要启用上下文菜单,只需实现 getNoEventsClickContextMenuActions
方法。同时,请确保将 noEventsClickEnabled
属性设置为 true
。
public function getNoEventsClickContextMenuActions(): array { return [ CreateAction::make('foo') ->model(Foo::class) ]; }
no_events_context_menu.mp4
自定义
区域设置
默认情况下,日历将使用应用程序的区域设置。
底层日历包不支持将语言和区域/国家代码组合为区域设置,因此如 fr_CA
或 en_US
这样的区域设置将无效。
我们通过仅使用区域设置的第一部分语言来尝试解决这个问题。如果您在本地化方面遇到任何问题,可以使用 locale
属性手动覆盖日历的区域设置。
protected ?string $locale = 'en';
故障排除
上下文菜单操作不起作用
如果您遇到上下文菜单问题,无论是操作未正确挂载还是参数数组为空,请确保在整个小部件中操作名称是唯一的。如果有另一个具有相同名称的操作,它可能会代替您想要的操作。
记录与事件记录
当与资源小部件一起工作时,$record
是当前打开的资源记录的记录,而 $eventRecord
是日历事件的记录(在事件操作、上下文菜单等期间)。
授权
出于安全原因,操作使用Laravel的默认授权机制来检查用户是否有权执行操作。
这意味着您添加的操作可能不会工作(例如,在事件点击上的查看或编辑操作)。如果是这种情况,请为您的模型创建策略并在策略中添加必要的检查。
您还可以覆盖您的小部件类上的 authorize
方法并自行处理所有授权逻辑。
// $ability will contain the name of the action public function authorize($ability, $arguments = []);
安全措施
请记住,此包中的许多数据来自客户端JavaScript,可能会被篡改。始终在服务器端验证数据,并永远不要信任来自客户端的数据。
测试
composer test
变更日志
有关最近更改的更多信息,请参阅 变更日志。
贡献
有关详细信息,请参阅 贡献指南。
安全漏洞
有关如何报告安全漏洞的信息,请参阅 我们的安全策略。
鸣谢
- 卢卡斯·弗雷
- 所有贡献者
- Spatie - 我们的包骨架是Spatie的包骨架的一个修改版本,详情请访问Spatie的包骨架
- vkurko/calendar - FullCalendar的免费、开源替代品
- saade/filament-fullcalendar - 本包的主要灵感来源
许可证
MIT许可证(MIT)。有关更多信息,请参阅许可证文件。