Nuxt3とmicroCMSで作るブログシリーズの6番目の記事です。
今回はタグでの絞り込みを実装していきます。
まずは、タグで絞り込まれた場合のルートを追加します。
階層が深いですが、pages
ディレクトリにtags
→[tagId]
→page
→[id].vue
と作成します。
<!-- src/pages/tags/[tagId]/page/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const page = Number(route.params.id || 1)
const tagId = String(route.params.tagId || '')
</script>
<template>
<Home :page="page" :tagId="tagId" />
</template>
こうしておくことで、例えば/tag/my-pretty-tag/page/1
にアクセスされた際に、route.params.tagId
とすればmy-pretty-tag
というタグIDが取り出せるようになります。
server/api配下に作成していきます。
// src/server/api/tagList.ts
import client from './client'
import { Tag } from '../../types/blog'
export default defineEventHandler(async (event) => {
// これ以上は増えない、という値
const queries = {
limit: 100,
}
const data = await client.getList<Tag>({
endpoint: 'tag',
queries: queries
})
return data
})
記事一覧と違ってタグは基本的にすべて表示させます。
なので、limitはこれより増えることはないだろう、という値を設定しています。
次にTags.vueコンポーネンツを作成していきます。
<!-- src/components/Tags.vue -->
<script setup lang="ts">
import { Tag } from '../types/blog';
type Props = {
tags: Tag[];
selectedTagId: string | undefined;
}
const { tags, selectedTagId } = defineProps<Props>();
function getClass(tagId: string) {
if (tagId == selectedTagId) return 'active'
return 'link'
}
</script>
<template>
<div class="wrapper">
<h1 class="pageTitle">タグ</h1>
<ul>
<li v-for="tag in tags" :key="tag.id" class="list">
<NuxtLink :to="`/tags/${tag.id}/page/1`" :class="getClass(tag.id)">
{{ tag.name }}
</NuxtLink>
</li>
</ul>
</div>
</template>
<style scoped>
.wrapper {
padding: 20px 0;
}
.pageTitle {
font-size: 2rem;
font-weight: bold;
color: #5ba9f7;
background-color: #c8e4ff;
padding: 6px 10px;
margin-bottom: 10px;
border-radius: 5px;
}
.list {
border-bottom: 1px solid #eee;
list-style-type: none;
}
.link,
.active {
display: block;
padding: 10px;
color: #888;
font-size: 1.6rem;
}
.active {
color: #5ba9f7;
}
</style>
タグ一覧と選択中のタグIDを親から受け取り、リンクを表示させるだけのシンプルな作りです。
選択中のタグとそうでない場合でクラスを変えて、どのタグを選択しているか色で表示するようにしています。
function getClass(tagId: string) {
if (tagId == selectedTagId) return 'active'
return 'link'
}
次にHome.vueを更新していきます。
追加でやりたいことは以下の2点です。
<!-- src/components.Home.vue -->
<script setup lang="ts">
import { MicroCMSQueries } from 'microcms-js-sdk';
import { BLOG_PER_PAGE } from '../settings/siteSettings';
type Props = {
page: number,
// オプショナルで追加
tagId?: string
}
const { page, tagId } = defineProps<Props>()
const limit = BLOG_PER_PAGE
const queries: MicroCMSQueries = {
limit: limit,
offset: (page - 1) * limit,
}
//tagIdを渡されているときはqueriesに加える
if (tagId) {
queries.filters = `tag[contains]${tagId}`
}
const { data: posts } = await useFetch('/api/postList', { params: queries })
// 追加 タグ一覧の取得
const { data: tags } = await useFetch('/api/tagList')
const numPages = Math.ceil(posts.value.totalCount / limit)
</script>
<template>
<div>
<div class="divider">
<section class="container">
<PostList :posts="posts.contents" />
</section>
<aside class="aside">
<SearchForm />
<!-- 追加 -->
<Tags :tags="tags.contents" :selectedTagId="tagId" />
</aside>
</div>
<Pagination :numPages="numPages" :current="page" />
</div>
</template>
先ほど作成したタグ一覧コンポーネントを配置しつつ、URLからタグIDを取得してparamに加えるようにしています。
タグIDが存在しないこともあるのでif分でparamに追加するようにしました。
//tagIdを渡されているときはqueriesに加える
if (tagId) {
queries.filters = `tag[contains]${tagId}`
}
次に記事一覧APIでもタグIDをキャッチできるようにしていきます。
この段階でタグでの絞り込みが行えるようになります。
タグmicroCMS
を選択した状態
だいたいできてきましたが、タグありのページネーションも追加する必要があります。
いまのままですと、タグを絞り込んだ状態で2ページ目に遷移した際に、絞り込みが解除されてしまいます。
これは簡単で現在のページネーションのコンポーネントを少し編集するだけです。
<!-- src/components/Pagination.vue -->
<script setup lang="ts">
type Props = {
numPages: number;
current: number;
// タグIDを受け取るようにする
selectedTagId?: string | undefined;
keyword?: string;
}
const { numPages, current, selectedTagId, keyword } = defineProps<Props>();
function getPath(p: number) {
// 追加 タグありのリンク
if (selectedTagId) return `tag/${selectedTagId}/page/${p}`
if (keyword) return `/search?q=${keyword}&page=${p}`
return `/page/${p}`
}
function getClass(page: number, current: number) {
if (page == current) return 'current'
return 'link'
}
</script>
あとは親からPaginationに対してタグIDを渡すだけです。
<!-- src/components/Home.vue -->
<template>
<div>
<div class="divider">
...
</div>
<Pagination :numPages="numPages" :current="page" :selectedTagId="selectedTagId" />
</div>
</template>
こちらでこのシリーズを一旦終了とします。