Nuxt.jsでお問い合わせフォームを作成する

Nuxt.jsを使って、確認画面を挟むタイプのお問い合わせフォームの処理を実装してみます。

フォーム入力画面の作成

まずはフォーム入力画面を作成します。
/pages/contact/index.vueを作成して、templateを以下のようにします。

<template>
  <div class="p-contact">
    <h1>お問い合わせ</h1>
    <form v-on:submit.prevent="submit">
      <div class="c-form-group">
        <div class="c-form-group__key">名前</div>
        <div class="c-form-group__val">
          <input type="text" name="name" class="c-input-text" v-model="contact.name">
        </div>
      </div>
      <div class="c-form-group">
        <div class="c-form-group__key">性別</div>
        <div class="c-form-group__val">
          <label for="man">
            <input
              type="radio"
              name="gender"
              value="男性"
              id="man"
              class="c-input-radio"
              v-model="contact.gender"
            >
            男性
          </label>
          <label for="woman">
            <input
              type="radio"
              name="gender"
              value="女性"
              id="woman"
              class="c-input-radio"
              v-model="contact.gender"
            >
            女性
          </label>
        </div>
      </div>
      <div class="c-form-group">
        <div class="c-form-group__key">メールアドレス</div>
        <div class="c-form-group__val">
          <input type="email" name="mail" class="c-input-text" v-model="contact.mail">
        </div>
      </div>
      <div class="c-form-group">
        <div class="c-form-group__key">お問い合わせ種別</div>
        <div class="c-form-group__val">
          <select class="c-select" name="type" v-model="contact.type">
            <option value="">選択してください</option>
            <option value="商品について">商品について</option>
            <option value="求人について">求人について</option>
            <option value="その他">その他</option>
          </select>
        </div>
      </div>
      <div class="c-form-group">
        <div class="c-form-group__key">お問い合わせ内容</div>
        <div class="c-form-group__val">
          <textarea name="body" class="c-textarea" v-model="contact.body"></textarea>
        </div>
      </div>
      <div class="c-form-group">
        <div class="c-form-group__key">サイトを訪問した経緯</div>
        <div class="c-form-group__val">
          <label for="search">
            <input
              type="checkbox"
              name="route"
              value="検索"
              id="search"
              class="c-input-check"
              v-model="contact.route"
            >
            検索
          </label>
          <label for="sns">
            <input
              type="checkbox"
              name="route"
              value="SNS"
              id="sns"
              class="c-input-check"
              v-model="contact.route"
            >
            SNS
          </label>
          <label for="other">
            <input
              type="checkbox"
              name="route"
              value="紹介"
              id="other"
              class="c-input-check"
              v-model="contact.route"
            >
            紹介
          </label>
        </div>
      </div>
      <button type="submit" class="c-btn">確認</button>
    </form>
  </div>
</template>

フォームで使用するdata()と、送信時の仮処理を用意します。

export default {
  data() {
    return {
      contact: {
        name: '',
        gender: null,
        mail: '',
        type: '',
        body: '',
        route: []
      }
    }
  },
  methods: {
    submit() {
      console.log(this.contact);
    }
  }
}

これで入力画面の準備が完了しました。

Vuexへの保存

このフォームで入力した内容を確認画面に反映するために、入力データをVuexに保存します。
/store/contact/index.jsを作成して、以下のような内容にします。

export const state = () => ({
  name: '',
  gender: null,
  mail: '',
  type: '',
  body: '',
  route: [],
})

export const mutations = {
  add(state, payload) {
    state.name = payload;
    state.gender = payload;
    state.mail = payload;
    state.type = payload;
    state.body = payload;
    state.route = payload;
  },
}

export const actions = {
  addAction({commit, dispatch, state}, payload) {
    commit('remove');
    commit('add',payload);
  },
}

stateの値更新の際、mutationsを直接ではなくactionsを経由して更新する想定にしています。

入力画面でのフォーム送信時に、Vuexへの保存と確認画面への遷移追加を行うように設定を追加してみます。

export default {
  data() {
    return {
      contact: {
        name: '',
        gender: null,
        mail: '',
        type: '',
        body: '',
        route: []
      }
    }
  },
  methods: {
    submit() {
      console.log(this.contact);
      // storeに保存
      this.$store.dispatch(`contact/addAction`, this.contact);
      // 確認画面に遷移
      this.$router.push('/contact/confirm/');
    }
  }
}

17〜20行目が追加部分になります。
これで入力画面でのフォーム送信時に、データが保存されるようになりました。

確認画面の作成

次に確認画面の作成ですが、確認画面ではVuesに保存したデータを取得する必要があります。
現在はその処理を追加していないので、/store/contact/index.jsに追加しておきます。

export const state = () => ({
  name: '',
  gender: null,
  mail: '',
  type: '',
  body: '',
  route: [],
})

export const mutations = {
  add(state, payload) {
    state.name = payload;
    state.gender = payload;
    state.mail = payload;
    state.type = payload;
    state.body = payload;
    state.route = payload;
  },
}

export const getters = {
  getContact(state) {
    return {
      name: state.name,
      gender: state.gender,
      mail: state.mail,
      type: state.type,
      body: state.body,
      route: state.route,
    };
  },
}

export const actions = {
  addAction({commit, dispatch, state}, payload) {
    commit('remove');
    commit('add',payload);
  },
}

21〜32行目のgettersを追加しました。
これでVuexからデータを取得する準備ができたので、確認画面を作成します。

/pages/contact/confirm.vueを作成して、templateを以下のようにします。

<template>
  <div class="p-contact">
    <h1>お問い合わせ</h1>
    <form v-on:submit.prevent="submit">
      <div class="c-form-group">
        <div class="c-form-group__key">名前</div>
        <div class="c-form-group__val">{{contact.name}}</div>
      </div>
      <div class="c-form-group">
        <div class="c-form-group__key">性別</div>
        <div class="c-form-group__val">{{contact.gender}}</div>
      </div>
      <div class="c-form-group">
        <div class="c-form-group__key">メールアドレス</div>
        <div class="c-form-group__val">{{contact.mail}}</div>
      </div>
      <div class="c-form-group">
        <div class="c-form-group__key">お問い合わせ種別</div>
        <div class="c-form-group__val">{{contact.type}}</div>
      </div>
      <div class="c-form-group">
        <div class="c-form-group__key">お問い合わせ内容</div>
        <div class="c-form-group__val">{{contact.body}}</div>
      </div>
      <div class="c-form-group">
        <div class="c-form-group__key">サイトを訪問した経緯</div>
        <div class="c-form-group__val">
          <span v-for="item in contact.route">{{item}}</span>
        </div>
      </div>
      <button type="submit" class="c-btn">送信</button>
      <NuxtLink to="/contact/">修正</NuxtLink>
    </form>
  </div>
</template>

先ほど作成したVuexからの取得を使って、各値を表示できるようにしてみます。

export default {
  data() {
    return {
      contact: {
        name: '',
        gender: null,
        mail: '',
        type: '',
        body: '',
        route: []
      }
    }
  },
  created() {
    const getContactData = this.$store.getters['contact/getContact'];
    this.contact.name = getContactData.name;
    this.contact.gender = getContactData.gender;
    this.contact.mail = getContactData.mail;
    this.contact.type = getContactData.type;
    this.contact.body = getContactData.body;
    this.contact.route = getContactData.route;
  },
  methods: {
    submit() {
      console.log(this.contact);
    }
  }
}

これで確認画面での表示ができました。

確認画面から戻る場合の処理

確認画面に修正ボタンを用意しているので、確認画面から入力画面に戻った時の処理を追加します。
Vuexからのフォームデータの取得は確認画面で用意しているので、その処理をそのまま流用します。

export default {
  data() {
    return {
      contact: {
        name: '',
        gender: null,
        mail: '',
        type: '',
        body: '',
        route: []
      }
    }
  },
  created() {
    const getContactData = this.$store.getters['contact/getContact'];
    this.contact.name = getContactData.name;
    this.contact.gender = getContactData.gender;
    this.contact.mail = getContactData.mail;
    this.contact.type = getContactData.type;
    this.contact.body = getContactData.body;
    this.contact.route = getContactData.route;
  },
  methods: {
    submit() {
      console.log(this.contact);
      // storeに保存
      this.$store.dispatch(`contact/addAction`, this.contact);
      // 確認画面に遷移
      this.$router.push('/contact/confirm/');
    }
  }
}

これで既にフォームデータ保存されている場合はフォームに値がセットされるようになりました。

確認画面からフォームを送信

確認画面からフォームを送信した後、保存しているデータを削除しておかないと、再度お問い合わせにアクセスした際に以前の入力値がフォームにセットされてしまいます。
そのためまずは、/store/contact/index.jsに保存したデータを削除する処理を追加します。

export const state = () => ({
  name: '',
  gender: null,
  mail: '',
  type: '',
  body: '',
  route: []
})

export const mutations = {
  add(state, payload) {
    state.name = payload.name;
    state.gender = payload.gender;
    state.mail = payload.mail;
    state.type = payload.type;
    state.body = payload.body;
    state.route = payload.route;
  },
  remove(state) {
    state.name = '';
    state.gender = null;
    state.mail = '';
    state.type = '';
    state.body = '';
    state.route = [];
  },
}

export const getters = {
  getContact(state) {
    return {
      name: state.name,
      gender: state.gender,
      mail: state.mail,
      type: state.type,
      body: state.body,
      route: state.route,
      flag: state.flag
    };
  },
}

export const actions = {
  addAction({commit, dispatch, state}, payload) {
    commit('remove');
    commit('add',payload);
  },
  removeAction({commit, dispatch, state}, payload) {
    commit('remove');
  }
}

データ保存時と同じく、actionsを経由しての削除になります。

これでデータの削除もできるようになったので、確認画面からフォームを送信した際の処理を追加します。

export default {
  data() {
    return {
      contact: {
        name: '',
        gender: null,
        mail: '',
        type: '',
        body: '',
        route: []
      }
    }
  },
  created() {
    const getContactData = this.$store.getters['contact/getContact'];
    this.contact.name = getContactData.name;
    this.contact.gender = getContactData.gender;
    this.contact.mail = getContactData.mail;
    this.contact.type = getContactData.type;
    this.contact.body = getContactData.body;
    this.contact.route = getContactData.route;
  },
  methods: {
    submit() {
      console.log(this.contact);
      // 送信後、storeに保存していたデータを破棄
      this.$store.dispatch('contact/removeAction');
      // 確認画面に遷移
      this.$router.push('/contact/complete/');
    }
  }
}

今回はサンプルの作成なので、実際には送信していません。
完了画面はpage/contact/complete.vueに遷移するように設定していますので、ページを作成してください。
今回処理は特にしないので、記事上は省略します。

これでフォームの一連の流れが一応完成しました。

確認画面に直接アクセスした場合の対応

現状だと確認画面に直接アクセスできてしまうので、入力画面からの流れでない場合は入力画面にリダイレクトするようにしてみます。
フォームの入力データが保存されているかでもよいのですが、どの項目が必須かが場合によってまちまちになりそうなので、確認用のフラグをVuexに追加して対応します。

export const state = () => ({
  name: '',
  gender: null,
  mail: '',
  type: '',
  body: '',
  route: [],
  flag: false
})

export const mutations = {
  add(state, payload) {
    state.name = payload.name;
    state.gender = payload.gender;
    state.mail = payload.mail;
    state.type = payload.type;
    state.body = payload.body;
    state.route = payload.route;
    state.flag = true;
  },
  remove(state) {
    state.name = '';
    state.gender = null;
    state.mail = '';
    state.type = '';
    state.body = '';
    state.route = [];
    state.flag = false;
  },
}

export const getters = {
  getContact(state) {
    return {
      name: state.name,
      gender: state.gender,
      mail: state.mail,
      type: state.type,
      body: state.body,
      route: state.route,
      flag: state.flag
    };
  },
}

export const actions = {
  addAction({commit, dispatch, state}, payload) {
    commit('remove');
    commit('add',payload);
  },
  removeAction({commit, dispatch, state}, payload) {
    commit('remove');
  }
}

flagという項目を追加して、入力画面でフォーム送信時にtrue、確認画面でフォーム送信後にfalseにするように設定しました。
確認画面でflagがfalseの場合は入力画面を経由していないことになるので、その場合にリダイレクトするようにします。

export default {
  data() {
    return {
      contact: {
        name: '',
        gender: null,
        mail: '',
        type: '',
        body: '',
        route: []
      }
    }
  },
  created() {
    const getContactData = this.$store.getters['contact/getContact'];
    if(!getContactData.flag) {
      this.$nuxt.context.redirect('/contact/');
    }
    this.contact.name = getContactData.name;
    this.contact.gender = getContactData.gender;
    this.contact.mail = getContactData.mail;
    this.contact.type = getContactData.type;
    this.contact.body = getContactData.body;
    this.contact.route = getContactData.route;
  },
  methods: {
    submit() {
      console.log(this.contact);
      // 送信後、storeに保存していたデータを破棄
      this.$store.dispatch('contact/removeAction');
      // 確認画面に遷移
      this.$router.push('/contact/complete/');
    }
  }
}

バリデーションの設定

最後にVeeValidateを使ってバリデーション処理を追加してみます。
詳しい設定は以前記事を投稿していますので、そちらをご確認ください。

/plugins/vee-validate.jsの内容を以下のようにします。

import Vue from 'vue'
import { ValidationProvider, ValidationObserver, extend, localize } from 'vee-validate'
import { required, email } from 'vee-validate/dist/rules';
import ja from 'vee-validate/dist/locale/ja.json';

localize('ja', ja);

extend('required', required);
extend('email', email);

Vue.component('ValidationProvider', ValidationProvider);
Vue.component('ValidationObserver', ValidationObserver);

入力画面のtemplateにValidationProviderとValidationObserverを追加します。

<template>
  <div class="p-contact">
    <h1>お問い合わせ</h1>
    <validationObserver v-slot="{ invalid, handleSubmit }">
      <form v-on:submit.prevent="submit">
        <div class="c-form-group">
          <div class="c-form-group__key">名前</div>
          <div class="c-form-group__val">
            <ValidationProvider name="name" rules="required" v-slot="{ errors }">
              <input type="text" name="name" class="c-input-text" v-model="contact.name">
              <div class="error">{{ errors[0] }}</div>
            </ValidationProvider>
          </div>
        </div>
        <div class="c-form-group">
          <div class="c-form-group__key">性別</div>
          <div class="c-form-group__val">
            <ValidationProvider name="gender" rules="required" v-slot="{ errors }">
              <label for="man">
                <input
                  type="radio"
                  name="gender"
                  value="男性"
                  id="man"
                  class="c-input-radio"
                  v-model="contact.gender"
                >
                男性
              </label>
              <label for="woman">
                <input
                  type="radio"
                  name="gender"
                  value="女性"
                  id="woman"
                  class="c-input-radio"
                  v-model="contact.gender"
                >
                女性
              </label>
              <div class="error">{{ errors[0] }}</div>
            </ValidationProvider>
          </div>
        </div>
        <div class="c-form-group">
          <div class="c-form-group__key">メールアドレス</div>
          <div class="c-form-group__val">
            <ValidationProvider name="mail" rules="required|email" v-slot="{ errors }">
              <input type="email" name="mail" class="c-input-text" v-model="contact.mail">
              <div class="error">{{ errors[0] }}</div>
            </ValidationProvider>
          </div>
        </div>
        <div class="c-form-group">
          <div class="c-form-group__key">お問い合わせ種別</div>
          <div class="c-form-group__val">
            <ValidationProvider name="type" rules="required" v-slot="{ errors }">
              <select class="c-select" name="type" v-model="contact.type">
                <option value="">選択してください</option>
                <option value="商品について">商品について</option>
                <option value="求人について">求人について</option>
                <option value="その他">その他</option>
              </select>
              <div class="error">{{ errors[0] }}</div>
            </ValidationProvider>
          </div>
        </div>
        <div class="c-form-group">
          <div class="c-form-group__key">お問い合わせ内容</div>
          <div class="c-form-group__val">
            <ValidationProvider name="body" rules="required" v-slot="{ errors }">
              <textarea name="body" class="c-textarea" v-model="contact.body"></textarea>
              <div class="error">{{ errors[0] }}</div>
            </ValidationProvider>
          </div>
        </div>
        <div class="c-form-group">
          <div class="c-form-group__key">サイトを訪問した経緯</div>
          <div class="c-form-group__val">
            <ValidationProvider name="route" rules="required" v-slot="{ errors }">
              <label for="search">
                <input
                  type="checkbox"
                  name="route"
                  value="検索"
                  id="search"
                  class="c-input-check"
                  v-model="contact.route"
                >
                検索
              </label>
              <label for="sns">
                <input
                  type="checkbox"
                  name="route"
                  value="SNS"
                  id="sns"
                  class="c-input-check"
                  v-model="contact.route"
                >
                SNS
              </label>
              <label for="other">
                <input
                  type="checkbox"
                  name="route"
                  value="その他"
                  id="other"
                  class="c-input-check"
                  v-model="contact.route"
                >
                その他
              </label>
              <div class="error">{{ errors[0] }}</div>
            </ValidationProvider>
          </div>
        </div>
        <button type="submit" class="c-btn" :disabled="invalid">確認</button>
      </form>
    </validationObserver>
  </div>
</template>

これでお問い合わせフォームの一連の処理を実装することができました。

参考サイト

このエントリーをはてなブックマークに追加

関連記事

コメントを残す

メールアドレスが公開されることはありません。
* が付いている欄は必須項目です

CAPTCHA


コメントが承認されるまで時間がかかります。

2024年4月
 123456
78910111213
14151617181920
21222324252627
282930