介绍

Orchid 中的屏幕是定义页面布局和行为的核心结构。每个屏幕由一个类表示,该类定义了 UI 组件、它们的属性以及它们之间的交互方式。

屏幕不直接处理数据获取;相反,它们专注于使用预定义的模板(布局)渲染 UI,并指定数据应如何显示。数据可以来自任何来源——无论是数据库、API 还是外部服务——而不会影响屏幕逻辑。

从核心上说,每个屏幕本质上是一个 HTML <form>,这简化了用户输入处理、数据提交和以标准化方式管理交互。这确保了可预测和一致的用户体验。

通过分离数据管理和 UI 渲染的关注点,Orchid 屏幕使构建和维护复杂的 Web 应用程序变得更容易。

创建屏幕

您可以通过运行以下命令创建一个新屏幕:

php artisan orchid:screen Idea

一个 Idea 文件将会在 app/Orchid/Screens 目录下创建,内容如下:

use Orchid\Screen\Screen;

class Idea extends Screen
{
    /**
     * 获取要在屏幕上显示的数据。
     */
    public function query() : array
    {
        return [];
    }

    /**
     * 屏幕的名称显示在标题中。
     */
    public function name(): ?string
    {
        return "Idea Screen";
    }

    /**
     * 屏幕的操作按钮。
     *
     * @return \Orchid\Screen\Action[]
     */
    public function commandBar() : array
    {
        return [];
    }

    /**
     * 屏幕的布局元素。
     *
     * @return \Orchid\Screen\Layout[]|string[]
     */
    public function layout() : array
    {
        return [];
    }
}

屏幕类包含几个方法,您可以用来定义屏幕的行为和外观。这些包括:

此示意图说明了 Orchid 中屏幕的工作原理。

  • query:此方法从数据库或其他来源加载数据。它应返回一个数据数组,这些数据将在屏幕的布局和视图中可用。

  • commandBar:此方法用于定义将在屏幕上显示的按钮和其他操作。

  • layout:此方法用于定义屏幕的结构和内容。它应返回一个布局对象数组,可用于在屏幕上显示数据、表单和其他元素。

要在应用程序中使用新屏幕,您需要在路由文件中注册它。

屏幕路由

在可以通过直接 URL 访问屏幕之前,必须在 routes/platform.php 路由文件中注册它。在此文件中注册的路由将通过 配置 中指定的中间件。

要注册屏幕,您可以使用 Route 类的 screen 方法。例如:

use App\Orchid\Screens\Idea;

Route::screen('/idea', Idea::class)->name('platform.idea');

这将为 Idea 屏幕注册一个新路由,可以通过 URL /idea 访问。

添加屏幕与通常的注册略有不同,例如 GET 请求。不是单个地址,而是注册了一整个组。为了清晰起见,您可以通过 Artisan 运行 route:list 命令:

Method   | URI                      | Name
---------+--------------------------+--------------
GET|POST | dashboard/idea/{method?} | platform.idea

如果您有多个屏幕,可以以相同的方式注册它们。例如:

use App\Orchid\Screens\Idea;
use App\Orchid\Screens\IdeaEdit;

Route::screen('/idea/edit', IdeaEdit::class)
    ->name('platform.idea.edit');

Route::screen('/idea', Idea::class)
    ->name('platform.idea');

需要注意的是,当注册多个路由时,Laravel 将选择第一个匹配的路由。

通过编写以下路由:

Route::screen('/idea', ...
Route::screen('/idea/edit',...

我们得到:

URI                           | Name
------------------------------+---------------------
dashboard/idea/{method?}      | platform.idea
dashboard/idea/edit/{method?} | platform.idea.edit

{method?} – 表示一个可选参数,可能会进一步传递。 因此,地址中的名称 “edit” 属于它。 结果将是重定向到 “dashboard/idea/”。为避免这种情况,请确保正确排序您的路由。

查询数据

屏幕的 query 方法用于从数据库或其他来源加载数据。 然后将这些数据作为数组传递给屏幕的布局和视图。

例如,您可以使用 query 方法来响应简单字符串:

public function query() : array
{
    return [
        'name'  => 'Alexandr',
    ];
}

您可以使用 AsSource trait 将 Eloquent 模型作为屏幕的数据源。这允许您轻松访问和操作屏幕查询方法中的模型数据。

要使用 AsSource trait,您需要在模型类中包含它:

namespace App;

use Illuminate\Database\Eloquent\Model;
use Orchid\Screen\AsSource;

class Order extends Model
{
    use AsSource;
}

然后,在屏幕的 query 方法中,您可以像这样访问模型的数据:

public function query() : array
{
    return [
        'order'  => Order::find(1),
        'orders' => Order::paginate(),
    ];
}

在此示例中,order 键将包含一个 Order 模型实例,而 orders 键将包含数据库中所有 Order 模型的分页列表。这些键可以在屏幕的布局和视图中使用,以向用户显示数据。

您还可以使用 Repository 包装器将数据数组传递给屏幕的布局和视图。例如:

use Orchid\Screen\Repository;    

public function query(): array
{
    return [
        'order' => new Repository([
            'product_id' => 'prod-100',
            'name'       => 'Desk',
            'price'      => 10.24,
            'created_at' => '01.01.2020',
        ]),
    ];
}

在此示例中,order 键将包含具有给定数据的想法数组。

query 方法是屏幕的重要组成部分,因为它用于加载将显示给用户的数据。请务必仔细考虑您需要加载的数据以及它将在布局和视图中如何使用。

自动完成公共属性

您可以在屏幕类中使用公共属性来存储和访问类中不同方法中的数据。当屏幕显示时,从 query 方法返回的数据将自动分配给同名的公共属性。

例如,考虑以下代码:

/**
 * @var string
 */
public $message;

public function query() : array
{
    return [
        'message'  => 'Hello World!',
    ];
}

public function name(): ?string
{
    return $this->message;
}

在此示例中,我们声明了一个名为 $message 的公共属性。当对屏幕进行 GET 请求时,将执行 query 方法,并将返回数组中 message 键的值分配给 message 属性。

然后,在 name 方法中,我们返回 $message 属性的值。这意味着当屏幕显示时,页面标题将包含 “Hello World!” 字样。

以这种方式使用公共属性是在屏幕类的不同方法之间传递数据的有用方法,特别是在构建具有多个布局和视图的复杂屏幕时非常有帮助。

屏幕操作

屏幕包含内置命令,允许用户执行各种操作。commandBar 方法负责定义这些控件。

有几种可用的操作类型:

按钮

按钮操作允许用户在点击时触发屏幕上的方法。例如:

use Orchid\Screen\Actions\Button;
use Orchid\Support\Facades\Toast;

public function commandBar() : array
{
    return [
        Button::make('Go print')->method('print'),
    ];
}

public function print(): void
{
   Toast::warning('Hello, world! This is a toast message.');
}

在此示例中,当点击打印按钮时,将调用屏幕的 print 方法。 用户在屏幕上看到的所有数据将在请求中可用。

链接

链接操作允许用户在点击时导航到不同的 URL。例如:

use Orchid\Screen\Actions\Link;

public function commandBar() : array
{
    return [
        Link::make('External reference')
                ->href('http://orchid.software'),
    ];
}

模态切换

模态切换操作允许用户在点击时打开模态窗口。例如:

use Orchid\Screen\Actions\ModalToggle;

public function commandBar() : array
{
    return [
        ModalToggle::make('Modal window')
            ->modal('CreateUserModal')
            ->method('save'),
    ];
}

重复操作

在软件开发中,通常需要在多个屏幕或类中执行类似的操作。例如,删除对象的操作可能需要在分页屏幕和详细信息屏幕上进行。与其在两个类中重复该方法,不如利用继承的概念更为高效。

PHP 提供了强大的继承功能,可以为实体创建一个基类,定义访问权限和方法。这些可以被特定屏幕继承,从而实现可重用性并减少冗余代码。这不仅提高了开发过程的效率,还使代码更易于维护和理解。

调用屏幕方法

在 Laravel Orchid 中的屏幕中工作时,所有 UI 操作都有一个对应的方法在调用时执行。要从 JavaScript 或 Blade 模板中显式调用所需的方法,必须向特定路由发出 POST 请求。必须使用 route('platform.screen.name', ['method' => 'hello']) 助手在属性中指定 method 属性。

例如,要调用 platform.screens.users 屏幕上的 hello 方法,可以使用以下代码:

<form action="{{ route('platform.screens.users', ['method' => 'hello']) }}"
      method="POST"
>
    @csrf
    <button type="submit">Say "Hello, World!"</button>
</form>

这将向 'platform.screens.users’ 屏幕发送一个 POST 请求,带有 hello 的方法属性,这将触发服务器端的相应方法。

您还可以将其与 UI 按钮一起使用:

use Orchid\Screen\Actions\Button;

Button::make('Say "Hello, World!"')
    ->action(route('platform.screens.users', [
        'method' => 'hello',
    ]));

屏幕布局

布局负责屏幕的外观,即数据将以何种形式显示。

逻辑和展示的分离是 Laravel Orchid 的设计原则之一。 展示的一个元素是 “Layouts”(布局),可以以各种变体显示。 如果尝试简要解释,这就是一个增强版的 view

在大多数情况下,我们使用相同类型的元素来构成页面,例如,想象一个显示名称、签名和个人资料头像的块:

<div class="d-sm-flex flex-row flex-wrap text-center text-sm-left align-items-center">
	<span class="thumb-sm avatar m-r-xs">
        <img src="/avatar/maria.jpg" class="bg-light" alt="Maria">
    </span>
    <div class="ml-sm-3 ml-md-0 ml-xl-3 mt-2 mt-sm-0 mt-md-2 mt-xl-0">
        <h6 class="mb-0">Maria</h6>
        <p class="text-muted mb-1">maria@exaple.com</p>
    </div>
</div>

一个简单的个人资料块显示可以出现在数十个页面上,如果它们被复制,那么维护它们的外观可能会花费大量时间。因此,各种重用选项正在被研究。这被称为组件方法,无论交付方式和责任级别如何,它在 Blade 和 React/Vue/Angular 中都被实践。

平台层正是由这样的组件组成,唯一的区别是需要操作类,创建这些类时您明确确定接受的参数 avatar 将插入到 <img> 标签中,而无需每次编辑源代码。

每个布局可以包含不同的布局,即嵌套。 例如,屏幕分为两列。左侧为填写字段,右侧为参考表和图表。 您可以想出自己的附件示例。

public function layout() : array
{
    return [
        Layout::columns([
            '左列' => [
                FirstRows::class,
            ],
            '右列' => [
                SecondRows::class,
            ],
        ]),
        
        // 模态窗口
        Layout::modal('Appointments', [
            ThirdRows::class,
        ]),
    ];
}

有时您会希望为不同的事物使用相同的布局。为了减少代码重复,您可以创建可配置的设计。 要向布局传递自定义参数,可以使用类构造函数来处理它们:

namespace App\Orchid\Layouts;

use Orchid\Screen\Field;
use Orchid\Screen\Fields\Input;
use Orchid\Screen\Fields\Label;
use Orchid\Screen\Layouts\Rows;

class ReusableLayout extends Rows
{
    public function __construct(
        private readonly string $prefix,
        private readonly string $title
    ) {}

    /**
     * 定义布局的字段。
     *
     * @return Field[]
     */
    protected function fields(): array
    {
        return [
            new Label('label')
                ->title($this->title),

            new Input($this->prefix . '.address')
                ->required()
                ->title('Address')
                ->placeholder('177A Bleecker Street'),
        ];
    }
}

实例可以以相同的方式使用,但它们可以接受参数

public function layout(): array
{
    return [
        new ReusableLayout('order.shipping_address', 'Shipping Address'),
        new ReusableLayout('order.invoice_address', 'Invoice Address'),
    ];
}

更多详细信息可以在 Layouts 部分找到。

其他方法

验证消息

/**
 * 表单无效时的消息。
 */
public function formValidateMessage(): string
{
    return '请检查输入的数据';
}

此方法在 HTML 表单验证失败时返回消息,例如,如果缺少必填字段。

防止导航时数据丢失

/**
 * 确定是否在尝试导航时防止数据丢失。
 */
public function needPreventsAbandonment(): bool
{
    return false;
}

没有人希望丢失刚刚输入的数据。为防止这种情况,此方法触发一个机制,如果对页面上的数据进行了更改,则阻止刷新或离开当前页面。

Stimulus 控制器

/**
 * 返回前端的基础 Stimulus 控制器的名称。
 *
 * 此方法用于确定将在应用程序前端使用的基础 Stimulus 控制器。
 * 控制器管理 UI 元素的行为,通过 Hotwire 与其他组件交互。
 *
 * @return string 基础控制器的名称。
 */
public function frontendController(): string
{
    return 'base';
}

此方法返回前端的基础 Stimulus 控制器的名称。它对于管理 UI 元素的行为以及通过 Hotwire 与其他组件交互至关重要。

我们的朋友