1

12. 面向对象的任务列表实现 (1)

后台

初始化

创建一个新应用:

$ laravel new vue-to-do
$ cd vue-to-do

创建 Task 相关:

$ php artisan make:model Task -mc
Model created successfully.
Created Migration: 2017_05_01_050911_create_tasks_table
Controller created successfully.

配置数据库:

// /.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=vue_todo
DB_USERNAME=root
DB_PASSWORD=

编辑迁移文件:

/database/migrations/2017_05_01_050911_create_tasks_table.php
Schema::create('tasks', function (Blueprint $table) {
   $table->increments('id');
   $table->string('name');
   $table->text('description');
   $table->timestamps();
});

执行迁移:

$ php artisan migrate

业务逻辑

路由:

/routes/web.php
Route::get('/tasks','TaskController@index');
Route::post('/tasks','TaskController@store');

控制器:

/app/Http/Controllers/TaskController.php
<?php

namespace App\Http\Controllers;

use App\Task;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    /**
     * 显示与创建任务列表
     * @return [Response] [view]
     */
    public function index()
    {
        $tasks = Task::all();
        return view('tasks.index', compact('tasks'));
    }

    /**
     * 保存任务列表
     * @return [Response] [message]
     */
    public function store()
    {
        $this->validate(request(), [
            'name' => 'required',
            'description' => 'required'
        ]);

        Task::forceCreate(request('name', 'description'));

        return ['message' => '任务创建成功'];
    }
}

前端

基本功能实现

首先定义视图:

/resources/views/tasks/index.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue-To-Do-List</title>
</head>
<body>
    <div id="app" class="container">

        @if (count($tasks))
            <table class="table">
                <thead>
                    <tr>
                        <th>#</th>
                        <th>任务名</th>
                        <th>描述</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach ($tasks as $task)
                        <tr>
                            <th>{{ $task['id']}}</th>
                            <td>{{ $task['name'] }}</td>
                            <td>{{ $task['description'] }}</td>
                        </tr>
                    @endforeach

                </tbody>
            </table>
        @endif

        <form method="POST" action="/tasks" @submit.prevent="onSubmit">

            <fieldset class="form-group">
                <label for="name">任务名</label>
                <input type="text" class="form-control" id="name" name="name" v-model="name">
            </fieldset>

            <fieldset class="form-group">
                <label for="description">任务描述</label>
                <input type="text" class="form-control" id="description" name="description" v-model="description">
            </fieldset>

            <button type="submit" class="btn btn-primary">创建</button>
        </form>

    </div>

    <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/vue/2.2.6/vue.js"></script>
    <script src="https://cdn.bootcss.com/axios/0.16.1/axios.js"></script>
    <script type="text/javascript" src="/js/app.js"></script>
</body>
</html>

为了阻止表单的提交,可以使用 prevent 修饰符,相当于调用 Event.preventDefault() 方法。

前端业务逻辑:

/public/js/app.js
Vue.prototype.$http = axios;
var vm = new Vue({
    el: '#app',

    data: {
        name:'',
        description:''
    },

    methods: {
        onSubmit(){
            this.$http.post('/tasks',this.$data)
        }
    }
})

这里使用了之前介绍的 axios 包,其中,$data 为实例的 data 对象。在这里相当于:

this.$http.post('/tasks',{
    name: this.name,
    description: this.description
})

基本的效果如下:

clipboard.png

Errors 类

之前只实现了正确提交的功能。接下来实现错误提交的情况。

axiospost 请求的基本方法如下:

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  // 成功
  .then(function (response) {
    console.log(response);
  })
  // 失败
  .catch(function (error) {
    console.log(error);
  });

假如提交了一个空的表单,后台会返回错误消息,我们可以通过 error.response.data 来访问。比如

data: {
        name:'',
        description:'',
        errors:{}
    },

    methods: {
        onSubmit(){
            this.$http.post('/tasks',this.$data)
              .catch(error => this.errors = error.response.data);
        }
    }

为了方便操作,可以将其封装成类:

Vue.prototype.$http = axios;

class Errors {

    /**
     * 初始化
     */
    constructor(){
        this.errors = {}
    }

    /**
     * 保存错误信息
     */
    record(errors){
        this.errors = errors;
    }

}
var vm = new Vue({
    el: '#app',

    data: {
        name:'',
        description:'',
        errors:new Errors()
    },

    methods: {
        onSubmit(){
            this.$http.post('/tasks',this.$data)
              .catch(error => this.errors.record = error.response.data);
        }
    }
})

在控制台中查看对应的 errors 实例:

clipboard.png

也就是说,可以通过 errors[name][0] 的方式来访问错误信息,将其写成方法:

get(field) {
    if (this.errors[field]) {
        return this.errors[field][0];
    }
}

页面中就可以显示错误消息了:

<fieldset class="form-group">
    <label for="name">任务名</label>
    <input type="text" class="form-control" id="name" v-model="name">
    <small class="text-danger" v-text="errors.get('name')"></small>
</fieldset>

<fieldset class="form-group">
    <label for="description">任务描述</label>
    <input type="text" class="form-control" id="description" v-model="description">
    <small class="text-danger " v-text="errors.get('description')"></small>
</fieldset>

效果如下:

clipboard.png

接着,增加条件判断,以决定是否显示错误信息的页面:

<small class="text-danger" v-if="errors.has('name')"  v-text="errors.get('name')"></small>
<small class="text-danger" v-if="errors.has('description')" v-text="errors.get('description')"></small>

对应的方法:

/**
 * 判断属性是否存在
 * @param  {string}  field
 */
has(field){
    return this.errors.hasOwnProperty(field);
}

当存在错误消息的时候,不允许用户再次提交:

<button type="submit" class="btn btn-primary" :disabled="errors.any()">创建</button>

对应的方法:

any(){
    return Object.keys(this.errors).length > 0;
}

最后,用户重新输入时,应当清空错误消息:

<form method="POST" action="/tasks" @submit.prevent="onSubmit" @keydown="errors.clear($event.target.name)">

对应方法:

clear(field){
    if (field) {
        delete this.errors[field];
        return;
    }

    this.errors = {};
}

附录:


心智极客
1k 声望645 粉丝