こちらの記事の続編です。 VueとSpringで作成したプロジェクトの構築手順の備忘録。 備忘録のため、詳細な説明を省略している部分があります。 基本的なJavaの知識やSpring、Vueの知識があること。 今はUIがこんな↓感じなので、
これをいい感じ↓にしていきます。
構成的には親コンポーネントにサイドバーとヘッダを入れて、メインコンテンツを子コンポーネントとして適宜切り替えていくイメージで実装を進めてまいります。 まずは親コンポーネントとなるコンポーネント達を作成します。
frontend/src直下にlayouts/defaultという命名でディレクトリを作成します。その直下にまず親コンポーネントとなるIndex.vueを作成します。 大体こんな構成でいこうかと思います。 各コンポーネントを構成している部品が多く、冗長なので今回の記事ではヘッダ、フッタ、サイドバーについては説明を割愛しますので、GitHubをご参照ください。 まず、default/layoutの直下にView.vueを作成します。 公式サイトを見ると、Vue-routerでのコンポーネントのネストの実装方法が載っていましたので、こちらを参考にしつつ、 まずはlodshをインストールしていきます。(lodashを使うとフォーマットの成形が楽になるので) 次に、routingの制御を行う汎用的なメソッドを作成していきたいので、src直下にutilという名称のフォルダを作成し、routes.jsファイルを作成します。 続いて、router/index.jsを修正します。 公式曰く、 <router-view> コンポーネントは与えられたパスに対してマッチしたコンポーネントを描画する関数型コンポーネント なので、Homeコンポーネントのパスをkeyに渡すことでview-routerを動的に切り替えてみようと思います。これで、子コンポーネントのあたるメインコンテンツをroutingに応じて切り替えられ(るはず)ます。 VuexのSotreにアクセスする記述方法が手続き的で毎回記述するのも冗長だと感じていたところ、Vuex-pathifyというライブラリを発見しましたので、試しに使ってみました。 一応公式にもこのような形でvuex-pthifyを導入することで得られるメリットなどが記載されています。 先頭のcongnitive overheadが何を指しているか分かりにくいのですが、おそらくコードをぱっと見たときの感覚的に理解できる時間が短くなるよ的なことかと。。。 ネックなのは参考になる日本語の記事が見当たらなかった点くらいですかね。。。 それでは早速、 こちらをインストールしてみます。 こちらができたら、storeのエントリポイントとなるindex.jsにimportして、 Vuexインスタンスにマウントします。 そして、続いてstore直下にmodulesという名称のフォルダを作成して配置。
その直下にapp.jsというファイルを作成します。 例として、今回説明を省いたDrawer.vueを下記に引用します。 個人的にはこちらの方が宣言的で好みでした。 今回はここまでとなります。 お付き合いいただきありがとうございます。まえがき
目的
前提
環境
ゴール
画面をいい感じにする
<template>
<v-app>
<default-bar />
<default-drawer />
<default-view />
<default-footer />
</v-app>
</template>
<script>
export default {
name: 'DefaultLayout',
components: {
DefaultBar: () =>
import (
/* webpackChunkName: "default-app-bar" */
'./AppBar'
),
DefaultDrawer: () =>
import (
/* webpackChunkName: "default-drawer" */
'./Drawer'
),
DefaultFooter: () =>
import (
/* webpackChunkName: "default-footer" */
'./Footer'
),
DefaultView: () =>
import (
/* webpackChunkName: "default-view" */
'./View'
),
},
}
</script>
メインコンテンツの実装
<template>
<v-main>
<v-container fluid>
<router-view :key="$route.path" />
</v-container>
</v-main>
</template>
<script>
export default {
name: 'DefaultView',
}
</script>
// Imports
import { kebabCase } from 'lodash'
export function layout (layout = 'Default', children, path = '') {
const dir = kebabCase(layout)
return {
children,
component: () => import(
/* webpackChunkName: "layout-[request]" */
`@/layouts/${dir}/Index`
),
path,
}
}
export function route (name, component, path = '', publishingSettings) {
component = Object(component) === component
? component
: { default: name.replace(' ', '') }
const components = {}
for (const [key, value] of Object.entries(component)) {
components[key] = () => import(
/* webpackChunkName: "views-[request]" */
`@/views/${value}`
)
}
const meta = {isPublic: publishingSettings}
return {
name,
components,
path,
meta
}
}
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login.vue'
import store from '../store/index.js'
import {
layout,
route,
} from '@/util/routes'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
scrollBehavior: (to, from, savedPosition) => {
if (to.hash) return { selector: to.hash }
if (savedPosition) return savedPosition
return { x: 0, y: 0 }
},
routes: [
{
path: '/',
name: 'Login',
component: Login,
meta: {isPublic: true}
},
layout('Default', [
route('Home', null, '/home', false)
])
]
})
router.beforeEach((to, from, next) => {
console.log(store)
// 非公開コンポーネントで未ログインの場合ログイン画面にリダイレクト
if (
to.matched.some(
// TODO:Store.stateのアクセス方法が不明確なので今後修正を行うこと
record => (record.meta.isPublic || store.state)
)
) {
next();
} else {
next({ path: "/", query: { redirect: to.fullPath } });
}
});
export default router
おまけ〜Vuex-pathify〜
npm i vuex-pathify
import pathify from 'vuex-pathify'
export default new Vuex.Store({
plugins: [ pathify.plugin ], // activate plugin
});
// Pathify
import { make } from 'vuex-pathify'
// Data
const state = {
drawer: null,
drawerImage: true,
mini: false,
items: [
{
title: 'Home',
icon: 'mdi-home-circle-outline',
to: '/home',
},
],
}
const mutations = make.mutations(state)
const actions = {
...make.actions(state),
init: async ({ dispatch }) => {
//
},
}
const getters = {}
export default {
namespaced: true,
state,
mutations,
actions,
getters,
}
<template>
<v-navigation-drawer
id="default-drawer"
v-model="drawer"
:right="$vuetify.rtl"
:mini-variant.sync="mini"
mini-variant-width="80"
app
width="260"
color="#424242"
>
<div class="px-2">
<default-drawer-header />
<v-divider class="mx-3 mb-2" />
<default-list :items="items" />
</div>
<div class="pt-12" />
</v-navigation-drawer>
</template>
<script>
// Utilities
import { get, sync } from 'vuex-pathify'
export default {
name: 'DefaultDrawer',
components: {
DefaultDrawerHeader: () => import(
/* webpackChunkName: "default-drawer-header" */
'./widgets/DrawerHeader'
),
DefaultList: () => import(
/* webpackChunkName: "default-list" */
'./List'
),
},
computed: {
...get('app', [
'items',
'version',
]),
...sync('app', [
'drawer',
'drawerImage',
'mini',
]),
},
}
</script>