Laravel 7.x環境でVueを触ってみる 〜情報取得と表示編〜

加藤です。

前回で、Laravel 7.x環境でvue.jsを導入してみました。

tech.arms-soft.co.jp

以降は勉強のため、Vueを使ってタスク管理Webアプリケーションを作成してみようと思います。

環境

  • PHP 7.3
  • Laravel 7.0
  • Node.js v8.12.0

目標・やりたいこと

  • ログインしたユーザのタスクを一覧表示するようにしたい
  • タスク部分は、Vueのコンポーネントを使って作ってみる

(1)タスク管理用のModelを作成

まずユーザのタスク管理用のTaskモデルを作成します。

$php artisan make:model Task -m

Taskモデルはこのように作成しました。UserとTaskは1対多の関係になるので、BelongsToを入れています。

Laravelのリレーションに関してはこちら。

readouble.com

/laravel/app/Task.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Task extends Model
{
    use SoftDeletes;

    protected $dates = ['start_date', 'end_date', 'deleted_at'];

    protected $fillable = [
        'user_id',
        'title',        // タイトル
        'contents',     // 内容
        'status',       // 状態
        'limit'         // 期限
    ];

    public function user()
    {
        // Userに対して多なのでBelongsTo
        return $this->BelongsTo('App\Model\Users');
    }
}

UserモデルにはTaskに対してhasManyを追加しておきます。

/laravel/app/User.php

<?php
class User extends Authenticatable
{
    
    public function tasks()
    {
        // Taskに対して1なのでhasMany
        return $this->hasMany('App\Task');
    }
}

(2)Tasksのmigrationを作成

Tasksテーブルを作成します

/laravel/database/migrations/2020_07_29_000000_create_tasks_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTasksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->id();
            $table->integer('user_id')->comment('ユーザID');
            $table->string('title')->comment('タイトル');
            $table->text('contents')->nullable()->comment('内容');
            $table->tinyInteger('status')->nullable()->comment('状態');
            $table->timestamp('limit')->nullable()->comment('期限');
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('tasks');
    }
}

マイグレーションを実行します。

$php artisan migrate

(3) Controllerにアクション追加

ログイン後のユーザホーム画面で、ユーザ情報取得するアクションと、ログインユーザが保持しているタスクを取得するアクションをControllerに追加します。

/laravel/app/Http/Controllers/HomeController.php

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class HomeController extends Controller
{
    /**
     * ユーザ情報取得
     */
    public function index()
    {
        $user = User::get_user(Auth::id());

        return view('user.home')->with('user', $user);
    }

    /**
     * ログインしているユーザのタスクを取得
     */
    public function tasks(Request $request)
    {
        if (!$request->ajax())
        {
            abort(404);
        }
        // タスクを取得
        $tasks = User::find(Auth::id())->tasks;

        return $tasks;
    }
}

(4) 内部API用のルーティングを追加

Routeに/home/tasks のルーティングを追加しておきます。

/laravel/routes/web.php

<?php

use Illuminate\Support\Facades\Route;

// ログイン認証後
Route::middleware('auth:user')->group(function () {
    // ホーム画面
    Route::get('/home', 'HomeController@index')->name('home');
    // タスク取得
    Route::get('/home/tasks', 'HomeController@tasks')->name('home.tasks');
});

(5) app.jsの編集

app.jsにタスク用のコンポーネントを追加してみます。

require('./bootstrap');

window.Vue = require('vue');

// デフォルトで用意されているコンポーネント
Vue.component('example-component', require('./components/ExampleComponent.vue').default);
// 追加してみるコンポーネント
Vue.component('task-component', require('./components/TaskComponent.vue').default);

const app = new Vue({
    el: '#app',
});

(6) viewの編集

長くなってしまいますのでapp.blade.phpのナビゲーション部分は省略しています。認証機能作成時点から変更していません。

laravel/resources/views/layouts/user/app.blade.php

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>
    
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">  // #appはここにある
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
           <!-- ナビゲーション部分は省略 -->
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
</body>
</html>

/laravel/resources/views/user/home.blade.php

@extends('layouts.user.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-12">
                <div class="card">
                    <div class="card-header">Dashboard</div>

                    <div class="card-body">
                        @if (session('status'))
                            <div class="alert alert-success" role="alert">
                                {{ session('status') }}
                            </div>
                        @endif

                        ホーム画面です。

                        <br/>
                        <table class="table">
                            <thead>
                            <tr>
                                <th>ID</th>
                                <th>名前</th>
                                <th>メールアドレス</th>
                                <th></th>
                                <th></th>
                            </tr>
                            </thead>
                            <tbody>
                            <td scope="row">{{ $user['id'] }}</td>
                            <td>{{ $user['name'] }}</td>
                            <td>{{ $user['email'] }}</td>
                            <td>
                                <button class='btn btn-primary'>Edit</button>
                            </td>
                            <td>
                                <button class='btn btn-danger'>Delete</button>
                            </td>
                            <div>
                            </div>
                            </tbody>
                        </table>
                        // コンポーネント
                        <task-component></task-component>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

(7) コンポーネントの編集

タスク用に追加したコンポーネント<task-component>を編集します。

<template>
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">ToDo</div>
                    <div class="card-body">
                        <table class="table" v-if="tasks">
                            <thead>
                            <th>No</th>
                            <th>Task</th>
                            <th>Contents</th>
                            <th>Limit</th>
                            </thead>
                            <tbody v-for="task in tasks"> // タスクの数だけ繰り返し
                            <td>{{ task.id }}</td>       // ID
                            <td>{{ task.title }}</td>    // タイトル
                            <td>{{ task.contents }}</td>    // 内容
                            <td><div v-if="task.limit">{{ task.limit }}</div><div v-else>-</div></td> // タスクの期限が入力されていたら表示、なければ「-」
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>

    export default {
        data : function(){
            return {
                tasks: null,
            };
        },
        mounted () {
            axios
                .get('/home/tasks')    // タスク取得用の内部APIをここで呼び出し
                .then((response) => {
                    this.tasks = response.data;
                })
                .catch(function (error) {
                    alert('通信に失敗しました');
                    console.log(error);
                })
        }
    }
</script>

こちらを参考に、タスク取得用のhome/tasksを呼び出してそのレスポンスからID、タイトル、内容、期限を表示するようにしました。

axios を利用した API の使用

jp.vuejs.org

結果、こんな画面になりました。

ログインするユーザを変更すると表示されるタスク一覧も変わります。

つまづいたのはAPI実行後のresponseの扱いでした…。 取得したObjectの構造をちゃんと見なかったせいで「あれ、取得…できてない…?」と一人で焦りましたが、Vue DevToolsを落ち着いて見て、あっさり解決しました(汗)

次回はタスクの追加や削除に挑戦してみようと思います。