教程:Laravel + Vuejs + Tailwind CSS 构建一个 Todo App 第二部分

Laravel

在我的 上一篇文章 中,我们已经制作了一个小的Todo Vue组件,并使用tailwind CSS对其进行了样式化。

让我们继续开发这个应用程序,通过丰富Laravel和MySQL的后端。要继续学习本教程,可以在GitHub上下载此项目的副本.

开发准备

在开发前我们需要创建一个 MySQL 数据库。准备一,你可能得运行下面的命令登陆到 MySQL:

mysql -uroot -p

当你输入正确的密码以后,敲击 enter 以后继续。现在,我们可以使用 MySQL 创建一个简单的数据库。对于本次教程,我们将数据库命名为tododb,并运行下面的命令创建它:

create database tododb;

看呐,我们有一个数据库! 接下来,让我们打开 现有的Laravel项目, 然后在 .env 文件里面将数据库连接到我们的项目。 打开 .env文件, 我们将看到下面的代码块:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

我们可以更新 DB_DATABASEDB_USERNAME以及 DB_PASSWORD 来匹配我们新创建的数据库,就像下面代码这样:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=tododb
DB_USERNAME=root
DB_PASSWORD=

完成所有这些工作之后,我们现在已经成功创建了数据库并将其连接到我们的Laravel项目。 现在,我们可以开始做待办事项了。

使用Laravel可以很容易的构建 todos App。利用Laravel内置的脚手架,在项目根目录下,执行命令 php artisan make:model Todo -a ,Laravel会帮助我们创建所有我们需要的文件。

上述命令会生成下列文件: 一个数据库迁移,一个模型工厂,一个控制器文件,以及一个数据模型。下面我们分别看看刚才创建的文件。

数据库迁移 Migration

数据库迁移文件定义了数据表结构,并将表结构的变更保存在源文件中。在变更表结构的时候,使用数据库迁移文件,会比使用手动更新表结构的方式更加方便。同时,迁移文件也是数据库表结构的便跟记录。因此我们可以通过数据库迁移文件看出表结构的变更历史,如果出现问题,可以快速的回滚到指定版本的表结构。

在我们的项目中,生成的数据库迁移文件路径为 database/migrations/<timestamp>_create_todos_table.php。对于每个数据库迁移文件,Laravel都会生成唯一的时间戳前缀确保数据库变更的顺序。所以每个文件都有一个不同的时间戳前缀<timestamp>

数据库迁移文件的代码如下:

public function up()
{
    Schema::create('todos', function (Blueprint $table) {
        $table->increments('id');
        $table->timestamps();
    });
}

在这里定义表结构。对于我们的 todo,我们需要一个 id,一个 text,以及一个 finished 列。我们提供了一个默认的 id ,因此我们可以添加其他两列,就像下面这样:

public function up()
{
    Schema::create('todos', function (Blueprint $table) {
        $table->increments('id');
        $table->longText('text');
        $table->boolean('finished')->default(false);
        $table->timestamps();
    });
}

现在, 我们需要让 MySQL 数据库保存我们定义的表。在命令行中运行  php artisan migrate。 使用 Sequel Pro 这样的命令行工具,你可以看到 todo 表是根据我们定义的架构创建的。

通过迁移文件,我们可以添加工厂,用一些虚拟数据来填充数据库。

工厂

工厂可用于整理大量的伪数据,对开发很有帮助。有利于我们看到数据在应用程序的工作方式。另外,当我们使用测试驱动开发或者 TDD 的时候,我们可以使用工厂创建可测试的数据。

在 database/factories 文件夹下,可以看到   TodoFactory.php 文件。 这里就是我们使用 Faker 软件包设置工厂的地方。我们的文件开始的时候就像下面这样:

<?php

use Faker\Generator as Faker;

$factory->define(App\Todo::class, function (Faker $faker) {
    return [
        //
    ];
});

我们真正需要添加的是todo的text。我们不必担心手动设置一个id,因为Laravel会自动递增并为我们设置这个id。对于finished列,您可能已经注意到我们为该列定义了一个默认值false。这意味着每当一个新的todo添加到我们的数据库中时,如果它是false,我们就不必显式地设置finished值。只剩下text栏了。我们可以这样设置:

<?php

use Faker\Generator as Faker;

$factory->define(App\Todo::class, function (Faker $faker) {
    return [
        'text' => $faker->sentence,
    ];
});

上面就是 factory 的代码。要查看它的实际效果,我们可以跳转到tinker中。Tinker是一个laravel包,它允许我们从命令行试验和操作应用程序的数据。当你只想做一些简单的任务,比如看看数据库中有多少to do时,这真是太棒了。

现在,让我们来到命令行并运行 php artisan tinker 启动它。一旦运行起来,我们可以通过 factory(App\Todo::class)->make() 命令,使用工厂来生成 todo。 这个命令仅仅生成 todo,并不能保存到数据库.为了保存 todo 到数据库,我们需要运行 factory(App\Todo::class)->create().

我们可以通过 factory(App\Todo::class, 5)->create() 增加5个 todo 到数据库。你可以从命令行输出中看到,我们已经添加了5个 todo。现在,如果我们运行  Eloquent 查询,我们可以看到之前创建的 todo 。通过运行 tinker 中的 Todo::all(),我们可以看到返回一个  collection 的 todo 集合。

完成了迁移和工厂,我们可以添加一些相匹配的虚拟数据。接下来,我们定义模型,以便 Laravel 可以更轻松的和数据进行交互。

模型

模型的概念对于 Laravel 新手来讲有时候会比较混淆。 本质上,模型允许我们使用Eloquent ORM与表交互。 在模型中,我们可以定义想要向用户公开的表列,那一列我们需要编辑, 创建自定义 mutators, 添加帮助方法,等。 在这篇文章中,我们将保持它的简单性,并逐步建立一个典型的模型。

在 app目录中,我们可以在Todo.php中看到我们的Todo模型。如您所见,我们有一个PHP基类。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
    //
}

默认情况下,生成的模型扩展了 Laravel 的基本模型。这非常有用,因为它为我们提供了很多现成的功能。我们可以添加一个被称为 fillable 的受保护变量来利用这个内置的功能。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
    protected $fillable = [
        'text',
        'finished',
    ];
}

这很重要,默认情况下, Laravel 可以保护我们免受大量的漏洞侵害。如果我们没有明确指出要更新的字段,那么恶意用户可能会动态更新敏感的字段。(例如 「password」或者 「is_admin」) 。如果在 $fillable 中定义了字段,我们可以根据需要更新指定字段。

我们可以在模型中定义修改器 属性。 修改器可以方便的将数据库的数据转换成程序需要的数据类型。例如,在数据库中布尔类型数据存储为1或者0,如下图所示:

虽然使用0和1程序也能够正常运行,但是如果严谨的使用布尔类型则更加好。所以,我们在模型中定义属性$casts,使得程序获取完数据库的finished字段数据之后将其转换成布尔类型的truefalse

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
    protected $fillable = [
        'text',
        'finished',
    ];

    protected $casts = [
        'finished' => 'boolean',
    ];
}

这样,当我们引用模型中的finished属性的时候,我们所获得的就是所需要的布尔类型数据。因为当前模型没有任何的关联模型,因此上面的代码就Todo应用中完整的模型中的代码。 接下来,我们进入到控制器中,并且在控制器中输出一些数据到前端Vue.js组件中。

控制器

我们的文件位于 app/Http/Controllers 目录下,且命名为  TodoController.php文件。当运行脚手架命令时,Laravel 将此文件设置为「resourceful」控制器。 你可以看到,我们提供了更多现成的样板。

该样板帮助我们遵循 RESTful 的模式。如果你正在寻找更多有关创建 REST API 的信息。请留意此人 Phil Sturgeon.。它写了很多这方法的文章以及出版了一本书 fantastic book,可以使你学到很多。

我们对于简单的应用程序, 我们要着重于去设置  index() 和 store() 方法. 这样我们可以看到所有待办事项并且能够添加新的待办事项。

我们的 index() 方法将看起来很简单. 我们将像如下这样返回所有待办事项:

public function index()
{
    return response(Todo::latest()->get(), 200);
}

这样可以将所有待办事项从最小的顺序排列到最大的顺序。对于我们的store() 方法而言, 我们需要做更多的工作. 一会儿, 你就可以看到我们所传递的 $request.

public function store(Request $request)
{

}

在 $request 里面,我们可以引用通过POST传递的数据。 我们对$request所做的第一件事就是去验证是否有创建待办事项所需的数据。 为此,我们将使用Laravel的内置请求验证器,如下所示:

public function store(Request $request)
{
    $data = $request->validate([
        'text' => 'required',
        'finished' => 'required|boolean',
    ]);
}

这是用来检查请求传入的 text 和 finished 字段是否符合我们定义的数据类型。验证通过后,我们就可以借助 $data 来创建一条 todo 记录。

public function store(Request $request)
{
    $data = $request->validate([
        'text' => 'required',
        'finished' => 'required|boolean',
    ]);

    $todo = Todo::create($data);

    return response($todo, 200);
}

store() 方法中,我们只需要将这条新创建的 todo 记录返回给 Vue.js 的状态即可。现在,我们还需要添加一些控制器的方法,就留给你自己来完成吧,看好你哦!

言归正传,最后还需要添加路由,以便于我们可以通过 Vue.js 的组件来触发控制器的动作。打开 routes/web.php,新增路由如下:

Route::apiResource('todos', 'TodoController');

查看一下我们定义的每一个路由,如果你使用 tinker 来查看应用路由的话,将会 输出下面的信息:

当然了,你也可以为每一个控制器级别的方法分别添加一个路由。但是在处理资源控制器的时候,使用资源路由更能节省一些时间。

哇!我们已经完成了很多工作,创建数据库,在Laravel中设置Todos。接下来就可以通过Vue组件完成我们的应用程序。

前端部分

在将 todo-component 组件连接到后端之前,让我们花一点时间进行部分代码的重构。对于初学者,让我们丢掉「Done」按钮。然后添加一个复选框,就像这样:

<div class="flex mb-4 items-center" v-for="(todo, index) in todos" :key="todo.id">
    <input type="checkbox" class="mr-2" @click="updateStatus(todo)">
    <p class="w-full" :class="todo.finished ? 'line-through text-green' : 'text-grey-darkest'">{{todo.text}}</p>
    <button class="flex-no-shrink p-2 ml-2 border-2 rounded text-red border-red hover:text-white hover:bg-red" @click="remove(index)">Remove</button>
</div>

那会清理我们的 todo。 我们可以在按下回车键的时候增加一个新的 todo。因此,我们需要在输入框上注册一个 keyup 事件,并且在 add() 方法上添加一些额外的验证,这样我们就不会提交空的 todo。看起来就像下面这样:

//输入框
<input class="shadow appearance-none border rounded w-full py-2 px-3 mr-4 text-grey-darker" v-model="newTodo" @keyup.enter="add" placeholder="Add Todo">

//方法
add() {
  const t = this;

  if(t.newTodo.length > 0) {
      let todo = {
        id: t.baseId,
        text: t.newTodo,
        finished: false,
      }

      t.todos.unshift(todo);

      t.newTodo = '';
      t.baseId++;
  }
},

编译我们的 JS ,现在组件看起来就像下面这样:

连接

完成了小小的重构之后,我们开始连接组件。首先,从数据库中取出 todo 。为此,我们需要在组件创建的部分生命周期中调用 axios。 因此,我们向组件添加一个新的 getTodos() 方法。

getTodos() {
  const t = this;

  axios.get('/todos')
      .then(({data}) => {
        t.todos = data;
      });
},

你可以看到,我们只是简单发出一个 GET 请求。并且通过返回的数据设置我们的  todos,现在,我们只需要在created lifecycle hook中调用这个方法来启动它。

created() {
  this.getTodos();
},

刷新页面,可以看到所有的工厂 todo 都在填充我们的组件。

现在,我们只需要创建一个方法增加新的 todo。之前我们已经创建过了这个方法了,因此,只需要对这个方法进行一些调整。为了让所有内容看起来更加清楚,我们再单独的为 axios POST 请求创建一个单独的方法。我们将得到以下内容:

createTodo(text) {
    const t = this;

   axios.post('/todos', {text: text, finished: false})
        .then(({data}) => {
               t.todos.unshift(data);
        });
},
add() {
  const t = this;

  if(t.newTodo.length > 0) {
      t.createTodo(t.newTodo);
      t.newTodo = '';
  }
},

在 createTodo() 方法中,我们只是传入 todo 文本,作为路由的传递参数。现在,一旦你创建了 todo,就会把新的 todo 增加到本地 todo 当中。

add() 方法中,我们能够删除负责添加 todo 的代码并且只专注于维护组件的状态。我们不再需要 baseId,因此,可以把它从 data 中移除。

现在,我们可以往数据库添加 todo。随着 todo 数量的增加,我们的 UI 变的有些不稳定,让我们顺便解决这个问题。

UI Tweaks

首先,我们需要一个自定义的 max_height。在项目根目录下的 tailwind.js 中,我们找到 maxHeight 这个属性,我们可以增加以下设置 'screen-1/2': '50vh',这确保了 todo 组件不会因为过高而撑破天。

在 Vue 组件中,我们可以将 max-h-screen-1/2 和 overflow-y-scroll 类添加到包含 todo 以及 空状态的 div 中。

<div class="max-h-screen-1/2 overflow-y-scroll">
    <div class="flex mb-4 items-center" v-for="(todo, index) in todos" :key="todo.id">
        <input type="checkbox" class="mr-2" @click="updateStatus(todo)">
        <p class="w-full" :class="todo.finished ? 'line-through text-green' : 'text-grey-darkest'">{{todo.text}}</p>
        <button class="flex-no-shrink p-2 ml-2 border-2 rounded text-red border-red hover:text-white hover:bg-red" @click="remove(index)">Remove</button>
    </div>
    <div v-show="todos.length === 0">
        <p class="w-full text-center text-grey-dark">There are no todos</p>
    </div>
</div>

现在,可以上车了。

总结

这篇文章包含了很多内容。我们创建了数据库。用 Laravel 构建了后端,并将其连接到 Vue 组件。另外,我们还重构了一次 UI。

虽然我们完成了很多工作,但依然有很多的事情要做。我们还需要完成控制器操作以及保留更新和删除。希望我们在下一篇 todo 应用文章中解决更多的问题。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://nick-basile.com/blog/post/buildi...

译文地址:https://learnku.com/laravel/t/36748

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 1

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!