タイトル通りNuxt.jsとVuetifyで以下の様なログイン画面を実装します。実装するのは見た目だけで認証処理には触れません。
環境
バージョンは以下になります。
- @nuxt/cli v2.15.8
- vuetify@2.5.9
プロジェクト作成
Nuxtのプロジェクトは以下の設定で作成しました。Vuetifyも一緒に用意しています。
テキストフィールドのコンポーネント化
メールアドレスとパスワードを入力するテキストフィールドを用意します。これらのテキストフィールドはログイン画面以外でも使いまわせそうなのでコンポーネントとして作成します。
Componentsフォルダの中にTextFieldComponentsという名前でフォルダを作成し、EmailField.vueとPasswordField.vueという名前でファイルを作成します。
まずはシンプルにpropsで親コンポーネントから受け取る値をv-text-fieldに引き渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<template> <v-text-field v-model="email" label="メールアドレス"> </v-text-field> </template> <script> export default { props: { email: { type: String, default: "", required: true, }, }, }; </script> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<template> <v-text-field v-model="password" label="パスワード"> </v-text-field> </template> <script> export default { props: { password: { type: String, default: "", required: true, }, }, }; </script> |
画面の確認
作成したテキストフィールドを試しに画面表示してみます。pagesフォルダにlogin.vueファイルを作成します。
login.vueを次の様に編集します。先ほど作成したテキストフィールドを表示しているだけの画面です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<template> <v-form> <EmailField :email="email" /> <PasswordField :password="password" /> </v-form> </template> <script> import EmailField from "~/components/TextFieldComponents/EmailField.vue"; import PasswordField from "~/components/TextFieldComponents/PasswordField.vue"; export default { data() { return { email: "", password: "", }; }, components: { EmailField, PasswordField }, }; </script> |
見た目はこんな感じになります。
この状態でテキストフィールドに何か値を入力すると「[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "email"」と以下の様な警告がDevToolsに表示されています。
propsの値を子コンポーネントの中から変更しないでね、と言われていますのでこれに対応します。
警告の対応
子コンポーネントで直接変更するのではなく、変更があったら親コンポーネントへ通知(emit)するようにします。また、v-modelだとv-text-fieldで変更があった場合に値を更新しようとしてしまうため:valueに変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<template> <v-text-field :value="email" label="メールアドレス" @input="$emit('update:email', $event)" > </v-text-field> </template> <script> export default { props: { email: { type: String, default: "", required: true, }, }, }; </script> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<template> <v-text-field :value="password" label="パスワード" @input="$emit('update:password', $event)" > </v-text-field> </template> <script> export default { props: { password: { type: String, default: "", required: true, }, }, }; </script> |
親コンポーネントでは.sync修飾子を付けて、子コンポーネントからの更新通知を自分の持ってるデータに反映させます。
参考 カスタムイベント — Vue.jsカスタムイベント — Vue.js以下の様なコードになります。5行目はただの確認用です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<template> <v-form> <EmailField :email.sync="email" /> <PasswordField :password.sync="password" /> {{ this.email }},{{ this.password }} </v-form> </template> <script> import EmailField from "~/components/TextFieldComponents/EmailField.vue"; import PasswordField from "~/components/TextFieldComponents/PasswordField.vue"; export default { data() { return { email: "", password: "", }; }, components: { EmailField, PasswordField }, }; </script> |
ちゃんと各データが反映されていることが確認できます。
メールアドレス入力欄のお化粧
少しお化粧しておきます。入力欄の前方にメールのアイコンを表示するようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<template> <v-text-field :value="email" label="メールアドレス" prepend-icon="mdi-mail" @input="$emit('update:email', $event)" > </v-text-field> </template> <script> export default { props: { email: { type: String, default: "", required: true, }, }, }; </script> |
パスワード入力欄のお化粧
パスワード入力欄もお化粧していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<template> <v-text-field :value="password" label="パスワード" prepend-icon="mdi-lock" :append-icon="show ? 'mdi-eye' : 'mdi-eye-off'" :type="show ? 'text' : 'password'" hint="最小8文字" counter @click:append="show = !show" @input="$emit('update:password', $event)" > </v-text-field> </template> <script> export default { props: { password: { type: String, default: "", required: true, }, }, data() { return { show: false, }; }, }; </script> |
こんな感じの見た目になります。
5行目:prepend-icon="mdi-lock"
入力欄の前方に鍵のアイコンを表示しています。
6行目::append-icon="show ? 'mdi-eye' : 'mdi-eye-off'"
入力欄の後方に目のアイコンを表示しています。下図のようにshowの値に応じてアイコンが切り替わるようになっています。
7行目::type="show ? 'text' : 'password'"
上図のようにshowの値に応じて入力欄をマスクするか指定してします。
8行目:hint="最小8文字"
フォーカスされた時に表示するメッセージを指定しています。
9行目:counter
入力欄右下に入力文字数が表示されるようになります。
10行目:@click:append="show = !show"
append要素がクリックされたらshowの値のtrue/falseを切り替えます。
24~28行目
変数showを定義しています。
フォームのコンポーネント化
ログインフォームを作っていきます。再利用できるようにコンポーネントとして作成します。
componentsフォルダにFormComponentsというフォルダを作成し、LoginForm.vueという名前でファイルを作成します。
中身を次の様に編集します。v-cardでレイアウトしている以外は先ほどのlogin.vueとほとんど同じですので一部のみ補足します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<template> <v-form> <v-card width="400px"> <!-- タイトル --> <v-card-title class="headline">ログイン</v-card-title> <!-- 入力欄 --> <v-card-text> <EmailField :email.sync="email" /> <PasswordField :password.sync="password" /> </v-card-text> <!-- アクションボタン --> <v-card-actions> <v-btn @click="submit">ログイン</v-btn> </v-card-actions> </v-card> </v-form> </template> <script> import EmailField from "~/components/TextFieldComponents/EmailField.vue"; import PasswordField from "~/components/TextFieldComponents/PasswordField.vue"; export default { components: { EmailField, PasswordField }, data() { return { email: "", password: "", }; }, methods: { submit() { console.log(this.email + "," + this.password); }, }, }; </script> |
15行目:<v-btn @click="submit">ログイン</v-btn>
ログインボタンを配置し、クリックイベントでsubmitメソッドを呼んでいます。
32~36行目
15行目で呼ばれるsubmitメソッドが定義されています。ここでサーバーへの認証要求を投げることになりますがここではそこまで実装しません。
ログイン画面からログインフォームコンポーネントを使う
作成したLoginForm.vueをlogin.vueから利用します。コンポーネントを配置するだけです。
1 2 3 4 5 6 7 8 9 10 |
<template> <LoginForm /> </template> <script> import LoginForm from "~/components/FormComponents/LoginForm.vue"; export default { components: { LoginForm }, }; </script> |
見た目はこんな感じです。ここからレイアウトや背景画像を編集していきます。
レイアウトの編集
ログイン画面からメニューバーは取り除こうと思います。そのためオリジナルのレイアウトファイルを作成します。layoutsフォルダにLoginLayout.vueという名前でファイルを作成します。
中身は次の様に編集します。5行目でfill-heightを指定することで上下中央寄せしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<template> <v-app dark> <span class="bg"></span> <v-main> <v-container fill-height> <Nuxt /> </v-container> </v-main> </v-app> </template> <style scoped> .bg { width: 100%; height: 100%; position: absolute; top: 0; left: 0; background: url("https://cdn.pixabay.com/photo/2016/03/26/13/09/organic-1280537_1280.jpg") no-repeat center center; background-size: cover; background-color: red; } </style> |
vuetifyで背景画像を設定する方法が見つからないので次のページのコードをコピペしました。
レイアウトの適用
作成したレイアウトをlogin.vueから使用するように指定します。
1 2 3 4 5 6 7 8 9 10 11 |
<template> <LoginForm class="mx-auto" /> </template> <script> import LoginForm from "~/components/FormComponents/LoginForm.vue"; export default { layout: "LoginLayout", components: { LoginForm }, }; </script> |
2行目:<LoginForm class="mx-auto" />
mx-autoで中央寄せしています。
8行目:layout: "LoginLayout",
作成したレイアウトを使うよう指定しています。
仕上げ
こんな感じの見た目に仕上がりました。darkテーマのままなのでnuxt.config.jsでテーマをdark:falseにします。
darkをfalseにセット。
白っぽくなりました。
最後に
本来であれば入力値のチェックをしなければならないですがそこは次の記事でやろうと思います。