1. nobu-maple

    No comment

    nobu-maple
Changes in body
Source | HTML | Preview

※ 2018/10/17 Laravel5.6 から 5.7 に記述を更新

Laravel でユーザ認証した後、Vue側でRouter使ってページを切り替えてみる
ユーザ管理(社員管理)のページとしてデータテーブルを使えるように Vuetify も使ったページを作成する

環境設定他関連記事はこちら
Laravel + Vue + Vuetify で業務サイト作ってみる

1.各パッケージをインストール

npm でサクッと vue-router と vuetify を入れとく
vuetify はこちらを参考に

npm install vue-router
npm install vuetify
npm install css-loader
npm install material-design-icons-iconfont

インストール後はこんな感じ

package.json
{
    "private": true,
    "scripts": {
        "dev": "npm run development",
        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "npm run development -- --watch",
        "watch-poll": "npm run watch -- --watch-poll",
        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
    },
    "devDependencies": {
        "axios": "^0.18",
        "bootstrap": "^4.0.0",
        "cross-env": "^5.1",
        "jquery": "^3.2",
        "laravel-mix": "^2.0",
        "lodash": "^4.17.5",
        "popper.js": "^1.12",
        "vue": "^2.5.7"
    },
    "dependencies": {
        "css-loader": "^1.0.0",
        "material-design-icons-iconfont": "^3.0.3",
        "vue-router": "^3.0.1",
        "vuetify": "^1.2.9"
    }
}

Vuetify は 1.2.9 が入りました

2.読み込み設定

追加した Vuetify と router を読み込むように設定
router については外出しして管理しやすいようにしとく
Vuetify は色設定も入れておく

resources/js/app.js
require('./bootstrap');

// Vue
import Vue from 'vue'


// Vuetify
import Vuetify from 'vuetify'
import colors from 'vuetify/es5/util/colors'

Vue.use(Vuetify, {
  theme: {
    primary: colors.indigo.base,
    secondary: colors.blue.base,
    accent: colors.amber.base,
  }
});
import 'vuetify/dist/vuetify.min.css'
import 'material-design-icons-iconfont/dist/material-design-icons.css'


// Vue-Router
import router from './router'


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

router 用設定

app.js と同じ階層に router ディレクトリを掘っておく

$ mkdir resources/js/router

作った ディレクトリに router 用設定ファイルを作成

resources/js/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

//
import example_component from '../components/ExampleComponent.vue'
import admin_component   from '../components/AdminComponent.vue'
import r_link            from '../components/RouterLink.vue'

//
Vue.component('example-component', example_component)
Vue.component('admin-component', admin_component)
Vue.component('r-link', r_link)

//
import home              from '../components/HomeComponent.vue'
import admin_user        from '../components/Admin/UserComponent.vue'

export default new Router({
  mode: 'history',
  routes: [
    { path: '/',             name: 'home',          component: home,          meta: {name: 'ホーム',   icon: 'home'}},
    { path: '/admin/user',   name: 'admin_user',    component: admin_user,    meta: {name: '社員管理', icon: 'supervisor_account'}},
  ],
})

リンク用の文字とかアイコンをここに定義
各画面で表示する文字とかアイコンはこの定義を参照することで変更も1か所で済むようにしておく

3.routerで切り替えるベースコンポーネント作成

vue-routerで切り替えるページのベースとなるコンポーネントを作成する
レイアウトは Vuetify のテンプレートを利用させてもらう

resources/js/components/AdminComponent.vue
<template>
  <v-app id="app">
    <v-navigation-drawer v-model="drawer" clipped fixed app >
      <v-list dense>
        <r-link linkname='home'></r-link>
        <r-link linkname='admin_user'></r-link>
      </v-list>
    </v-navigation-drawer>

    <v-toolbar color="primary" dark fixed app clipped-left>
      <v-toolbar-side-icon @click.stop="drawer = !drawer"></v-toolbar-side-icon>
      <v-toolbar-title>{{title}}</v-toolbar-title>
      <v-spacer></v-spacer>
      {{ name }}
      <v-btn icon @click="axiosLogout()">
        <v-tooltip left>
          <v-icon slot="activator" color="white" dark >exit_to_app</v-icon>
          <span>ログアウト</span>
        </v-tooltip>
      </v-btn>
    </v-toolbar>

    <v-content>
      <v-container fluid fill-height
        <v-layout justify-center fluid column>
          <v-fade-transition mode="out-in">
            <router-view @axios-logout="axiosLogout"></router-view>
          </v-fade-transition>
        </v-layout>
      </v-container>
    </v-content>

    <v-footer color="primary" dark app fixed>
      <span class="white--text ml-3" v-html="footer"></span>
    </v-footer>
  </v-app>
</template>

<script>
  export default {
    name: 'AdminComponent',

    props: {
      name: String,
      logout: String,
    },

    data: () => ({
      drawer: false,
      footer: 'foo-----footer',
      title: 'tit------title',
    }),

    mounted() {
      console.log('AdminComponent mounted.')

      if (process.env.MIX_FOOTER) { this.footer = process.env.MIX_FOOTER }
      if (process.env.MIX_TITLE) { this.title = process.env.MIX_TITLE }
    },

    methods: {
      axiosLogout: function() {
        axios.post(this.logout)
        .then( function (response) {
          console.log(response)
        }.bind(this))

        .catch(function (error) {
          console.log(error)
          if (error.response) {
            if (error.response.status) {
              if (error.response.status == 401 || error.response.status == 419) {
                var parser = new URL(this.logout)
                location.href=parser.origin
              }
            }
          }
        }.bind(this))
      },
    },
  }
</script>

リンク部分をコンポーネント化

リンク毎に同じ定義を書くのもめんどくさいのでコンポーネントにして、アイコンやらタイトルやらは route/index.js の定義から設定できるようにしとく

resources/js/components/RouterLink.vue
<template>
  <router-link :to="{name: linkname}">
    <v-list-tile>
      <v-list-tile-action>
        <v-icon>{{ getRoute( linkname, 'icon' ) }}</v-icon>
      </v-list-tile-action>
      <v-list-tile-content>
        <v-list-tile-title>{{getRoute( linkname, 'name' )}}</v-list-tile-title>
      </v-list-tile-content>
    </v-list-tile>
  </router-link>
</template>

<script>
  export default {
    name: 'routerlink',
    props: {
      linkname: String,
    },

    methods: {
      getRoute(name, key) {
        for(var i=0 ; i<this.$router.options.routes.length; i++) {
          if (this.$router.options.routes[i].name == name ) {
            return this.$router.options.routes[i].meta[key]
          }
        }
      },
    },

  }
</script>

<style>
  a:hover {
    text-decoration: none;
  }
</style>

4.home コンポーネント作成

とりあえず空のコンポーネント
Vuetifyで書いておく

resources/js/components/HomeComponent.vue
<template>
  <v-content>
    <v-container fluid fill-height>
      <v-layout justify-center fluid>
        <v-flex xs12 offset-mx5>
          Home Component.
        </v-flex>
      </v-layout>
    </v-container>
  </v-content>
</template>

<script>
  export default {
    data: () => ({
    }),

    props: {
    },

    created() {
      console.log('Home Component created.')
      this.initialize()
    },

    mounted() {
      console.log('Home Component mounted.')
    },

    methods: {
      initialize: function() {
      },
    },

  }
</script>

5.ユーザ一覧を表示するコンポーネントを作成

Vuetify のデータテーブルを利用して登録済みのユーザ(社員)情報を一覧表示してみます

ユーザ情報は axios で取ってきてデータテーブルにセット
検索とページングとソートの設定もしときます

データテーブル構造は動的に headers に設定しているやつをセットするようにしてます
ついでに連番も表示するようにしておきました

resources/js/components/Admin/UserComponent.vue
<template>
  <v-flex>
    <v-card xs12 class="m-3 px-3">

      <v-card-title class="title">
        <v-icon class="pr-2">{{ $route.meta.icon }}</v-icon> {{ $route.meta.name }} {{ /* 社員管理 */ }}
        <v-spacer></v-spacer>
        <v-spacer></v-spacer>
        <v-text-field
          v-model="search"
          append-icon="search"
          label="Search"
          single-line
          hide-details
        ></v-text-field>
      </v-card-title>

      <v-data-table
        :headers="headers"
        :items="tabledata"
        :pagination.sync="pagination"
        :rows-per-page-items='[10,25,50,{"text":"All","value":-1}]'
        :loading="loading"
        :search="search"
        class="elevation-0 p-1"
      >
        <v-progress-linear slot="progress" color="blue" indeterminate></v-progress-linear>

        <template slot="items" slot-scope="props">
          <tr>
            <td class="text-xs-center" xs1>{{ (props.index + 1) + (pagination.page - 1) * pagination.rowsPerPage }}</td>
            <template v-for="n in (headers.length - 1)">
              <td :class="'text-xs-' + headers[n].align" style="white-space: nowrap;" v-text="props.item[headers[n].value]"></td>
            </template>
          </tr>
        </template>

      </v-data-table>
    </v-card>
  </v-flex>
</template>

<script>
  export default {
    name: 'UserComponent',

    props: {
      logout: String,
    },

    data: () => ({
      loading: true,
      search: '',
      pagination: { sortBy: 'name', descending: false, },

      tabledata: [],
      headers: [
        { align: 'center', sortable: false, text: 'No',       },
        { align: 'left',   sortable: true,  text: '社員ID',   value: 'loginid' },
        { align: 'left',   sortable: true,  text: '氏名',     value: 'name' },
        { align: 'center', sortable: true,  text: '権限',     value: 'role' },
      ],
    }),

    created() {
      console.log('User Component created.')
      this.initialize()
    },

    methods: {
      initialize: function() {
        this.getUsers()
      },

      getUsers() {
        this.loading = true
        axios.post('/api/admin/user')

        .then( function (response) {
          this.loading = false
console.log(response)
          if (response.data.users) {
            this.tabledata = response.data.users
            this.setRole()
          }
        }.bind(this))

        .catch(function (error) {
          this.loading = false
          console.log(error)
          if (error.response) {
            if (error.response.status) {
              if (error.response.status == 401 || error.response.status == 419) {
                this.$emit('axios-logout')
              }
            }
          }
        }.bind(this))
      },

      setRole() {
        for (var i=0; i<this.tabledata.length; i++) {
          if (this.tabledata[i].role) {
            if (this.tabledata[i].role == 5) { this.tabledata[i].role = '管理者'  }
            if (this.tabledata[i].role == 10) { this.tabledata[i].role = 'ユーザ'  }
          }
        }
      },
    },
  }
</script>

権限(role)は数字で出されてもわかりにくいので日本語に変換(setRole)して表示するようにしています

6.Laravel側でユーザ用コントローラを作成

ひな形をまず作って

php artisan make:controller UserController

一覧のデータとして全件を返す index を定義しときます

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

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App/User;

class UserController extends Controller
{
  public function index()
  {
    $users = User::all();
    return ['users' => $users];
  }
}

権限(role)も表示対象にする

App/User.php でroleは隠す設定にしていたので、hiddenからfillableに変更

app/User.php
<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
//        'email',
        'loginid',
        'password',
        'role',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];
}

7.Laravel側のルーティング設定

管理者以外はアクセスできないように定義したGATEで制限(can:adminもかけときます

routes/web.php
<?php

Route::get('/', function () {
    return view('home');
})->middleware('auth');

// Authentication Routes...
Route::get('/login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('/login', 'Auth\LoginController@login');
Route::post('/logout', 'Auth\LoginController@logout')->name('logout');

// Admin
Route::group( ['middleware' => ['auth', 'can:admin']], function() {

  // USER
  Route::post('/api/admin/user', 'UserController@index')->name('admin/user');
});

// Other
Route::get('/{any}', function () {
  return view('home');
})->middleware('auth')->where('any', '.*');

8.動作確認

コンパイルして、サーバを起動して

npm run dev
php artisan serve --host=172.16.0.100  --port=8000

ブラウザでアクセスして
http://172.16.0.100:8000/

↓↓

ログインページへ飛ばされて
http://172.16.0.100:8000/login

↓↓

User の権限でログインしたら今までの Vue コンポーネントが表示されて
172.16.0.100_8000_js_colors.js.map.png

↓↓

ログアウトして 今度は Admin の管理者権限でログインしたら

↓↓

Vuetify で作った Adminコンポーネントが表示されて
172.16.0.100_8000_js_colors.js.map.png

↓↓

メニューから 「社員管理」を選ぶと vue-router の機能で一覧のコンポーネントに切り替わり
172.16.0.100_8000_js_colors.js.map.png

↓↓

User情報を axios が取ってきて Vuetify のデータテーブルにセットして表示
172.16.0.100_8000_js_colors.js.map.png

検索とかページングとかソートとかの動作もうまく動いているようです
172.16.0.100_8000_js_colors.js.map.png

ヘッダーとフッターの環境設定

ヘッダーとフッターを環境変数から取得するようにしてあるので

resources/js/components/AdminComponent.vue
~~
    mounted() {
      console.log('AdminComponent mounted.')

      if (process.env.MIX_FOOTER) { this.footer = process.env.MIX_FOOTER }
      if (process.env.MIX_TITLE) { this.title = process.env.MIX_TITLE }
    },
~~

環境変数を設定

.env
~~

MIX_FOOTER="フッター文字列を指定"
MIX_TITLE="タイトル指定"

末尾に定義を追加

ついでにVuetifyの色設定も変更してみる

resources/js/app.js
~~~
Vue.use(Vuetify, {
  theme: {
    primary: colors.amber.base,
~~~

indigo 指定を amber 指定に変更

コンパイルして再表示

npm run dev
php artisan serve --host=172.16.0.100  --port=8000

色とタイトルとフッターが変わったよ
172.16.0.100_8000_admin_user.png

以上
Vue-Router でページを切り替えて
Vuetify でデータテーブルを表示できました

ソースはこちら
https://github.com/u9m31/u9m31/tree/step04