BLOG ブログ


2024.12.25 TECH

Nuxt+newtでページャを作ってみる

前回(https://www.northdetail.co.jp/blog/3459/)はHeadless CMS「Newt」を使い、蒐集品を管理するサイトを作ってみました。

今回はNuxtとNewtを用いて記事一覧画面をつくり、そこにページャを付けたいと思います。早速やってみましょう。

階層

pages内の階層は次のようになります。

pages/gallery/index.vue 記事一覧トップ
pages/gallery/page/[page].vue 記事一覧の各ページ

API

今回はnewtを使用するので、あらかじめpluginsで設定を行ってください。

そしてAPIを叩く部分は次のように作ります。

import type { Landscape } from "~/types/landscape"

export const useLandscapeApi = async(
  pagekey: string,
  limit= 1000,
  skip= 0,
  param= {}
) => {
  try{
    const {data} = await useAsyncData(pagekey, async() => {
      const {$newtClient} = useNuxtApp()
      return await $newtClient.getContents<Landscape>({
        appUid: 'landscape',
        modelUid: 'post',
        query: {
          select: ['_id', 'title', 'slug', 'landscape', 'detail'],
          limit: limit,
          skip: skip,
          ...param
        }
      })
    })
    return data.value
  } catch (err: any) {
    console.log(err)
    return false
  }
}

このように、引数として
limit?: number,(一度に取得する件数)
skip?: number,(スキップする件数)
を取ります。

一覧ページ

まずは「pages/gallery/index.vue(記事一覧トップ)」と「pages/gallery/page/[page].vue(記事一覧の各ページ)」を作ります。

<template>
  <LandscapeList />
</template>

<script lang="ts" setup>
definePageMeta({
  title: '綺麗な景色一覧'
})
</script>
<template>
  <LandscapeList :key="`landscape-${route.params.page}`"/>
</template>

<script lang="ts" setup>
definePageMeta({
  title: '綺麗な景色一覧'
})
</script>

次にここから呼び出している「LandscapeList」コンポーネントを作りましょう。今回は一度に3件の記事を取得することにします。

<template>
  <main class="Container">
    <article class="landscape" key="article-landscape">
      <h2>綺麗な景色一覧</h2>
      <ul v-if="landscapeList && landscapeList.length > 0">
        <li v-for="landscape in landscapeList"
            :key="landscape._id">
          <h3>{{ landscape.title }}</h3>
          <NuxtLink :to="`/gallery/${landscape.slug}`">
              <img :src="landscape.img.src" alt="" :style="{'view-transition-name': `landscape-img-${landscape.slug}`}">
          </NuxtLink>
        </li>
      </ul>
      <Pagination :skip="skip" :limit="limit" :total="total" :currentPageNo="currentPageNo" :type="'/gallery'" />
    </article>
  </main>
</template>

<script lang="ts" setup>
import type { Landscape } from '~/types/landscape'
const route = useRoute()
const currentPageNo = route.params?.page ? Number(route.params.page) : 1
const limit = 3 // 何件ずつ表示するか
const skip = (currentPageNo - 1) * limit
const total = ref(0)

const resData = await useLandscapeApi(
  `landscape-${currentPageNo}`,
  limit,
  (currentPageNo - 1) * limit
)
const landscapeList : Landscape[] | false | null =  resData ? resData.items : null

if (resData) {
  total.value = resData?.total ?? 0
}
</script>

const currentPageNo = route.params?.page ? Number(route.params.page) : 1
で、現在のページNoを取得します。記事一覧の各ページを見ている場合は[page]の値を取得し、記事一覧トップを見ている場合は「1」とします。
今回は3件ずつ表示するのでlimitは「3」です。
現在のページNoに応じてskipの値を変更します。1ページ目に居るならskipは0。2ページ目に居るなら1ページ目の分は飛ばしたいので、「(現在のページNo(2) - 1)×limit(3)」となります。
また、APIを叩いたときにtotalが取得できるので、refで受け皿を準備しておきます。

その後、limitとskipを引数で渡してAPIを叩きます。

Pagination

次に「Pagination」コンポーネントを作ります。

<template>
  <nav>
    <ul>
      <li v-for="page in pagerMaxNum" :key="`${routePath}-page-{page}`">
        <nuxtLink
          :to="page === 1 ? `${type}` : `${type}/page/${page}`"
          type="nuxtLink"
          :class="`pagination_button ${page === currentPageNo ? '_current' : ''}`">{{ page }}</nuxtLink
        >
      </li>
    </ul>
    <p>
      {{ props.total }}件中 {{ currentNumberStart }}件目から{{ currentNumberEnd }}件目を表示中
    </p>
  </nav>
</template>

<script lang="ts" setup>
interface Props {
  skip: number
  limit: number
  total: number,
  currentPageNo: number,
  type: string
}

const props = withDefaults(defineProps<Props>(), {
  skip: 0,
  limit: 0,
  total: 0,
  currentPageNo: 1,
  type: ''
})

const route = useRoute()
const routePath = route.path

const currentNumberStart = computed(() => { return props.skip + 1 })
const currentNumberEnd = computed(() => { return (props.skip + props.limit + 1) <= props.total ? (props.skip + props.limit) : props.total })
const pagerMaxNum = computed(() => {
  return Math.ceil(props.total / props.limit)
})
</script>

「LandscapeList」からskip、limit、total、currentPageNo、typeが渡ってきています。typeは現在のカテゴリのパスを表しています。今回のサイトにあるのは「綺麗な景色一覧」のみですが、他にも異なるカテゴリ一覧がある場合は、ここの値を変えて対応します。

pagerMaxNumをv-forで回してページャを作ります。1ページ目のときは「pages/gallery/index.vue(記事一覧トップ)」、2ページ目以降のときは「pages/gallery/page/[page].vue(記事一覧の各ページ)」へリンクします。

また、currentNumberStartとcurrentNumberEndで現在表示されているのが何件目から何件目なのかを表示します。totalは「LandscapeList」コンポーネントでAPIを叩いたときに取得できているのでそのまま出力します。

動作確認

npm run generate→npm run previewで動作確認を行います。
無事、各ページが生成され、ページャから遷移できることが確認できるかと思います。

まとめ

ページャはブログやコーポレートサイト、その他様々なサイトで必要になってくる機能です。必要になった際はぜひ今回の方法を試してみてください。


一覧に戻る


LATEST ARTICLE 最新の記事

CATEGORY カテゴリー