vue3(パンくず)

<Breadcrumb :pageNames="['集会一覧', '集会詳細', '演題一覧', '演題新規登録']" />
// 子コンポーネント
<script lang="ts">
export interface Params {
  meetingId: string;
  emailSendingHistoryId: string;
  mailTemplateId: string;
  reviewRequestId: string;
  reviewId: string;
  historyId: string;
  administratorId: string;
}
</script>

<script setup lang="ts">
import { computed, PropType } from 'vue';
import { Link, usePage } from '@inertiajs/vue3';

// 📌 `params` を `usePage().props.params` から取得し、分割代入
const {
  meetingId,
  emailSendingHistoryId,
  mailTemplateId,
  reviewRequestId,
  reviewId,
  historyId,
  administratorId
} = usePage().props.params as Params;

// `pageNames`(ページ名の配列)を受け取る
const props = defineProps({
  pageNames: {
    type: Array as PropType<string[]>,
    required: true,
  },
});

// 📌 `breadcrumbMap` を外部に定義し、パラメータに `params` の値をセット
const breadcrumbMap: Record<string, { routeName: string; params?: string[] }> = {
  集会一覧: { routeName: 'meeting.index' },
  集会登録: { routeName: 'meeting.create' },
  集会詳細: { routeName: 'meeting.show', params: [meetingId] },
  集会編集: { routeName: 'meeting.edit', params: [meetingId] },
  演題一覧: { routeName: 'abstract.index', params: [meetingId] },
  演題登録: { routeName: 'abstract.bulkRegistration.show', params: [meetingId] },
  管理者一覧: { routeName: 'administrator.index' },
  管理者詳細: { routeName: 'administrator.show', params: [administratorId] },
  管理者登録: { routeName: 'administrator.create' },
  メール送信履歴: { routeName: 'email.history.index', params: [meetingId] },
  メール送信履歴詳細: { routeName: 'email.history.show', params: [meetingId, emailSendingHistoryId] },
  メール管理: { routeName: 'email.emailTemplate.index' },
  メールテンプレート詳細: { routeName: 'email.emailTemplate.show', params: [mailTemplateId] },
  メールテンプレート編集: { routeName: 'email.emailTemplate.edit', params: [mailTemplateId] },
  査読依頼一覧: { routeName: 'reviewRequest.index', params: [meetingId] },
  査読依頼詳細: { routeName: 'reviewRequest.show', params: [meetingId, reviewRequestId] },
  査読割設定: { routeName: 'reviewRequest.create', params: [meetingId] },
  査読再割付: { routeName: 'reviewRequest.recreate', params: [meetingId] },
  査読依頼メール送信: { routeName: 'reviewRequest.emailSending.create', params: [meetingId, reviewRequestId] },
  査読詳細: { routeName: 'review.show', params: [meetingId, reviewRequestId, reviewId] },
  査読編集: { routeName: 'review.edit', params: [meetingId, reviewRequestId, reviewId] },
  査読可能領域更新依頼メール送信: { routeName: 'reviewableArea.requestUpdate.create' },
  査読可能領域メール送信履歴: { routeName: 'reviewableArea.requestUpdate.history.index' },
  査読可能領域メール送信履歴詳細: { routeName: 'reviewableArea.requestUpdate.history.show', params: [historyId] },
  集会分析: { routeName: 'meetingAnalysis.index', params: [meetingId] },
  マスタ管理: { routeName: 'masters.index' },
  キーワード一覧: { routeName: 'masters.keywords.index' },
  セッション割: { routeName: 'session.index', params: [meetingId] },
  集会担当組織: { routeName: 'masters.meetingOrganizations.index' },
  採択結果通知メール送信: { routeName: 'abstract.emailSending.create', params: [meetingId] },
  キーワード編集: { routeName: 'masters.keywords.edit' },
  演題登録フォーム一覧: { routeName: 'abstractForm.list', params: [meetingId] },
};

// 📌 `breadcrumbs` を `computed` で計算
const breadcrumbs = computed(() => {
  return props.pageNames.map((pageName) => {
    const routeData = breadcrumbMap[pageName];

    return {
      label: pageName,
      href: routeData
        ? route(routeData.routeName, routeData.params) // 🔥 params にはすでに値が入っているので、そのまま渡す
        : '#',
    };
  });
});

// 📌 `current`(現在のページ)
const current = computed(() => breadcrumbs.value[breadcrumbs.value.length - 1]);
</script>

<template>
  <nav class="breadcrumb">
    <ul>
      <li v-for="breadcrumb in breadcrumbs" :key="breadcrumb.href">
        <template v-if="breadcrumb === current">
          {{ breadcrumb.label }}
        </template>
        <template v-else>
          <Link :href="breadcrumb.href">{{ breadcrumb.label }}</Link>
        </template>
      </li>
    </ul>
  </nav>
</template>

Vue.js + Inertia.js で動的なパンくずリストを作成する方法

このページでは、Vue.js + Inertia.js を使った動的なパンくずリスト(Breadcrumb)コンポーネント の作り方を解説します。

pageNames を渡すだけで、適切なルート付きのナビゲーションを自動生成 できます。

📌 このパンくずリストでできること

現在のページに応じたパンくずリストを自動生成!

pageNames を渡すだけで、適切なページ遷移が可能!

Inertia.js の usePage().props.params からページごとの ID を自動取得!

Vue の computed() を活用して、最適なルーティング処理を実装!

🛠 親コンポーネントでの使い方

パンくずリストを表示したいページで、以下のように Breadcrumb コンポーネントを使用します。

<Breadcrumb :pageNames="['集会一覧', '集会詳細', '演題一覧', '演題新規登録']" />

この場合のパンくずリスト

📍 集会一覧 > 集会詳細 > 演題一覧 > 演題新規登録

クリック可能なページ は <Link> で遷移

現在のページ はリンクなしで表示

📌 パンくずリストの実装(Vue コンポーネント)

以下が、Vue.js でパンくずリストを動的に生成するコンポーネントです。

<script lang="ts">
export interface Params {
  meetingId: string;
  emailSendingHistoryId: string;
  mailTemplateId: string;
  reviewRequestId: string;
  reviewId: string;
  historyId: string;
  administratorId: string;
}
</script>

<script setup lang="ts">
import { computed, PropType } from 'vue';
import { Link, usePage } from '@inertiajs/vue3';

// 📌 `params` を `usePage().props.params` から取得し、分割代入
const {
  meetingId,
  emailSendingHistoryId,
  mailTemplateId,
  reviewRequestId,
  reviewId,
  historyId,
  administratorId
} = usePage().props.params as Params;

// `pageNames`(ページ名の配列)を受け取る
const props = defineProps({
  pageNames: {
    type: Array as PropType<string[]>,
    required: true,
  },
});

// 📌 `breadcrumbMap` を定義し、各ページに対応するルートを設定
const breadcrumbMap: Record<string, { routeName: string; params?: string[] }> = {
  集会一覧: { routeName: 'meeting.index' },
  集会登録: { routeName: 'meeting.create' },
  集会詳細: { routeName: 'meeting.show', params: [meetingId] },
  集会編集: { routeName: 'meeting.edit', params: [meetingId] },
  演題一覧: { routeName: 'abstract.index', params: [meetingId] },
  演題登録: { routeName: 'abstract.bulkRegistration.show', params: [meetingId] },
  管理者一覧: { routeName: 'administrator.index' },
  管理者詳細: { routeName: 'administrator.show', params: [administratorId] },
  管理者登録: { routeName: 'administrator.create' },
  メール送信履歴: { routeName: 'email.history.index', params: [meetingId] },
  メール送信履歴詳細: { routeName: 'email.history.show', params: [meetingId, emailSendingHistoryId] },
  メール管理: { routeName: 'email.emailTemplate.index' },
  メールテンプレート詳細: { routeName: 'email.emailTemplate.show', params: [mailTemplateId] },
  メールテンプレート編集: { routeName: 'email.emailTemplate.edit', params: [mailTemplateId] },
  査読依頼一覧: { routeName: 'reviewRequest.index', params: [meetingId] },
  査読依頼詳細: { routeName: 'reviewRequest.show', params: [meetingId, reviewRequestId] },
  査読割設定: { routeName: 'reviewRequest.create', params: [meetingId] },
  査読再割付: { routeName: 'reviewRequest.recreate', params: [meetingId] },
  査読依頼メール送信: { routeName: 'reviewRequest.emailSending.create', params: [meetingId, reviewRequestId] },
  査読詳細: { routeName: 'review.show', params: [meetingId, reviewRequestId, reviewId] },
  査読編集: { routeName: 'review.edit', params: [meetingId, reviewRequestId, reviewId] },
  キーワード編集: { routeName: 'masters.keywords.edit' },
  演題登録フォーム一覧: { routeName: 'abstractForm.list', params: [meetingId] },
};

// 📌 `breadcrumbs` を `computed` で計算
const breadcrumbs = computed(() => {
  return props.pageNames.map((pageName) => {
    const routeData = breadcrumbMap[pageName];

    return {
      label: pageName,
      href: routeData
        ? route(routeData.routeName, routeData.params) // 🔥 `params` にすでに値が入っているのでそのまま渡す
        : '#',
    };
  });
});

// 📌 `current`(現在のページ)
const current = computed(() => breadcrumbs.value[breadcrumbs.value.length - 1]);
</script>

<template>
  <nav class="breadcrumb">
    <ul>
      <li v-for="breadcrumb in breadcrumbs" :key="breadcrumb.href">
        <template v-if="breadcrumb === current">
          {{ breadcrumb.label }}
        </template>
        <template v-else>
          <Link :href="breadcrumb.href">{{ breadcrumb.label }}</Link>
        </template>
      </li>
    </ul>
  </nav>
</template>

✅ 修正のポイント

params を usePage().props.params から取得し、分割代入

breadcrumbMap の params に params の値を直接セット

無駄な map() の処理を削除し、シンプルに params を渡す

route(routeData.routeName, routeData.params) に変更して最適化

🔍 具体的な動作

例1: 査読編集(review.edit) の場合

// `params` の値
const meetingId = '123';
const reviewRequestId = '456';
const reviewId = '789';

// breadcrumbMap のエントリー
査読編集: { routeName: 'review.edit', params: [meetingId, reviewRequestId, reviewId] }

route() に渡る値

route('review.edit', ['123', '456', '789'])

結果: /review/edit/123/456/789

🚀 まとめ

修正前修正後
params にキーの文字列を入れていたparams に usePage().props.params の値を直接代入
map() を使って params[param] を取得params にすでに値が入っているため map() を削除
routeData.params?.map(param => params[param])route(routeData.routeName, routeData.params) に変更

これで params の値を直接使う形に修正完了! 🚀

コメント

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