支持 Laravel.io 的持续开发 →

使用 Livewire、Alpine.js 和 Tailwind UI 给 Laravel.io 添加通知

2020年6月29日 阅读时间约12分钟

设定场景

在进入这篇文章的正文之前,我想借此机会分享一下我是如何参与 Laravel.io 项目的。

去年六月,我有幸在伦敦举行的 Laravel Live UK 上发表演讲。在演讲者晚餐上,我坐在 Dries Vints 对面。自 2014 年 Laravel 第四版本的早期,我就开始使用 Laravel 了,因此我在他成为雇员之前就很清楚 Dries 为社区所做的一切。

我也知道 Dries 是 Laravel.io 的维护者,这是一个自从我开始与该框架互动以来就一直在使用的社区工具。我之前 阅读过一篇关于重构 UI 团队的一篇帖子,该论坛被用作改版案例研究,并询问他是否对有人实现设计结果感兴趣。

会议结束后,我又与Dries取得了联系,并且从那时起,我们就以那篇帖子为灵感,完全重新设计了应用。随后,我们做了大量的工作来解决旧的pull requests,提升框架和PHP版本,并最终确定了一个完全由社区项目组成的科技栈,包括Tailwind CSSAlpine.jsLaravelLivewire——现在被人亲昵地称为TALL栈

现在您已经了解了情况,让我们来谈谈如何使用上述工具构建我们最新功能的过程:通知。

灵感来源

应用有一个公共个人资料页面和为登录用户提供的仪表板页面。这两个页面在结构上非常相似,仪表板只是增加了一些额外的功能,例如编辑账户的能力。

向应用添加通知自2018年3月以来一直是一个GitHub问题。最明显的地方是将它们添加到现有的仪表板中。

然而,在开始这个项目之前,我想对现有的仪表板和个人资料页面做一些改进。它们完成了它们的功能,但我发现设计有点缺乏灵感,将通知融入现有设计将会有点困难。

现有个人资料页面 Current profile

现有仪表板页面 Current dashboard

我并不是一个设计师,因此当我遇到创作瓶颈时,我通常会在互联网上寻找灵感。

我一直很喜欢GitHub的个人资料页面,鉴于我们的用户都是开发者,并且我们已经在使用GitHub进行社交登录,我决定以这个格式作为我的参考点。

GitHub个人资料页面 Github profile

我最喜欢Tailwind的一点是它很容易在浏览器中进行原型设计。大约一个小时后,我将以下内容发给了Dries,我们以此作为起点。

建议的新布局 Proposed new layout

使用Alpine JS构建界面

我们已经确定了一个设计,现在是时候将其变为现实。首要任务是分页标签界面。

我们的思路是将现有的“最新帖子”和“最新回复”包含在它们自己的标签中,为我们在以后添加“通知”标签奠定基础,这样就可以解决我在现有布局中没有地方放置它们的问题。

幸运的是,Alpine让这一切变得非常简单。如果您不清楚,Alpine是一个将大多数行为转移到您的标记上的最小化JavaScript框架。这是一个非常清新的方法。

在Alpine中,您可以使用标记中的HTML元素上的x-data属性定义组件,并将其值设置为对象。在此对象内部,您可以定义组件的初始状态。此对象的所有属性都对组件内的任何元素都是可访问的。

<div x-data="{ tab: 'threads' }">
    …
</div>

在这里,我们定义了一个组件,并通过将tab属性设置为threads来初始化其状态。这将是我们最初的活动标签。

标签通过点击导航中的按钮来切换。Alpine允许我们从标记中直接监听浏览器事件。

<button @click="tab = 'replies'" :class="{ 'active': tab === 'replies' }">
    Latest Threads
</button>

注意:@clickx-on指令的简写。同样可以使用x-on:click="tab = replies"语法达到相同的效果。类似地,:classx-bind指令的简写。

代码段上发生了一些事情。

首先,我们使用@click指令在按钮点击时,将组件数据对象中的tab属性更改为replies

注意,没有必要使用如this.data.tabthis.props.tab这样的语法来访问属性,它作为变量直接暴露给组件。

此外,我们使用x-bind指令在tab属性的值为replies时动态设置元素的类名为active

现在我们可以通过和导航的交互来操作数据对象中的值,用它来确定应该显示哪个标签的内容。

在Alpine中,可以使用x-show指令实现。

<div x-show="tab === 'threads'">
    // Tab content here…
</div>

x-show将在传入它的JavaScript表达式评估为false时将元素的style属性设置为display: none

在上面的示例中,元素将在我们点击设置数据对象中tab属性为threads的按钮前隐藏。

就这样,我们有了标签页!

之前 Current profile

之后 New profile

通知

现在标签页已经设置好,就可以转移到通知上了。

实现这个功能使用了Laravel原生通知系统的数据库驱动

应用程序本身已经使用通知系统发送电子邮件通知,所以要添加数据库驱动功能就相对简单。

首先,需要发布通知表的迁移。

php artisan notifications:table

然后,需要更新从通知类的via方法返回的数组,以包括数据库频道。

public function via(User $user)
{
    return ['mail', 'database'];
}

最后,需要塑造应该存储在数据库中的数据。这通过从通知类的toArraytoDatabase方法返回数组来完成。Laravel将自动将其编码为JSON,并根据用户将其存储在数据库中。

public function toDatabase(User $user)
{
    return [
        'type' => 'new_reply',
        'reply' => $this->reply->id(),
        'replyable_id' => $this->reply->replyable_id,
        'replyable_type' => $this->reply->replyable_type,
        'replyable_subject' => $this->reply->replyAble()->replyAbleSubject(),
    ];
}

使用Livewire渲染通知

我们希望用户与通知的交互具有动态体验。特别是,我们希望在交互分页或标记通知为已读时避免页面刷新。此外,我们希望通知指示器实时更新。

正是Livewire进入舞台的时候。

当我决定使用Livewire处理这一问题时,我最担心的是分页。

然后我查阅了文档,我的顾虑不复存在。Livewire 自带了这项功能,无需额外配置。

首先,我们需要创建一个新的Livewire组件。

php artisan livewire:make notifications  

运行此命令后,Livewire将创建一个名为Notifications.php的新类,其中包含组件的逻辑。

在这个类中,需要定义一个render方法,该方法返回一个视图以及所有所需数据。

此外,作为奖励,视图文件也作为上述make命令的一部分创建 —— notifications.blade.php

public function render(): View
{
    return view('livewire.notifications', [
        'notifications' => Auth::user()->unreadNotifications()->paginate(10),
    ]);
}

在视图文件中,循环遍历通知集合,为每个通知创建一个新表格行,并在渲染分页链接之前显示。

<table class="min-w-full">
    <tbody>
        @foreach ($notifications as $notification)
            @includeIf("notifications.{$notification->data['type']}")
        @endforeach
    </tbody>
</table>

{{ $notifications->links() }}

需要注意的是,我们使用blade的@includeIf动态包含表格行,取决于通知类型。这样,在之后添加更多通知类型时,我们只需进行简单的修改。

现在我们有了分页的通知表格。唯一的问题是这个将表现得完全像一个标准的blade视图。点击分页链接仍将导致整个页面刷新。到目前为止,我们还没有充分利用Livewire的力量。

要使分页动态化使用Livewire,我们只需进行一个简单的更改。

class Notifications
{
    use WithPagination;
    …
}

这就完了!我们只需要使用 WithPagination 特性。

我对Livewire如何实现这一点很感兴趣,于是进行了一番源代码探寻,发现答案简洁明了。

WithPagination 特性给使用该特性的组件增加了一些方法,同时也将Laravel分页器使用的默认视图替换成了Livewire附带的一个视图(如果需要自定义,这是可以配置的)。这个视图包含了自己的操作,这些操作会调用特性中定义的方法。这些方法反过来操纵分页器,使组件重新渲染。

<li>
    <button wire:click="gotoPage({{ $page }})">
        {{ $page }}
    </button>
</li>

上面是从Livewire分页视图中抽取的一段代码。点击上面的按钮会调用由特性提供的组件的 goToPage 方法。

public function gotoPage($page)
{
    $this->paginator['page'] = $page;
}

分页工作后,接下来需要考虑的是交互式界面能否标记通知为已读的能力。

为此,我们界面上有一个用户可以点击的按钮。通常,这会被一个会带来不必要的页面重新加载的表单提交所取代。但这可以通过Livewire通过创建一个操作来防止。

<button wire:click="markAsRead('{{ $notification->id }}')">
    Mark as read
</button>

wire:click 属性会提示Livewire截获按钮点击并调用相应组件的 markAsRead 方法。

public function markAsRead(string $notificationId)
{
    $this->notificationId = $notificationId;

    $this->authorize(NotificationPolicy::MARK_AS_READ, $this->notification);

    $this->notification->markAsRead();

    $this->emit('NotificationMarkedAsRead', Auth::user()->unreadNotifications()->count());
}

你可能注意到,我们能够使用Laravel内置的 权限管理 来确定操作是否允许。如果用户没有权限执行操作,Livewire将返回403状态码。

标记通知为已读后,返回更新后的通知分页列表,界面会相应地动态更新。

要完成这个特性,还有最后一个环节要做的就是实现通知指示器。

这稍微有点复杂,但Livewire仍然为我们提供了支持。

问题在于通知指示器需要在界面中的多个部分使用,但又不属于组件本身。

对于这种情况,Livewire允许我们使用 事件 在组件之间进行通信。

在上面的代码片段中,我故意隐藏了紧接在返回语句之前的那一行。

$this->emit('NotificationMarkedAsRead', $unreadNotifications->total());

在这里,我们触发一个名为 NotificationMarkedAsRead 的事件,并为其提供来自分页实例的总未读通知数。

现在,我们可以定义一个新的Livewire组件,该组件可以监听并响应该事件。

class NotificationCount extends Component
{
    public $count;

    protected $listeners = [
        'NotificationMarkedAsRead' => 'updateCount',
    ];

    …

    public function updateCount(int $count): int
    {
        return $count;
    }
}

在组件内,定义了一个以事件名称为键、事件触发时应调用的方法为值的监听器数组。

这种语法非常熟悉,因为它几乎与Laravel的事件系统使用相同的语法。

updateCount 方法中,传递给监听器的参数 $count 直接返回给视图,并且会再次动态更新,无需页面重新加载。

功能完成后,下面是结果。

通知 Notifications mark one

但这个故事还没有结束...

Tailwind UI

在我提交拉取请求的同时,Tailwind UI 正式发布。Tailwind UI 是一组由Tailwind CSS的制作者以及Refactoring UI的作者 Adam Wathan 和 Steve Schoger 制作的精美设计的UI组件。

我和Dries已经在Tailwind UI发布时讨论过,我们将如何为Laravel.io利用它。

我们很快意识到,早期通道包含了实现通知功能所必需的所有组件,因此我们决定咬紧牙关,立即更换它们。

Dries已经完成了Tailwind UI的安装并更换了一些网站上的组件。

因此,需要做的事情就是直接从Tailwind UI界面复制相关组件,将它们粘贴到现有组件的位置,对颜色进行一些小的调整,并实现变量数据。

这是一个非常简单的流程。事实上,这可能只需要30分钟。然而,额外的光泽让所有差异。

下面是最终的结果。

Tailwind UI通知 通知标记二

摘要

构建这个功能非常有趣,我在这个过程中学到了很多,我认为最终结果看起来很好,并且按照我们的期望实现了这个任务。

我一直期待着构建这个功能,因为这对我们未来的一些大计划奠定了基础。

如果您想参与这个项目,我们将非常感激您的帮助,请随时与我们联系!

一如既往,如果您有任何问题或发现问题,请告知我。

最后更新 1年前。

driesvints, taftse, alejandrozepeda, wmandai, chistel, neil, euneuber, dk009dk 赞了这篇文章

8
喜欢这篇文章吗?让作者知道并为他们鼓掌!
joedixon (Joe Dixon) 软件开发者 @laravel,维护者 @laravelio

你可能还喜欢的其他文章

2024年3月11日

如何使用Larastan将您的Laravel应用程序从0到9

在使用Larastan之前,就可能在您的Laravel应用程序中发现错误,因为Larastan...

阅读文章
2024年7月19日

无需特性标准化API响应

我发现,大多数为API响应创建的库都是使用特性实现的,...

阅读文章
2024年7月17日

通过 Discord 通知在您的 Laravel 项目中收集反馈

如何在 Laravel 项目中创建一个反馈模块,并在收到消息通知时查看 Discord 通知...

阅读文章

我们非常感谢这些 惊人的公司 对我们的支持

您的标志在这里?

Laravel.io

问题解决、知识分享和社区建设的 Laravel 门户。

© 2024 Laravel.io - 版权所有。