Vue Routerを使ってお問い合わせフォームを作成しよう

本記事では、Vue.jsのVue Routerを使用して、3ステップ構成のお問い合わせフォームを作成する方法を紹介します。Vue Routerを利用することで、画面ごとにURLを分けて直感的な操作性を実現します。


使用技術とバージョン情報

  • Vue.js: 3.x (CDN経由で最新バージョンを使用)
  • Vue Router: 4.x (CDN経由で最新バージョンを使用)
  • Tailwind CSS: 3.x (CDN経由で最新バージョンを使用)
  • PHP: 8.2.22
  • サーバー環境: エックスサーバー

Vue Routerを利用するメリット

  1. 直感的なナビゲーション:
  • URLごとに画面を管理できるため、ブラウザの「戻る」「進む」ボタンを活用可能。
  1. コードの分離と再利用性:
  • 各画面を独立したコンポーネントとして作成することで、コードが整理されメンテナンス性が向上。
  1. 柔軟なルーティング:
  • 動的パラメータやクエリ文字列を使った高度なルーティングが可能。
  1. SEO対応:
  • サーバーサイドレンダリング(SSR)にも対応できるため、SEOに有利。

Vue Routerの動きの解説

Vue Routerは、Vue.jsでシングルページアプリケーション (SPA) を実現するためのライブラリです。以下の特徴があります。

  1. ルート管理:
  • 各URLに対応するコンポーネントを定義し、動的に切り替え。
  1. データの引き継ぎ:
  • 入力内容を確認画面や完了画面に引き継ぐ際に、localStoragequeryパラメータを活用。
  1. ブラウザ履歴との統合:
  • createWebHistoryを利用して、ブラウザの前進・後退ボタンをサポート。

フォームの特徴

  1. 3ステップ構成:
  • 入力画面: 名前、メールアドレス、お問い合わせ内容を入力。
  • 確認画面: 入力内容を確認し、送信または修正が可能。
  • 完了画面: 問い合わせ送信完了のメッセージを表示。
  1. ページごとのURL管理:
  • Vue Routerを利用し、以下のURLを設定:
    • http://localhost/ → 入力画面
    • http://localhost/confirm → 確認画面
    • http://localhost/complete → 完了画面
  1. レスポンシブデザイン:
  • Tailwind CSSを使用して、PCやモバイルでも見やすいデザインを実現。

スクリーンショット

入力画面

確認画面

完了画面


フロントエンドの実装

Vue Routerの設定とCDN

Vue RouterをCDNから読み込むために、以下を<head>タグに追加します。

<script src="https://unpkg.com/vue-router@4"></script>

index.html

以下は、Vue Routerを使ったフロントエンドのコードです。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue.js お問い合わせフォーム</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script src="https://unpkg.com/vue@3"></script>
  <script src="https://unpkg.com/vue-router@4"></script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
  <div id="app" class="bg-white shadow-md rounded p-6 w-full max-w-md mx-auto">
    <router-view></router-view>
  </div>
  <script>
    const { createRouter, createWebHistory } = VueRouter;
    const { createApp } = Vue;

    // 入力画面
    const InputForm = {
      template: `
        <div>
          <h1 class="text-2xl font-bold mb-4">お問い合わせフォーム</h1>
          <form @submit.prevent="goToConfirm">
            <div class="mb-4">
              <label for="name" class="block text-sm font-medium text-gray-700">名前:</label>
              <input v-model="formData.name" type="text" id="name" required class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
            </div>
            <div class="mb-4">
              <label for="email" class="block text-sm font-medium text-gray-700">メールアドレス:</label>
              <input v-model="formData.email" type="email" id="email" required class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
            </div>
            <div class="mb-4">
              <label for="message" class="block text-sm font-medium text-gray-700">お問い合わせ内容:</label>
              <textarea v-model="formData.message" id="message" required class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"></textarea>
            </div>
            <button type="submit" class="w-full bg-indigo-600 text-white py-2 px-4 rounded hover:bg-indigo-700">確認画面へ</button>
          </form>
        </div>
      `,
      data() {
        return {
          formData: JSON.parse(localStorage.getItem('formData')) || { name: '', email: '', message: '' },
        };
      },
      methods: {
        goToConfirm() {
          localStorage.setItem('formData', JSON.stringify(this.formData));
          this.$router.push({ name: 'confirm' });
        },
      },
    };

    // 確認画面
    const ConfirmForm = {
      template: `
        <div>
          <h1 class="text-2xl font-bold mb-4">内容の確認</h1>
          <p class="mb-2"><strong>名前:</strong> {{ formData.name }}</p>
          <p class="mb-2"><strong>メールアドレス:</strong> {{ formData.email }}</p>
          <p class="mb-4"><strong>お問い合わせ内容:</strong> {{ formData.message }}</p>
          <div class="flex space-x-4">
            <button @click="goBack" class="w-1/2 bg-gray-600 text-white py-2 px-4 rounded hover:bg-gray-700">戻る</button>
            <button @click="sendForm" class="w-1/2 bg-indigo-600 text-white py-2 px-4 rounded hover:bg-indigo-700">送信する</button>
          </div>
        </div>
      `,
      data() {
        return {
          formData: JSON.parse(localStorage.getItem('formData')) || { name: '', email: '', message: '' },
        };
      },
      methods: {
        goBack() {
          this.$router.push({ name: 'input' });
        },
        sendForm() {
          fetch('backend/contact.php', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(this.formData),
          })
            .then((response) => response.json())
            .then((data) => {
              if (data.success) {
                localStorage.removeItem('formData');
                this.$router.push({ name: 'complete' });
              } else {
                alert('送信に失敗しました: ' + data.error);
              }
            })
            .catch(() => alert('エラーが発生しました。通信環境を確認してください。'));
        },
      },
    };

    // 完了画面
    const CompleteForm = {
      template: `
        <div>
          <h1 class="text-2xl font-bold mb-4">送信が完了しました</h1>
          <p>お問い合わせいただきありがとうございます。</p>
          <button @click="goToInput" class="mt-4 w-full bg-indigo-600 text-white py-2 px-4 rounded hover:bg-indigo-700">フォームに戻る</button>
        </div>
      `,
      methods: {
        goToInput() {
          this.$router.push({ name: 'input' });
        },
      },
    };

    const routes = [
      { path: '/', name: 'input', component: InputForm },
      { path: '/confirm', name: 'confirm', component: ConfirmForm },
      { path: '/complete', name: 'complete', component: CompleteForm },
    ];

    const router = createRouter({
      history: createWebHistory(),
      routes,
    });

    const App = {
      template: '<router-view></router-view>',
    };

    const app = createApp(App);
    app.use(router);
    app.mount('#app');
  </script>
</body>
</html>

バックエンドの実装

以下は、contact.phpのコードです。フロントエンドから送信されたデータを受け取り、メールを送信します。

contact.php

<?php
header('Content-Type: application/json');

// リクエストデータの取得
$data = json_decode(file_get_contents('php://input'), true);

// 入力チェック
if (empty($data['name']) || empty($data['email']) || empty($data['message'])) {
    echo json_encode(['success' => false, 'error' => '入力内容が不完全です。']);
    exit;
}

// 管理者への通知メール
$toAdmin = 'admin@example.com';
$subjectAdmin = 'お問い合わせフォームからのメッセージ';
$messageAdmin = "名前: {$data['name']}\nメールアドレス: {$data['email']}\nメッセージ: {$data['message']}";
$headersAdmin = 'From: noreply@example.com';

$mailToAdmin = mail($toAdmin, $subjectAdmin, $messageAdmin, $headersAdmin);

// 問い合わせ者への自動返信メール
$toUser = $data['email'];
$subjectUser = 'お問い合わせありがとうございます';
$messageUser = "{$data['name']} 様\n\nお問い合わせありがとうございます。\n以下の内容で受け付けました。\n----------------------\nお名前: {$data['name']}\nメールアドレス: {$data['email']}\nお問い合わせ内容:\n{$data['message']}\n----------------------\n\n担当者より折り返しご連絡いたします。";
$headersUser = 'From: noreply@example.com';

$mailToUser = mail($toUser, $subjectUser, $messageUser, $headersUser);

if ($mailToAdmin && $mailToUser) {
    echo json_encode(['success' => true]);
} else {
    echo json_encode(['success' => false, 'error' => 'メール送信に失敗しました。']);
}

まとめ

この記事では、Vue Routerを活用してURLごとに画面を切り替えるお問い合わせフォームの作成方法を解説しました。Vue Routerを利用することで、直感的なナビゲーションやコードの分離を実現できます。


関連記事

コメント

タイトルとURLをコピーしました