1. nobu-maple

    Posted

    nobu-maple
Changes in title
+Laravel5.6 + Vue2.5 でvue-routerとVuetifyを使ってみる
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,487 @@
+Laravel でユーザ認証した後、Vue側でRouter使ってページを切り替えてみる
+ユーザ管理のページとしてテーブルを使えるように Vuetify も使ったページを作成する
+
+
+環境設定他これまでの記事はこちら
+[Qiita: Laravel5.6 + Vue2.5 でLaravelからVueにデータを渡す](https://qiita.com/nobu-maple/items/a704fe70809b0394b5c9)
+[Qiita: Laravel5.6 + Vue2.5 でLaravel ユーザでログインして Vue画面を表示する](https://qiita.com/nobu-maple/items/f0ee9324eacfed6eddd7)
+[Qiita: Laravel5.6 + Vue2.5 でユーザ権限によってvueコンポーネントを出しわける](https://qiita.com/nobu-maple/items/3eb735c148a698585fa3)
+
+
+#■1.各パッケージをインストール
+
+npm でサクッと
+今後のために日付処理系の [moment.js](https://momentjs.com/) も入れておく
+
+```bash
+npm install vue-router
+npm install vuetify
+npm install css-loader
+npm install material-design-icons-iconfont
+npm install moment
+```
+
+インストール後はこんな感じ
+
+```json: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.1.2",
+ "cross-env": "^5.2.0",
+ "jquery": "^3.2",
+ "laravel-mix": "^2.0",
+ "lodash": "^4.17.4",
+ "popper.js": "^1.12",
+ "vue": "^2.5.7"
+ },
+ "dependencies": {
+ "css-loader": "^1.0.0",
+ "material-design-icons-iconfont": "^3.0.3",
+ "moment": "^2.22.2",
+ "vue-router": "^3.0.1",
+ "vuetify": "^1.1.5"
+ }
+}
+```
+
+
+#■2.読み込み設定
+
+追加したパッケージを読み込むように設定
+
+```javascript:resources/assets/js/app.js
+ /**
+ * First we will load all of this project's JavaScript dependencies which
+ * includes Vue and other libraries. It is a great starting point when
+ * building robust, powerful web applications using Vue and Laravel.
+ */
+
+ require('./bootstrap');
+
+ window.Vue = require('vue');
+★1 window.Vuetify = require('vuetify');
+★1 Vue.use(Vuetify);
+
+★2 import 'vuetify/dist/vuetify.min.css';
+★2 import 'material-design-icons-iconfont/dist/material-design-icons.css';
+
+
+★3 import VueRouter from 'vue-router';
+★3 Vue.use(VueRouter);
+
+ Vue.component('example-component', require('./components/ExampleComponent.vue'));
+ Vue.component('admin-component', require('./components/AdminComponent.vue'));
+
+★4 const router = new VueRouter({
+★4 mode: 'history',
+★4 routes: [
+★4 { path: '/', component: require('./components/DashboardComponent.vue')},
+★4 { path: '/admin/user', component: require('./components/Admin/UserComponent.vue' )},
+★4 ]
+★4 });
+
+
+
+ /**
+ * Next, we will create a fresh Vue application instance and attach it to
+ * the page. Then, you may begin adding components to this application
+ * or customize the JavaScript scaffolding to fit your unique needs.
+ */
+
+
+ const app = new Vue({
+ el: '#app',
+★4 router,
+ });
+```
+
+★1 Vuetify 読み込み設定
+★2 Vuetify 関連のCSS読み込み設定
+★3 router 読み込み設定
+★4 router ルーティング設定
+
+
+#■3.routerで切り替えるベースコンポーネント作成
+
+vue-routerで切り替えるページのベースとなるコンポーネントを作成する
+レイアウトは Vuetify のテンプレートを利用させてもらう
+
+```html:resources/assets/js/components/AdminComponent.vue
+<template>
+ <v-app id="app">
+ <v-navigation-drawer v-model="drawer" clipped fixed app >
+ <v-list dense>
+ <router-link to="/home">
+ <v-list-tile @click="drawer = !drawer">
+ <v-list-tile-action> <v-icon>home</v-icon> </v-list-tile-action>
+ <v-list-tile-content>
+ <v-list-tile-title>HOME</v-list-tile-title>
+ </v-list-tile-content>
+ </v-list-tile>
+ </router-link>
+
+ <router-link to="/admin/user">
+ <v-list-tile @click="drawer = !drawer">
+ <v-list-tile-action> <v-icon>supervisor_account</v-icon> </v-list-tile-action>
+ <v-list-tile-content>
+ <v-list-tile-title>社員管理</v-list-tile-title>
+ </v-list-tile-content>
+ </v-list-tile>
+ </router-link>
+ </v-list>
+ </v-navigation-drawer>
+
+ <v-toolbar color="indigo" dark fixed app clipped-left>
+ <v-toolbar-side-icon @click.stop="drawer = !drawer"></v-toolbar-side-icon>
+ <v-toolbar-title>Application</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-fade-transition mode="out-in">
+ <router-view></router-view>
+ </v-fade-transition>
+
+ <v-footer color="indigo" dark app fixed>
+ <span class="white--text ml-3">
+ &copy; 2018
+ <a class="white--text" href="https://qiita.com/nobu-maple">Qiita nobu-maple</a>
+ </span>
+ </v-footer>
+ </v-app>
+</template>
+
+<script>
+ export default {
+ data: () => ({
+ drawer: false,
+ }),
+
+ props: {
+ id: String,
+ name: String,
+ role: String,
+ logout: String,
+ },
+
+ mounted() {
+ console.log('Component mounted.')
+ },
+
+ methods: {
+ axiosLogout: function() {
+ var params = new URLSearchParams()
+ axios.post(this.logout, params, {headers: {'Content-Type': 'application/x-www-form-urlencoded'}})
+
+ .then( function (response) {
+ console.log(response)
+ }.bind(this))
+
+ .catch(function (error) {
+ if (error.response.status === 401) {
+ var parser = new URL(this.logout)
+ location.href=parser.origin
+ }
+ console.log(error.response)
+ }.bind(this))
+ },
+
+ },
+ }
+</script>
+```
+
+
+#■4.home コンポーネント作成
+
+とりあえず空のコンポーネント
+Vuetifyで書いておく
+
+```html:resources/assets/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('Component created.')
+ this.initialize()
+ },
+
+ mounted() {
+ console.log('Component mounted.')
+ },
+
+ methods: {
+ initialize: function() {
+ },
+ },
+
+ }
+</script>
+```
+
+#■5.ユーザ一覧を表示するコンポーネントを作成
+
+Vuetify の[データテーブル](https://vuetifyjs.com/ja/components/data-tables)を利用します
+ユーザ一覧は axios で取ってきてテーブルにデータをセット
+検索とページングとソートの設定もしときます
+
+```html:resources/assets/js/components/Admin/UserComponent.vue
+<template>
+ <v-content>
+ <v-container fluid fill-height>
+ <v-layout justify-center fluid>
+ <v-flex xs12 offset-mx5>
+ <v-card xs12>
+
+ <v-card-title class="title">
+ <v-icon class="ml-2">supervisor_account</v-icon> 社員管理
+ <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="users"
+ :pagination.sync="pagination"
+ :rows-per-page-items='[10,25,50,{"text":"All","value":-1}]'
+ :loading="loading"
+ :search="search"
+ class="elevation-0"
+ >
+
+ <v-progress-linear slot="progress" color="blue" indeterminate></v-progress-linear>
+ <template slot="items" slot-scope="props">
+ <tr @click="props.expanded = !props.expanded">
+ <td class="text-xs-center" xs1>{{ (props.index + 1) + (pagination.page - 1) * pagination.rowsPerPage }}</td>
+ <td class="text-xs-left">{{ props.item.loginid }}</td>
+ <td class="text-xs-left">{{ props.item.name }}</td>
+ <td class="text-xs-left">{{ props.item.role }} - {{ props.item.role == '10' ? 'ユーザ' : '管理者' }}</td>
+ </tr>
+ </template>
+ </v-data-table>
+
+ </v-card>
+ </v-flex>
+ </v-layout>
+ </v-container>
+ </v-content>
+</template>
+
+<script>
+ export default {
+ data: () => ({
+ loading: true,
+ search: '',
+ pagination: { sortBy: 'name', descending: true, },
+
+ users: [],
+ headers: [
+ { align: 'center', sortable: false, text: 'No', },
+ { align: 'left', sortable: true, text: '社員ID', value: 'loginid' },
+ { align: 'left', sortable: true, text: '氏名', value: 'name' },
+ { align: 'left', sortable: true, text: '権限', value: 'role' },
+ ],
+ }),
+
+ props: {
+ },
+
+ created() {
+ console.log('Component created.')
+ this.initialize()
+ },
+
+ mounted() {
+ console.log('Component mounted.')
+ },
+
+ methods: {
+ initialize: function() {
+ this.getUsers()
+ },
+
+ getUsers: function() {
+ var params = new URLSearchParams()
+ this.loading = true
+ axios.post('/api/admin/user/', params, {headers: {'Content-Type': 'application/x-www-form-urlencoded'}})
+
+ .then( function (response) {
+ this.loading = false
+ console.log(response)
+ this.users = response.data.users
+ }.bind(this))
+
+ .catch(function (error) {
+ this.loading = false
+ console.log(error.response)
+ }.bind(this))
+ },
+
+ },
+ }
+</script>
+```
+
+
+#■6.ユーザ用コントローラを作成
+
+ひな形をまず作って
+
+```bash
+php artisan make:controller UserController
+```
+
+
+一覧のデータとして全件を返す index を定義しときます
+
+
+```php: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];
+ }
+}
+```
+
+#■7.Laravel側のルーティング設定
+
+管理者以外はアクセスできないように定義したGATEで制限もかけときます
+
+```php:routes/web.php
+<?php
+
+/*
+|--------------------------------------------------------------------------
+| Web Routes
+|--------------------------------------------------------------------------
+|
+| Here is where you can register web routes for your application. These
+| routes are loaded by the RouteServiceProvider within a group which
+| contains the "web" middleware group. Now create something great!
+|
+*/
+
+Route::get('/', function () {
+ return view('home');
+})->middleware('auth');
+
+//Auth::routes();
+
+// Authentication Routes...
+Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
+Route::post('login', 'Auth\LoginController@login');
+Route::post('logout', 'Auth\LoginController@logout')->name('logout');
+
+Route::get('/home', 'HomeController@index')->name('home');
+
+Route::group(['middleware' => ['auth', 'can:admin-higher']], function () {
+ Route::post('/api/admin/user', 'UserController@index')->name('admin/user');
+});
+
+
+Route::get('/{any}', function () {
+ Route::auth();
+ return view('home');
+})->where('any', '.*');
+```
+
+
+#■8.動作確認
+
+コンパイルして、サーバを起動して
+
+```bash
+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
+![login.png](https://qiita-image-store.s3.amazonaws.com/0/153259/8d0ccfbc-d759-ce7e-7838-578453181406.png)
+
+↓↓
+
+User の権限でログインしたら今までの Vue コンポーネントが表示されて
+![user.png](https://qiita-image-store.s3.amazonaws.com/0/153259/06e6a83d-b865-ea2b-4c23-f37e8f42cc35.png)
+
+↓↓
+
+ログアウトして 今度は Admin の管理者権限でログインしたら
+![login_admin.png](https://qiita-image-store.s3.amazonaws.com/0/153259/0d79e024-8fa2-6334-3717-b6fddc50644c.png)
+
+
+↓↓
+
+Vuetify で作った Adminコンポーネントが表示されて
+![admin_top.png](https://qiita-image-store.s3.amazonaws.com/0/153259/f97b2f83-3672-935b-7ba2-3ffba0691d30.png)
+
+↓↓
+
+メニューから 「社員管理」を選ぶと vue-router の機能で一覧のコンポーネントに切り替わり
+
+![admin_menu.png](https://qiita-image-store.s3.amazonaws.com/0/153259/f8efdea8-0036-74a9-b4e4-a9fdf36081bc.png)
+
+
+↓↓
+
+User情報を axios が取ってきて Vuetify のデータテーブルにセットして表示
+
+![admin_userlist.png](https://qiita-image-store.s3.amazonaws.com/0/153259/e513867d-0315-55fa-cda3-1353d6fbe7e6.png)
+
+
+