作为一个完全不懂 Vue、Nuxt 和 TypeScript 的新手,我花了两天时间成功接入了 Bangumi API,实现了动漫和游戏进度的展示功能,本文基于 Nuxt 3 + Vue 3 + TypeScript 编写。
1. 准备工作
首先需要了解Bangumi API的基本用法:
2. 定义返回数据类型
在types/bangumi.d.ts中根据 API 文档定义返回数据的类型,比如:
export interface BangumiApiResponse {
total: number // 总数
limit: number // 每页数
offset: number // 偏移量
data: {
updated_at: string // 更新时间
comment: string | null // 自己的评论
subject: {
images: {
common: string // 封面
}
name: string // 名称
name_cn: string // 中文名称
score: number // 均分
id: number // 条目 ID
}
subject_id: number
rate: number // 自己的评分
}[]
}
3. 实现数据获取逻辑
要使用 API,还需要准备5个参数,分别是用户名、条目类型、收藏类型、每页条数和偏移量。
根据文档得知,参数SubjectType为2表示动画,4表示游戏。参数Type为1表示想看,2表示看过,3表示再看。
重点说说 limit 和 offset,这两个参数就可以用来分页。limit 可以理解为获取多少条数据,而 offset 可以理解为忽略多少条数据。
比如,我们每页显示10条数据,那么 limit 就是10,当第一页的时候 offset 就为0,当第二页的时候 offset 就为10,以此类推。
将这些参数拼接后,就可以使用 useFetch获取到自己想要的数据了
创建 composables/useBangumi.ts文件:
import type { BangumiApiResponse } from '~/types/bangumi' // 导入定义好的类型
import BlogConfig from '~~/blog.config' // 导入用户名
export type ContentType = 'anime' | 'game' // 定义条目类型
export type CollectionType = keyof typeof TYPE_ID_MAP // 定义收藏类型
export const ITEMS_PER_PAGE = 12 // 每页显示12条数据
// 收藏类型映射表
const TYPE_ID_MAP = {
wish: 1,
collect: 2,
do: 3,
} as const
export default function useBangumiCollection(
contentType: ContentType = 'anime',
collectionType: Ref<CollectionType> = ref('wish'),
page: Ref<number> = ref(1),
) {
const username = BlogConfig.bgmUsername
const subjectType = computed(() => contentType === 'anime' ? 2 : 4)
const typeId = computed(() => TYPE_ID_MAP[toValue(collectionType)])
const offset = computed(() => (page.value - 1) * ITEMS_PER_PAGE)
const { data, status, error } = useFetch<BangumiApiResponse>(() => {
return `https://api.bgm.tv/v0/users/${username}/collections?subject_type=${subjectType.value}&type=${typeId.value}&limit=${ITEMS_PER_PAGE}&offset=${offset.value}`
}, {
// 这里的 key 是 Nuxt 用来缓存 fetch 请求的标识,避免重复请求,提高性能
key: `bangumi-${contentType}-${toValue(collectionType)}-page-${toValue(page)}`,
})
const totalPages = computed(() => data.value ? Math.ceil(data.value.total / ITEMS_PER_PAGE) : 0) // 计算总页数
return {
data,
status,
error,
totalPages,
}
}
4. 页面展示逻辑
<script setup lang="ts">
import useBangumi from '~/composables/useBangumi'
const contentType = ref<'anime' | 'game'>('anime')
const collectionType = ref<'wish' | 'collect' | 'do'>('wish')
const page = ref(1)
const { data, pending, error, totalPages } = useBangumi(
contentType,
collectionType,
page
)
// 切换收藏类型时重置页码
watch(collectionType, () => {
page.value = 1
})
</script>
当我们需要获取不同类型的数据时,只需要修改 contentType 和 collectionType 即可。
data 包含了 API 返回的数据,pending 表示请求是否正在进行,error 表示请求是否出错,totalPages 表示总页数。要实现骨架屏和错误提示,只需要在页面上添加相应的组件即可。下面只展示逻辑
<template>
<div>
<div v-if="pending">加载中...</div>
<div v-else-if="error">加载失败:{{ error.message }}</div>
<div v-else>
<!-- 数据展示 -->
</div>
</div>
</template>
分页控制也比较简单,只需要在页面上添加上一页和下一页的按钮,然后在点击按钮时修改 page 的值即可。
<template>
<div class="pagination">
<button @click="page--" :disabled="page <= 1">上一页</button>
<span>{{ page }} / {{ totalPages }}</span>
<button @click="page++" :disabled="page >= totalPages">下一页</button>
</div>
</template>
这是我第一次使用 Vue 和 Nuxt 接入第三方 API,过程虽然有些曲折,但也收获颇多。 学到了一点 TypeScript 的知识,也了解了一些 Vue 的基本用法。
评论区
评论加载中...