vue3 ページネーション

<script lang="ts">
export interface PageNumber {
  value: number;
}

export interface Ellipsis {
  value: string;
}

export type PageItem = PageNumber | Ellipsis;
</script>

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

const props = defineProps({
  totalResults: {
    type: Number as PropType<number>,
    required: true,
  },
  currentPage: {
    type: Number as PropType<number>,
    required: true,
  },
  totalPage: {
    type: Number as PropType<number>,
    required: true,
  },
});

// Emitイベントを定義
const emit = defineEmits(['update:page']);

// ページを変更する関数
const changePage = (page: number) => {
  emit('update:page', page);
};

// 次のページに移動
const nextPage = () => {
  if (props.currentPage < props.totalPage) {
    changePage(props.currentPage + 1);
  }
};

// 前のページに移動
const previousPage = () => {
  if (props.currentPage > 1) {
    changePage(props.currentPage - 1);
  }
};

// ページ番号の配列を取得するメイン関数
const getPages = () => {
  const pages: PageItem[] = [];
  const maxPagesToShow = 7; //表示するページ数
  const sidePages = Math.floor((maxPagesToShow - 3) / 2);

  // 全体のページ数が表示するページ数以下の場合は、単純にすべてのページ番号を表示する
  if (props.totalPage <= maxPagesToShow) {
    for (let i = 1; i <= props.totalPage; i++) {
      pages.push({ value: i });
    }
    return pages;
  }

  const { startPage, endPage } = getMiddlePages(props.currentPage, props.totalPage, sidePages, maxPagesToShow);

  addBoundaryPages(pages, props.totalPage);

  if (startPage > 2) {
    addEllipsis(pages, 'start');
  }

  for (let i = startPage; i <= endPage; i++) {
    pages.splice(pages.length - 1, 0, { value: i });
  }

  if (endPage < props.totalPage - 1) {
    addEllipsis(pages, 'end');
  }

  return pages;
};

/**
 * getPages()の中で呼ばれる関数群
 */
// 1ページ目と最後のページを追加する関数
const addBoundaryPages = (pages: PageItem[], totalPages: number) => {
  pages.push({ value: 1 });
  if (totalPages > 1) {
    pages.push({ value: totalPages });
  }
};

// 「...」を追加する関数
const addEllipsis = (pages: PageItem[], position: 'start' | 'end') => {
  const ellipsis: PageItem = { value: '...' };
  if (position === 'start') {
    pages.splice(1, 0, ellipsis);
  } else {
    pages.splice(pages.length - 1, 0, ellipsis);
  }
};

// 中間ページの範囲を取得する関数
const getMiddlePages = (currentPage: number, totalPages: number, sidePages: number, maxPagesToShow: number) => {
  let startPage = Math.max(2, currentPage - sidePages);
  let endPage = Math.min(totalPages - 1, currentPage + sidePages);

  if (currentPage <= sidePages + 2) {
    startPage = 2;
    endPage = maxPagesToShow - 1;
  } else if (currentPage > totalPages - (sidePages + 2)) {
    startPage = totalPages - (maxPagesToShow - 2);
    endPage = totalPages - 1;
  }

  return { startPage, endPage };
};
</script>

<template>
  <div class="pagination">
    <span>検索結果:{{ totalResults }}件</span>

    <!-- 前のページへ移動 -->
    <span :class="{ disabled: currentPage === 1 }">
      <template v-if="currentPage > 1">
        <Link @click="previousPage" href="#"> &lt; </Link>
      </template>
      <template v-else>
        <span>&lt;</span>
      </template>
    </span>

    <!-- ページ番号のリンク -->
    <span>
      <span
        v-for="page in getPages()"
        :key="page.value"
        :class="{ active: currentPage === page.value, ellipsis: page.value === '...' }"
      >
        <template v-if="page.value === '...'">
          <span>{{ page.value }}</span>
        </template>
        <template v-else-if="currentPage === page.value">
          <span>{{ page.value }}</span>
        </template>
        <template v-else>
          <Link @click="changePage(page.value)" href="#">{{ page.value }}</Link>
        </template>
      </span>
    </span>

    <!-- 次のページへ移動 -->
    <span :class="{ disabled: currentPage === totalPage }">
      <template v-if="currentPage < totalPage">
        <Link @click="nextPage" href="#"> &gt; </Link>
      </template>
      <template v-else>
        <span>&gt;</span>
      </template>
    </span>
  </div>
</template>

Vue.js + Inertia.js でページネーションを実装する方法

このページでは、Vue.js + Inertia.js を使用したページネーションコンポーネント の作成方法を解説します。

このコンポーネントを使うことで、検索結果や一覧ページに適切なページネーションを追加 できます。

📌 このページネーションでできること

現在のページに応じたページネーションを自動生成!

前後のページに簡単に移動可能!

ページが多い場合は … を表示し、ページリストを適切に縮小!

現在のページを強調表示し、クリックでページ遷移が可能!

Inertia.js の emit(‘update:page’, page) を使い、ページ番号を変更可能!

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

ページネーションを表示したいページで、以下のように Pagination コンポーネントを使用します。

<Pagination 
  :totalResults="150"
  :currentPage="3"
  :totalPage="10"
  @update:page="handlePageChange"
/>

この場合のページネーション

検索結果:150件
[ < ]  1 ... 2 3 4 5 ... 10  [ > ]

• totalResults → 検索結果の総数

• currentPage → 現在のページ番号

• totalPage → 総ページ数

• @update:page=”handlePageChange” → 親コンポーネント側でページ変更時の処理を実装

📌 ページネーションの実装

以下が、Vue.js でページネーションを動的に生成するコンポーネントです。

<script lang="ts">
export interface PageNumber {
  value: number;
}

export interface Ellipsis {
  value: string;
}

export type PageItem = PageNumber | Ellipsis;
</script>

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

const props = defineProps({
  totalResults: {
    type: Number as PropType<number>,
    required: true,
  },
  currentPage: {
    type: Number as PropType<number>,
    required: true,
  },
  totalPage: {
    type: Number as PropType<number>,
    required: true,
  },
});

// Emitイベントを定義
const emit = defineEmits(['update:page']);

// ページを変更する関数
const changePage = (page: number) => {
  emit('update:page', page);
};

// 次のページに移動
const nextPage = () => {
  if (props.currentPage < props.totalPage) {
    changePage(props.currentPage + 1);
  }
};

// 前のページに移動
const previousPage = () => {
  if (props.currentPage > 1) {
    changePage(props.currentPage - 1);
  }
};

// 📌 ページ番号の配列を取得するメイン関数
const getPages = () => {
  const pages: PageItem[] = [];
  const maxPagesToShow = 7; // 表示するページ数
  const sidePages = Math.floor((maxPagesToShow - 3) / 2);

  // 全体のページ数が表示するページ数以下の場合は、単純にすべてのページ番号を表示する
  if (props.totalPage <= maxPagesToShow) {
    for (let i = 1; i <= props.totalPage; i++) {
      pages.push({ value: i });
    }
    return pages;
  }

  const { startPage, endPage } = getMiddlePages(props.currentPage, props.totalPage, sidePages, maxPagesToShow);

  addBoundaryPages(pages, props.totalPage);

  if (startPage > 2) {
    addEllipsis(pages, 'start');
  }

  for (let i = startPage; i <= endPage; i++) {
    pages.splice(pages.length - 1, 0, { value: i });
  }

  if (endPage < props.totalPage - 1) {
    addEllipsis(pages, 'end');
  }

  return pages;
};

/**
 * getPages() の中で呼ばれる関数群
 */
// 1ページ目と最後のページを追加する関数
const addBoundaryPages = (pages: PageItem[], totalPages: number) => {
  pages.push({ value: 1 });
  if (totalPages > 1) {
    pages.push({ value: totalPages });
  }
};

// 「...」を追加する関数
const addEllipsis = (pages: PageItem[], position: 'start' | 'end') => {
  const ellipsis: PageItem = { value: '...' };
  if (position === 'start') {
    pages.splice(1, 0, ellipsis);
  } else {
    pages.splice(pages.length - 1, 0, ellipsis);
  }
};

// 中間ページの範囲を取得する関数
const getMiddlePages = (currentPage: number, totalPages: number, sidePages: number, maxPagesToShow: number) => {
  let startPage = Math.max(2, currentPage - sidePages);
  let endPage = Math.min(totalPages - 1, currentPage + sidePages);

  if (currentPage <= sidePages + 2) {
    startPage = 2;
    endPage = maxPagesToShow - 1;
  } else if (currentPage > totalPages - (sidePages + 2)) {
    startPage = totalPages - (maxPagesToShow - 2);
    endPage = totalPages - 1;
  }

  return { startPage, endPage };
};
</script>

<template>
  <div class="pagination">
    <span>検索結果:{{ totalResults }}件</span>

    <!-- 前のページへ移動 -->
    <span :class="{ disabled: currentPage === 1 }">
      <template v-if="currentPage > 1">
        <Link @click="previousPage" href="#"> &lt; </Link>
      </template>
      <template v-else>
        <span>&lt;</span>
      </template>
    </span>

    <!-- ページ番号のリンク -->
    <span>
      <span
        v-for="page in getPages()"
        :key="page.value"
        :class="{ active: currentPage === page.value, ellipsis: page.value === '...' }"
      >
        <template v-if="page.value === '...'">
          <span>{{ page.value }}</span>
        </template>
        <template v-else-if="currentPage === page.value">
          <span>{{ page.value }}</span>
        </template>
        <template v-else>
          <Link @click="changePage(page.value)" href="#">{{ page.value }}</Link>
        </template>
      </span>
    </span>

    <!-- 次のページへ移動 -->
    <span :class="{ disabled: currentPage === totalPage }">
      <template v-if="currentPage < totalPage">
        <Link @click="nextPage" href="#"> &gt; </Link>
      </template>
      <template v-else>
        <span>&gt;</span>
      </template>
    </span>
  </div>
</template>

✅ このページネーションのポイント

totalResults を表示し、検索結果の総数をユーザーに示す!

現在のページはハイライト表示し、クリックできないようにする!

ページ数が多い場合は … を追加して適切に省略!

emit(‘update:page’, page) を使い、親コンポーネントからページ変更が可能!

🚀 まとめ

このコンポーネントを導入することで、Vue + Inertia.js を使用した 動的なページネーション を簡単に追加できます! 🚀

コメント

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