深入 Laravel Nova 教程实例

在我的上一篇文章我简单的介绍了一下 Nova 的用法,下面我将通过新建一个简单的 CRM 系统来更深入的了解 Nova

在此过程中,我们将了解如何使用 Nova 的 metrics , 自定义搜索条,配置 filters 和 lenses,听起来很复杂,咱们一步一步开始吧!

本文代码:https://github.com/leienshu/learn-nova

开始#

不会安装 Nova 的请参考我的上一篇写 Nova 的入门文章,本文默认你已经安装好了,并且创建了一个登录用户。

访客提交#

在我们深入了解 Nova 之前,我们需要创建 Laravel 模型。 我们的 CRM 将创建使用两个模型。

  • 一个 Visitor 模型,我们将用它来为我们跟踪与我们互动的所有访客。

  • 一个 Note 模型,我们的管理员用户可以为潜在客户留下笔记,访客也可以留下笔记。

因此,我们的模型关系,我们的潜在客户将拥有许多 Notes,我们的管理员用户也将拥有许多 Notes。

下面我们开始创建模型:


php artisan make:model Visitor -a

php artisan make:model Note -a

上面 -a 参数的意思是同时创建 Model、Migration、Controller、Factory 。

迁移#

修改 visitors 的 migration 文件:


public function up()

    {

        Schema::create('visitors', function (Blueprint $table) {

            $table->increments('id');

            $table->text('type')->comment('类型');

            $table->text('status')->comment('状态');

            $table->text('name')->comment('名字');

            $table->text('email')->comment('邮箱');

            $table->timestamps();

        });

    }

修改 notes 的 migration 文件:


public function up()

    {

        Schema::create('notes', function (Blueprint $table) {

            $table->increments('id');

            $table->integer('user_id')->comment('用户ID');

            $table->integer('visitor_id')->comment('访客ID');

            $table->text('priority')->comment('优先级');

            $table->text('title')->comment('标题');

            $table->text('body')->comment('内容');

            $table->timestamps();

        });

    }

执行迁移:


php artisan migrate

模型#

我们的每一条 note 既有访客又有管理用户,所以我们定义 note 的模型如下:


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Note extends Model

{

    public function user()

    {

        return $this->belongsTo('App\User');

    }

    public function visitor()

    {

        return $this->belongsTo('App\visitor');

    }

}

然后我们继续添加我们的 User 模型和 Visitor 模型和 Note 模型的关系:

都增加以下代码:


public function notes()

    {

        return $this->hasMany('App\Note');

    }

好了,我们继续在 Visitor 模型中添加一些 fillable 属性。


protected $fillable = [

        'type',

        'status',

        'email',

        'name'

    ];

最后,让我们在 Visitor 和 Note 模型中提供一些常量和辅助方法,以便我们可以轻松访问它们的类型,状态和优先级值。

// Note.php


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Note extends Model

{

    // 定义优先级

    const LOW_PRIORITY = 'Low';

    const MEDIUM_PRIORITY = 'Medium';

    const HIGH_PRIORITY = 'High';

    public function user()

    {

        return $this->belongsTo('App\User');

    }

    public function visitor()

    {

        return $this->belongsTo('App\Visitor');

    }

    // 获取优先级

    public static function getPriorities()

    {

        return [

            self::LOW_PRIORITY => self::LOW_PRIORITY,

            self::MEDIUM_PRIORITY => self::MEDIUM_PRIORITY,

            self::HIGH_PRIORITY => self::HIGH_PRIORITY,

        ];

    }

}

// Visitor.php


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Visitor extends Model

{

    // 定义用户常量

    const ORGANIC_TYPE = 'Organic';  //基本类型

    const USER_SUBMITTED_TYPE = 'User Submitted';  //用户提交类型

    const PROSPECT_STATUS = 'Prospect';  //预期

    const VISITOR_STATUS = 'Visitor';  //访客状态

    const CUSTOMER_STATUS = 'Customer'; //客户状态

    protected $fillable = [

        'type',

        'status',

        'email',

        'name'

    ];

    public function notes()

    {

        return $this->hasMany('App\Note');

    }

    // 获取类型

    public static function getTypes()

    {

        return [

            self::ORGANIC_TYPE => self::ORGANIC_TYPE,

            self::USER_SUBMITTED_TYPE => self::USER_SUBMITTED_TYPE,

        ];

    }

    //获取状态

    public static function getStatuses()

    {

        return [

            self::PROSPECT_STATUS => self::PROSPECT_STATUS,

            self::VISITOR_STATUS => self::VISITOR_STATUS,

            self::CUSTOMER_STATUS => self::CUSTOMER_STATUS,

        ];

    }

}

上面定义的常量有点麻烦,如果你不喜欢它,请随意忽略这一部分。我这样做只是为了更好地让我们将使用这些方法来帮助我们使模型与他们的 Nova 资源保持同步。

就这样,我们完成了我们的模型。

现在,让我们在我们的应用程序里创建一个快速表单,以便访问者可以输入他们的详细信息并成为潜在客户。

访客表单#

我们继续用 welcome.blade.php 这个视图文件,做一些修改:


<!doctype html>

<html lang="{{ app()->getLocale() }}">

<head>

    <meta charset="utf-8">

    <title>Nova CRM 演示</title>

</head>

<body>

    <div>

        <form action="/form-submit" method="POST">

            <h1>注册我们的Nova程序</h1>

            {{ csrf_field() }}

                <div>

                    <label>姓名:</label>

                    <input id="name" name="name" type="text" placeholder="Lei">

                </div>

                <div>

                    <label>邮箱:</label>

                    <input id="email" name="email" type="email" placeholder="lei@example.com">

                </div>

                <button type="submit">提交</button>

        </form>

        @if (session('form-success'))

            <div>

                <p>{{ session('form-success') }}</p>

            </div>

        @endif

    </div>

</body>

</html>

弄好了这个后就去 web.php 文件里面加路由吧!


Route::post('/form-submit', 'VisitorController@store');

建好了路由就修改控制器吧:


public function store(Request $request)

    {

        $validatedData = $request->validate([

        'name' => 'required|string',

        'email' => 'required|email|unique:visitors,email',

        ]);

        $visitor = new Visitor;

        $visitor->name = $validatedData['name'];

        $visitor->email = $validatedData['email'];

        $visitor->type = Visitor::ORGANIC_TYPE;

        $visitor->status = Visitor::PROSPECT_STATUS;

        $visitor->save();

        return redirect()->back()

            ->with('form-success', 'Thank you for your submission!');

    }

好了,这样我们的访客就能提交东西了。

Nova CRM#

新建 resources,使用下面的命令创建 nova 的 resources:


php artisan nova:resource Visitor

php artisan nova:resource Note 

我们看到 Nova 文件夹下面多处了一个 Visitor 的 recource 和一个 Note 的 recource,编辑它们:

修改 Visitor 的 recource:


<?php

namespace App\Nova;

use Laravel\Nova\Fields\ID;

use Laravel\Nova\Fields\Text;

use Laravel\Nova\Fields\Select;

use Laravel\Nova\Fields\HasMany;

use Illuminate\Http\Request;

use Laravel\Nova\Http\Requests\NovaRequest;

class Visitor extends Resource

{

    public static $model = 'App\Visitor';

    public static $title = 'name';

    public static $search = [

        'id',

        'name',

        'email'

    ];

    public function fields(Request $request)

    {

        return [

            ID::make()->sortable(),

            Text::make('name')->sortable(),

            Select::make('Status')

                ->options(\App\Visitor::getStatuses())

                ->sortable()

                ->rules('required', 'string'),

            Select::make('Type')

                ->options(\App\Visitor::getTypes())

                ->sortable()

                ->rules('required', 'string'),

            Text::make('Email')

                ->sortable()

                ->rules('required', 'email')

                ->creationRules('unique:visitors,email')

                ->updateRules('unique:visitors,email,{{resourceId}}'),

            HasMany::make('Notes'),

        ];

    }

}

正如你所看到的,我这里用了比前一篇文章更多的内容。

在我们的电子邮件字段中,我们正在进行一些特殊验证,以确保我们保持电子邮件的唯一性。

另外,请注意我们的模型方法现在正在出现! 我们可以使用它们来填充选择字段,因此我们的值可以保持同步。也许有点矫枉过正,但我知道我只要在一个地方改变它,所有地方都会同步更新。

最后,我们可以看到索引中出现的所有字段都是可排序的。

打开我们的 Nova 管理员界面,我们可以看到索引已经自行构建,我们的表单现在可用了!


现在访问者可以提交数据,我们的团队可以上传他们的潜在客户!如果我们能够跟踪管理员用户在 Visitors 和 Notes 上为管理目的所做的任何编辑或更改,那将是很好的。 很幸运的是,Nova 让这变得非常容易。

我们只需要将 Actionable trait 添加到模型中,我们将获得在模型上一些列执行的操作列表。


//In app\Visitor.php 和 app\Note.php

use Actionable;

好了,Visitor 可以进行添加操作了,加完后如下图:

现在,让我们来操作 Notes 吧,打开:Note Recource


<?php

namespace App\Nova;

use Laravel\Nova\Fields\ID;

use Laravel\Nova\Fields\Text;

use Laravel\Nova\Fields\Select;

use Laravel\Nova\Fields\BelongsTo;

use Laravel\Nova\Fields\Markdown;

use Illuminate\Http\Request;

use Laravel\Nova\Http\Requests\NovaRequest;

class Note extends Resource

{

    public static $model = 'App\Note';

    public static $title = 'title';

    public static $search = [

        'id',

        'title',

    ];

    public function fields(Request $request)

    {

        return [

            ID::make()->sortable(),

            Text::make('Title')

                ->sortable()

                ->rules('required', 'string'),

            Select::make('Priority')

                ->options(\App\Note::getPriorities())

                ->sortable()

                ->rules('required', 'string'),

            Markdown::make('Body')

                ->rules('required', 'string'),

            BelongsTo::make('Visitor')

                ->sortable()

                ->rules('required'),

            BelongsTo::make('User')

                    ->sortable()

                ->rules('required'),

        ];

    }

}

继续修改 User Recource,让它能关联到 Notes:

只要增加两行代码:自己找相应的位置


use Laravel\Nova\Fields\HasMany;

HasMany::make('Notes')

弄好了就可以添加记录了,如下图:

好了我们的 CRM 的 CURD 到这里就搞定了,不过你以为完了吗,没有,我们继续增加更多的特性吧!

Metrics#

什么是没有任何指标的 CRM?开箱即用的 Nova 使我们可以非常轻松地向管理员添加指标。

那么,我们想看到什么?

随着时间的推移,看到访客的增长会很棒;

我们在一定时期内获得了多少潜在客户,以及每种潜在客户的细分情况。

我们赶紧来设置下吧!

首先让我们用下面的命令来创建 Metric 吧:


php artisan nova:trend VisitorsPerDay

将 caculate 方法里默认的模型的值替换一下:


public function calculate(Request $request)

{

    return $this->countByDays($request, \App\Visitor::class);

}

现在,为了显示我们的新趋势图,我们将回到我们的 Visitor Recource 并在 cards()方法中注册它。由于我们希望它占据屏幕的三分之一,我们还将使用 width('1/3')方法进行链接。


public function cards(Request $request)

{

    return [

        (new Metrics\VisitorsPerDay)->width('1/3'),

    ];

}

打开 Nova 后台,你将看到牛逼的东西来了:

是不是很神奇,好了,我们继续用下面的命令创建另外几个东西:


php artisan nova:value NewVisitors

php artisan nova:partition VisitorsPerStatus

参照 VisitorsPerDay 修改上面两个文件,注意一下 VisitorsPerStatus 里面有一个 grouByColoum 我们要替换成 status,什么意思?字面意思!

搞完了,在修改 cards 方法:


public function cards(Request $request)

{

    return [

        (new Metrics\VisitorsPerDay)->width('1/3'),

        (new Metrics\NewVisitors)->width('1/3'),

        (new Metrics\VisitorsPerStatus)->width('1/3'),

    ];

}

生成的图形如下:

怎么样?是不是简单到爆炸!!

让我们继续下一个功能吧!

Filters#

这个是干嘛的呢?字如其意,我不会写教程,直接搞完了看图说话,你们就明白了!


php artisan nova:filter VisitorType

php artisan nova:filter VisitorStatus

看你的 Nova 目录下是不是又多了一个文件夹 Filters,我们在里面加一些查询吧。

分别修改两个文件,为了方便我都放一起了:


// 这是VisitorStatus

public function apply(Request $request, $query, $value)

{

    return $query->where('status', $value);

}

public function options(Request $request)

{

    return \App\Visitor::getStatuses();

}

// 这是VisitorType

public function apply(Request $request, $query, $value)

{

    return $query->where('type', $value);

}

public function options(Request $request)

{

    return \App\Visitor::getTypes();

}

搞定后进入 Visitor 注册 Filters。


public function filters(Request $request)

{

    return [

        new Filters\VisitorStatus,

        new Filters\VisitorType,

    ];

}

弄完回去看一眼,你就可以发现这个过滤器了:

牛逼吧,是不是好简单!!

让我们继续吧!

Lenses#

这又是个啥?你可以这样理解他就是一个更高级的 filter,可以定制一些 eloquent。看下面的代码示例吧!

比如我们现在要查修哪些访问时间密集的客户要怎么做呢?

先创建一个 lens


php artisan nova:lens TimeIntensiveVisitors

修改 lenses,添加一些查询语句:


public static function query(LensRequest $request, $query)

{

    return $request->withOrdering($request->withFilters(

        $query->select([

        'visitors.id',

        'visitors.name',

        'visitors.email',

        'visitors.status',

        'visitors.type',

        DB::raw('count(notes.id) as Count')

    ])

    ->join('notes', 'visitors.id', '=', 'notes.visitor_id')

    ->orderBy('Count', 'desc')

    ->groupBy('visitors.id', 'visitors.name')

    ));

}

添加完这个后,我们再继续在 field 里面添加哪些需要显示的:


 public function fields(Request $request)

{

    return [

        ID::make('ID', 'id')->sortable(),

        Text::make('Name')->sortable(),

        Select::make('Status')->sortable(),

        Text::make('Type')->sortable(),

        Text::make('Email')->sortable(),

        Number::make('Notes Count', 'Count'),

    ];

}

注意,不要忘记在开头 use 一下这些字段。


use App\Nova\Filters\VisitorStatus;

use App\Nova\Filters\VisitorType;

use Laravel\Nova\Fields\ID;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\DB;

use Laravel\Nova\Fields\Text;

use Laravel\Nova\Fields\Select;

use Laravel\Nova\Fields\Number;

use Laravel\Nova\Lenses\Lens;

use Laravel\Nova\Http\Requests\LensRequest;

接下来,我们将注册我们的自定义过滤器,以便我们可以像过去那样操纵结果,lenses 可以随意用我们之前定义的过滤器。


public function filters(Request $request)

{

    return [

        new VisitorStatus,

        new VisitorType,

    ];

}

完事了吗,不,还没完呢,还要在我们的 Visitor Recource 里面引用一下。


public function lenses(Request $request)

{

    return [

        new Lenses\TimeIntensiveVisitors,

    ];

}

好了,终于完事了,打开 nova 后台看看,什么样的结果。

总结#

怎么样,通过这个例子是不是明白了 nova 的一些基本的操作方法了,还有些别的我们下次再写吧。这是 nova 系列的第二篇。

所有代码我都上传到了:我的 github 了!

后续我将扩展下这个例子,继续再搞一篇 nova,与君共勉!

本作品采用《CC 协议》,转载必须注明作者和本文链接
求知若饥,虚心若愚!
本帖由系统于 3年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。