加藤です。
前回で、Laravel 7.x環境でvue.jsを導入してみました。
以降は勉強のため、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のリレーションに関してはこちら。
/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 の使用
結果、こんな画面になりました。
ログインするユーザを変更すると表示されるタスク一覧も変わります。
つまづいたのはAPI実行後のresponseの扱いでした…。 取得したObjectの構造をちゃんと見なかったせいで「あれ、取得…できてない…?」と一人で焦りましたが、Vue DevToolsを落ち着いて見て、あっさり解決しました(汗)
次回はタスクの追加や削除に挑戦してみようと思います。