こちらの記事の続編です。 VueとSpringで作成したプロジェクトの構築手順の備忘録。 備忘録のため、詳細な説明を省略している部分があります。 基本的なJavaの知識やSpring、Vueの知識があること。 それでは早速、実装を進めていきます。 まずはaxiosとvuex,vuetifyをインストールします。 ※ Vuetify導入時にエラーが発生したので記事にまとめましたのでご参考までに。。。(フロントエンド開発は流れが特に早いので各ライブラリ間でのバージョンの互換性が取れなくなるのは当たりまえなのかなぁ・・・) Vutifyの導入にあたって公式のnpmに沿ってwebpack.config.jsなどを作成しましたが、公式のまんまなので省略。
下記参考にしてください〜 次に、src直下のmain.jsファイルを以下のように書き換えます。 インストールしたパッケージたちをVueのインスタンスにマウントしてます。 Vueのprototypeにaxiosを追加することで各componentからthis.$axiosの形でアクセスすることができます。 続いて、Storeにログイン処理時のstateを登録していきます。
srcディレクトリ直下にstoreディレクトリを作成し、index.jsを以下のように記述します。 stateはアプリケーションレベルの状態が全て含まれており、"信頼できる唯一の情報源 (single source of truth)" として機能します。ここでは、getAuthStateという関数に切り出しています。stateの初期値を返却する関数を作ることでstateのリセットをしやすくなるからです。 Vue.jsではオブジェクト内の一部を変更してもリアクティブに伝搬してくれないので、resetDataメソッドでstate全体を変更することで対応しています。
ちなみに、getAuthStateを関数ではなく変数にしてしまうと、コンポーネント側で色々とstateを変えたりしているとgetAuthStateの値も変わってしまいますので注意(参照渡しになるので)。 認証の判定にはemailaddressをユーザーIDとして利用する想定なので、こちらに値がセットされているか否かで判定を行なっていきます。 VuexではgettersからStoreのstateにアクセスします。 ここでは、emailaddressがnullでなければtureを返し、nullならfalseを返すメソッドを定義しこの値に応じてcomponentの公開・非公開を制御していきます。 Vuex ではミューテーションをコミットすることでストアの状態を変更できるので、mutationにはstateを更新するメソッドとstateをリセットするメソッドを定義しています。 actionはmutationに似た役割を担っていますが、公式には以下の点が異なると記載されています。 アクションは、状態を変更するのではなく、ミューテーションをコミットします。
アクションは任意の非同期処理を含むことができます。 なのでここではaxiosを使用してAPIでから取得したデータをmutationにコミットすることで間接的にstateを書き換えています。 お次は、ルーティングの設定を行なっていきます。 Modes and Environment Variables | Vue CLI 次に、 Vuetifyの設定、ログイン画面、ログイン後の遷移画面、ログイン後の遷移画面から遅延ローディングの実証画面を実装します。 srcフォルダ直下にviewsという名称のフォルダを作成し、その直下に各画面を作成していきます。 Login.vue Home.vue About.vue また、App.vueファイルを以下のように変更します。 router-vueタグの中身がルーティング時に表示されるコンポーネントに置き換えられる形になります。 次に、ログインボタン押下時にバックエンドアプリケーションにPOSTする必要があるため、vue.config.jsにプロキシ設定を追記します。 ここまでの設定で、 ログイン画面→Home画面(Home画面上にバックエンドで作成したAPIの値が取得されていること)→Aboutページへの遷移が実装されていること 未ログイン状態でHome/About画面にアクセスするとログイン画面にリダイレクトされること が達成されていれば完了です。
最後にログアウト機能を実装します。
Home.vueにログアウトボタンを実装します。
templateタグ内に下記のように実装し、 scriptタグ内にlogoutメソッドを定義しstoreにdispatchします。 storeのlogoutメソッドはstateをnullに変更するメソッドとして定義されています。(store/index.jsのコード参照)
そして、stateの値をcomputed(※算術プロパティ)で取得し、watchプロパティでstateの変更を感知したらログインフォームに遷移するように実装します。 ※算術プロパティ あるデータから派生するデータをプロパティとして公開する仕組みで、Vueコンストラクタのオプションオブジェクトの1つです。データそのものに何らかの処理を与えたものをプロパティにしたいときなどに使われます。 これでログアウト機能の実装は完了です。 今回の内容は一旦これで終了です。 ここまで読んで頂きありがとうございます。まえがき
目的
前提
環境
やること
実装
環境構築
npm install vuex
npm install axios
npm install vuetify
npm install sass@~1.32 sass-loader deepmerge -D
git diff
コマンドなどで確認してpackage.jsonにインストールしたパッケージの依存関係が追加されていればOKです。import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import axios from 'axios'
import vuetify from '@/plugins/vuetify'
Vue.config.productionTip = false
axios.defaults.baseURL = '/api';
// Cookieを有効にする。
axios.defaults.withCredentials = true;
Vue.prototype.$axios = axios;
Vue.use(vuetify)
new Vue({
router,
store,
axios,
vuetify,
render: h => h(App)
}).$mount('#app')
説明
Storeの実装
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios"
Vue.use(Vuex);
const getAuthState = () => ({
emailAddress: null
});
export default new Vuex.Store({
state: getAuthState(),
getters: {
isAuthenticated: state => {
return state.emailAddress != null;
},
},
mutations: {
updateId(state, emailAddress) {
state.emailAddress = emailAddress;
},
resetData(state) {
state.emailAddress = null;
},
},
actions: {
login({ commit }, authData) {
axios
.post('/login', {
emailAddress: authData.emailAddress,
password: authData.password,
})
.then(() => {
commit('updateId', authData.emailAddress);
});
},
logout({ commit }) {
axios.post('/logout').then(() => {
commit('resetData');
});
},
},
});
説明
ルーティングの実装
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Login',
component: Login,
meta: {isPublic: true}
},
{
path: '/home',
name: 'Home',
component: Home,
meta: {isPublic: false}
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
meta: {isPublic: false}
}
]
const router = new VueRouter({
routes,
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 }
},
})
router.beforeEach((to, from, next) => {
// 非公開コンポーネントで未ログインの場合ログイン画面にリダイレクト
if (
to.matched.some(
record => (record.meta.isPublic || Store.getters.isAuthenticated)
)
) {
next();
} else {
next({ path: "/login", query: { redirect: to.fullPath } });
}
});
export default router
説明
ログイン・ログアウトの実装
<template>
<div>
<v-main>
<v-card :tile="$vuetify.breakpoint.sm || $vuetify.breakpoint.xs" elevation="2" class="mx-auto fill-width" flat max-width="540" height="300">
<div class="pt-6">
<v-card-title>ログイン</v-card-title>
<div class="forms">
<v-text-field v-model="emailAddress" :rules="[emailRules.required, emailRules.regex]" autofocus dense height="48px" outlined placeholder="メールアドレスを入力してください"></v-text-field>
<v-text-field v-model="password" :append-icon="passwordShow ? 'mdi-eye' : 'mdi-eye-off'" :rules="[passwordRules.required]" :type="passwordShow ? 'text' : 'password'" dense height="48px" name="input-password" outlined placeholder="パスワードを入力してください" @click:append="passwordShow = !passwordShow"></v-text-field>
</div>
<v-btn @click="login" color="#7b68ee">ログイン</v-btn>
</div>
</v-card>
</v-main>
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
emailAddress: '',
password: '',
emailRules: {
required: value =>
!!value || 'メールアドレスは必須です',
regex: value =>
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
) || 'メールアドレスの形式が違います'
},
passwordShow: false,
passwordRules: {
required: value =>
!!value || 'パスワードは必須です',
}
};
},
methods: {
login() {
this.$store.dispatch('login', {
emailAddress: this.emailAddress,
password: this.password
});
}
},
computed: {
isLogedIn() {
return this.$store.state.emailAddress
}
},
watch: {
isLogedIn() {
this.$router.push('/home').catch(err => console.log(`エラー:${err}`));
}
}
};
</script>
<style scoped>
.fill-width {
top: 50%;
text-align: center;
}
.error--text {
color: red !important;
}
.forms {
width: 80%;
margin:0 auto;
}
</style>
<template>
<div>
<h1>Home画面です</h1>
<router-link to="/about">about</router-link>
<p>API取得値:{{ text }}</p>
</div>
</template>
<script>
export default {
name: 'Home',
data(){
return{
text: null
}
},
created() {
this.getApi();
},
methods: {
getApi() {
const _this = this;
this.$axios.get('/home')
.then(response => {
_this.text = response.data
})
.catch(error => {
_this.text = error
})
}
},
}
</script>
<template>
<div class="about">
<h1>This is an about page</h1>
<router-link to="/home">back to Home</router-link>
</div>
</template>
<script>
export default {
name: 'About',
components: {
},
}
</script>
<template>
<v-app>
<v-main>
<router-view/>
</v-main>
</v-app>
</template>
<script>
export default {
name: 'App',
};
</script>
devServer: {
proxy: {
'^/api': {
target: 'http://localhost:8080/',
ws: true,
changeOrigin: true
}
}
<v-btn @click="logout" color="#7b68ee">ログアウト</v-btn>
logout() {
this.$store.dispatch('logout')
}
computed: {
isLogout() {
return this.$store.state.emailAddress
}
},
watch: {
isLogout() {
this.$router.push('/');
}
}
参考