guava/calendar

为 Filament PHP 添加对 vkurko/calendar 的支持。

资助包维护!
GuavaCZ

1.7.0 2024-09-22 07:21 UTC

This package is auto-updated.

Last update: 2024-09-22 07:22:12 UTC


README

calendar Banner

为 Filament PHP 添加对 vkurko/calendar 的支持。

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

此包为 FilamentPHP 控板添加了对 vkurko/calendar(FullCalendar 的免费开源替代品)的支持。

展示

Showcase 01 Showcase 02

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),
        ];
    }

创建事件

如示例所示,有多种创建事件的方式。至少需要一个包含 titlestartend 属性的数组对象。

为了帮助您创建事件,我们提供了一个包含所有事件可能具有的属性的 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 构造函数。这设置了事件的 keymodel 属性,以便它可以用来触发动作。

事件对象

事件对象接受所有可用选项,例如底层日历包,更多信息请在此处阅读。

以下是在事件对象中可用的方法列表

设置标题

设置在日历中渲染的事件的标题。

Event::make()->title('My event');

自定义开始/结束日期

设置日历中事件的开始或结束日期(以及时间)。

Event::make()
    ->start(today())
    ->end(today()->addDays(3));

使事件为全天事件

设置事件是否为全天事件。

Event::make()->allDay();

自定义背景/文本颜色

设置事件的背景颜色(默认为面板的主色)。

Event::make()
->backgroundColor('#ff0000')
->textColor('#ffffff');

自定义显示方式

默认情况下,事件以的形式渲染。这是当显示设置为auto时,默认就是这样。您还可以将事件设置为以背景事件的形式渲染,这样就会填充整个日期单元格。要做到这一点,您可以在事件上设置displaybackground

但这并不总是有效,它仅在全天事件和特定视图中有效。如果background事件不受支持,则事件将完全无法渲染。

Event::make()
->display('background') // or 'auto'
->displayAuto() // short-hand for ->display('auto')
->displayBackground(); // short-hand for ->display('background')

设置点击时的动作

这设置当点击事件时应执行的动作。它可以是你定义在widget中的任何filament动作的名称,如editview

默认情况下,所有CalendarWidget类已包括viewedit动作。

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 开头)允许您将事件分组到资源中(如房间、项目等)。

要使用资源视图,您需要创建资源并将您的活动分配给这些资源。

Resources Screenshot 01

创建资源

要创建资源,您需要覆盖您的日历小部件类上的 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在 selectdateClick 事件中提供的所有信息,但最重要的是

  • startStrendStr 用于范围选择
  • 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_CAen_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

变更日志

有关最近更改的更多信息,请参阅 变更日志

贡献

有关详细信息,请参阅 贡献指南

安全漏洞

有关如何报告安全漏洞的信息,请参阅 我们的安全策略

鸣谢

许可证

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