logo

定义动作

Nova 操作允许你在一个或多个 Eloquent 模型上执行自定义任务。例如,你可以编写一个操作,向用户发送一封电子邮件,其中包含他们请求的账户数据。或者,你可以编写一个操作,将一组记录传输给另一个用户。

在资源定义中附加了一个操作后,就可以从资源的索引页或详情页启动该操作:

Action

如果在资源的表格行中启用了显示操作,你也可以通过资源索引页从资源的操作下拉菜单中启动该操作。这些操作称为 内联操作」:

Inline Action

概览

Nova 操作可使用 nova:action Artisan 命令生成。默认情况下,所有动作都放在 app/Nova/Actions 目录中:

bash
php artisan nova:action EmailAccountProfile

你可以通过传递 --destructive 选项来生成 破坏性动作

bash
php artisan nova:action DeleteUserData --destructive

要了解如何定义 Nova 操作,让我们来看一个示例。在这个示例中,我们将定义一个向用户或用户组发送电子邮件的操作:

php
<?php

namespace App\Nova\Actions;

use App\Models\AccountData;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Collection;
use Laravel\Nova\Actions\Action;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Fields\ActionFields;

class EmailAccountProfile extends Action
{
    use InteractsWithQueue, Queueable;

    /**
    * 对给定的模型执行操作。
    *
    * @param  \Laravel\Nova\Fields\ActionFields  $fields
    * @param  \Illuminate\Support\Collection  $models
    * @return mixed
    */
    public function handle(ActionFields $fields, Collection $models)
    {
        foreach ($models as $model) {
            (new AccountData($model))->send();
        }
    }

    /**
    * 获取操作中可用的字段。
    *
    * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
    * @return array
    */
    public function fields(NovaRequest $request)
    {
        return [];
    }
}

动作最重要的方法是 handle 方法。handle 方法接收附加到操作的任何字段的值,以及所选模型的集合。即使只对一个模型执行操作,handle 方法 总是 会接收模型的集合 Collection

handle 方法中,你可以执行完成操作所需的任何任务。您可以更新数据库记录、发送电子邮件、调用其他服务等。一切尽在掌握!

动作标题

通常,Nova 会利用动作的类名来确定应在动作选择菜单中显示的动作的可显示名称。如果想更改动作的可显示名称,可以在动作类上定义一个 name 属性:

php
/**
 * 可显示的操作名称。
 *
 * @var string
 */
public $name = 'Action Title';

破坏性动作

你可以通过定义一个扩展了 Laravel\Nova\Actions\DestructiveAction 的动作类来指定一个动作为破坏性或危险动作。这将把动作的确认按钮颜色改为红色:

Destructive Action

破坏性行动和策略

在有关联授权策略的资源中添加破坏性操作时,策略的 delete 方法必须返回 true,该操作才能运行。

动作回调

队列动作回调

如果你的操作是队列操作,则不应使用 Action::then 方法。要在使用队列动作时实现类似功能,应利用 Nova 的 动作批量回调

当针对多个资源运行一个操作时,你可能希望在完成针对所有资源的操作后执行一些代码。例如,你可能希望生成一份报告,详细说明所选资源的所有更改。为此,你可以在 注册动作 时调用then方法。

then 方法接受一个闭包,该闭包将在针对所有选定资源的操作执行完毕后被调用。闭包将接收一个扁平化的 Laravel 集合,其中包含操作返回的值。

例如,请注意以下操作的 handle 方法会返回收到的 $models

php
public function handle(ActionFields $fields, Collection $models)
{
    foreach ($models as $model) {
        (new AccountData($model))->send();
    }

    return $models;
}

在资源上注册此操作时,我们可以使用 then 回调来访问返回的模型,并在操作执行完毕后与它们进行交互:

php
public function actions(NovaRequest $request)
{
    return [
        (new Actions\EmailAccountProfile)->then(function ($models) {
            $models->each(function ($model) {
                //
            });
        }),
    ];
}

动作字段

有时,你可能希望在执行动作之前从用户那里收集更多信息。因此,Nova 允许你将 Nova 支持的大多数 字段 直接附加到动作。启动操作时,Nova 会提示用户为字段提供输入:

Action Field

要在动作中添加字段,请将该字段添加到操作的 fields 方法返回的字段数组中:

php
use Laravel\Nova\Fields\Text;

/**
 * 获取操作中可用的字段。
 *
 * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
 * @return array
 */
public function fields(NovaRequest $request)
{
    return [
        Text::make('Subject'),
    ];
}

最后,在动作的 handle 方法中,你可以使用所提供的 ActionFields 实例上的动态访问器访问字段:

php
/**
 * 对给定的模型执行动作。
 *
 * @param  \Laravel\Nova\Fields\ActionFields  $fields
 * @param  \Illuminate\Support\Collection  $models
 * @return mixed
 */
public function handle(ActionFields $fields, Collection $models)
{
    foreach ($models as $model) {
        (new AccountData($model))->send($fields->subject);
    }
}

动作字段默认值

你可以使用 default 方法为动作字段设置默认值:

php
Text::make('Subject')->default(function ($request) {
    return 'Test: Subject';
}),

动作响应

通常情况下,当一个操作被执行时,Nova UI 会显示一条通用的「success」消息。不过,你可以使用 Action 类提供的各种方法自由定制此响应。要显示自定义的「success」消息,你可以从你的 handle 方法中调用 Action::message 方法:

php
/**
 * 对给定的模型执行动作。
 *
 * @param  \Laravel\Nova\Fields\ActionFields  $fields
 * @param  \Illuminate\Support\Collection  $models
 * @return mixed
 */
public function handle(ActionFields $fields, Collection $models)
{
    // ...

    return Action::message('It worked!');
}

要返回红色的「danger」信息,可以调用 Action::danger 方法:

php
return Action::danger('Something went wrong!');

重定向响应

要在执行动作后将用户重定向到一个全新的位置,可以使用 Action::redirect 方法:

php
return Action::redirect('https://example.com');

要将用户重定向到 Nova 中的另一个位置,你可以使用 Action::visit 方法:

php
return Action::visit('/resources/posts/new', [
    'viaResource' => 'users',
    'viaResourceId' => 1,
    'viaRelationship' => 'posts'
]);

要将用户重定向到新浏览器标签页中的新位置,可以使用 Action::openInNewTab 方法:

php
return Action::openInNewTab('https://example.com');

下载响应

要在执行动作后启动文件下载,可以使用 Action::download 方法。download 方法的第一个参数是要下载文件的 URL,第二个参数是所需的文件名:

php
return Action::download('https://example.com/invoice.pdf', 'Invoice.pdf');

自定义模态响应

除了在操作执行前和执行过程中提供的自定义选项外,Nova 还支持向用户显示自定义模式响应的功能。这样,你就可以向用户提供额外的上下文或后续操作,并根据你的使用情况进行定制。

例如,假设你定义了一个名为 GenerateApiToken 的操作,该操作可创建用于 REST API 的唯一令牌。使用自定义动作响应模态,你可以向运行该动作的用户显示一个模态,允许他们将新生成的 API 标记复制到剪贴板。

使用 nova:asset Artisan 命令,你可以 生成自定义资源,并在 Nova 的 Vue 实例中注册自定义模态:

js
import ApiTokenCopier from "./components/ApiTokenCopier";

Nova.booting(app => {
  app.component("api-token-copier", ApiTokenCopier);
});

接下来,你可以在动作的 handle 方法中使用 modal 方法,该方法将指示 Nova 在运行动作后显示模态,并将 Vue 组件的名称和你指定的任何其他数据传递给该组件。这些数据将作为道具提供给自定义模态的 Vue 组件:

php
public function handle(ActionFields $fields, Collection $models)
{
    if ($models->count() > 1) {
        return Action::danger('Please run this on only one user resource.');
    }

    $models->first()->update(['api_token' => $token = Str::random(32)]);

    return Action::modal('api-token-copier', [
        'message' => 'The API token was generated!',
        'token' => $token,
    ]);
}

队列执行动作

有时,你的操作可能需要延迟完成。因此,Nova 可以轻松地将你的动作 队列执行。要指示 Nova 将一个动作推送到队列而不是同步运行,请使用 ShouldQueue 接口标记该动作:

php
<?php

namespace App\Nova\Actions;

use App\Models\AccountData;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Collection;
use Laravel\Nova\Actions\Action;
use Laravel\Nova\Contacts\BatchableAction;
use Laravel\Nova\Fields\ActionFields;

class EmailAccountProfile extends Action implements ShouldQueue
{
    use InteractsWithQueue, Queueable;

    // ...
}

你可以在执行 nova:action Artisan 命令时提供 --queued 选项,快速创建一个支持队列的 Nova 动作:

bash
php artisan nova:action EmailAccountProfile --queued

使用队列操作时,不要忘记为应用程序配置并启动 队列。否则,你的操作将无法得到处理。

队列行动文件字段

目前,Nova 不支持将 File 字段附加到队列动作。如果需要将 File 字段附加到一个操作,该操作必须同步运行。

自定义连接和队列

你可以通过在操作的构造函数中设置 $connection$queue 属性,自定义队列连接和队列名称:

php
/**
 * 创建一个新的操作实例。
 *
 * @return void
 */
public function __construct()
{
    $this->connection = 'redis';
    $this->queue = 'emails';
}

批量执行

你还可以通过使用 Laravel\Nova\Contracts\BatchableAction 接口标记动作,指示 Nova 将动作批量 batch 队列。此外,动作应使用 Illuminate\Bus\Batchable 特性。

当一个动作可批处理时,应定义一个 withBatch 方法,负责配置该动作的 批处理回调。这样,你就可以定义在针对多个选定资源的整批操作执行完毕后运行的代码。事实上,您甚至可以访问执行批处理操作时所选择的所有资源的模型 ID:

php
<?php

namespace App\Nova\Actions;

use App\Models\AccountData;
use Illuminate\Bus\Batch;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\PendingBatch;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Collection;
use Laravel\Nova\Actions\Action;
use Laravel\Nova\Contacts\BatchableAction;
use Laravel\Nova\Fields\ActionFields;
use Throwable;

class EmailAccountProfile extends Action implements BatchableAction, ShouldQueue
{
    use Batchable, InteractsWithQueue, Queueable;

    /**
     * 准备执行给定的批次。
     *
     * @param  \Laravel\Nova\Fields\ActionFields  $fields
     * @param  \Illuminate\Bus\PendingBatch  $batch
     * @return void
     */
    public function withBatch(ActionFields $fields, PendingBatch $batch)
    {
        $batch->then(function (Batch $batch) {
            // 所有工作顺利完成...

            $selectedModels = $batch->resourceIds;
        })->catch(function (Batch $batch, Throwable $e) {
            // 检测到第一个批处理作业失败...
        })->finally(function (Batch $batch) {
            // 批次已执行完毕...
        });
    }
}

动作日志

查看针对特定资源运行的操作日志通常非常有用。此外,在队列动作时,了解队列动作的实际执行时间也很重要。值得庆幸的是,Nova 通过将 Laravel\Nova\Actions\Actionable 特质附加到资源对应的 Eloquent 模型,可以轻松地将操作日志添加到资源中。

例如,我们可以将 Laravel\Nova\Actions\Actionable 特性附加到 User Eloquent 模型上:

php
<?php

namespace App;

use Laravel\Nova\Actions\Actionable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Actionable, Notifiable;

    // ...
}

特性附加到模型后,Nova 会自动开始在资源详情页底部显示操作日志:

Action Log

禁用操作日志

如果不想在操作日志中记录操作,可在操作类中添加 withoutActionEvents 属性,禁用此行为:

php
/**
 * 禁用此动作的操作日志事件。
 *
 * @var bool
 */
public $withoutActionEvents = true;

或者,使用 withoutActionEvents 方法,当某个操作附加到某个资源时,可以禁用该操作的操作日志。禁用动作日志通常对经常同时对数千个资源执行的动作特别有用,因为这样可以避免数千次缓慢、连续的动作日志数据库插入:

php
/**
 * 获取资源的可用操作。
 *
 * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
 * @return array
 */
public function actions(NovaRequest $request)
{
    return [
        (new SomeAction)->withoutActionEvents()
    ];
}

队列行动状态

当队列动作正在运行时,你可以更新该动作的「status」,以显示通过其模型集合传递给该动作的任何模型。例如,你可以使用该操作的 markAsFinished 方法来表示该操作已完成对特定模型的处理:

php
/**
 * 对给定的模型执行操作。
 *
 * @param  \Laravel\Nova\Fields\ActionFields  $fields
 * @param  \Illuminate\Support\Collection  $models
 * @return mixed
 */
public function handle(ActionFields $fields, Collection $models)
{
    foreach ($models as $model) {
        (new AccountData($model))->send($fields->subject);

        $this->markAsFinished($model);
    }
}

或者,如果你想指出某个给定模型的操作「failed」,可以使用 markAsFailed 方法:

php
/**
 * 对给定的模型执行操作。
 *
 * @param  \Laravel\Nova\Fields\ActionFields  $fields
 * @param  \Illuminate\Support\Collection  $models
 * @return mixed
 */
public function handle(ActionFields $fields, Collection $models)
{
    foreach ($models as $model) {
        try {
            (new AccountData($model))->send($fields->subject);
        } catch (Exception $e) {
            $this->markAsFailed($model, $e);
        }
    }
}

自定义动作模态

默认情况下,操作运行前会要求用户确认。你可以自定义确认信息、确认按钮和取消按钮,以便在运行操作前为用户提供更多上下文信息。这可以通过在定义动作时调用 confirmTextconfirmButtonTextcancelButtonText 方法来实现:

php
/**
 * 获取资源的可用操作。
 *
 * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
 * @return array
 */
public function actions(NovaRequest $request)
{
    return [
        (new Actions\ActivateUser)
            ->confirmText('Are you sure you want to activate this user?')
            ->confirmButtonText('Activate')
            ->cancelButtonText("Don't activate"),
    ];
}