前言
鸽了这么久的时间,至于为什么鸽了这么久的原因有很多:写小说、默默对站点进行添加新的功能(没有写文)、玩游戏等等,当然这次的文章也是非常高级的。 本教程参考了以下页面的东西
Mikuの鬆
采用该卡片的元素样式
风纪星辰
采用该卡片的元素样式(v0版本的卡片,现已抛弃)与tab样式
功能解析
- 采用两栏菜单栏,分为追更作品类型栏(第一个tab栏)与追更作品可见范围栏(第二个tab栏)
- 使用全新加载方式(即风纪星辰豆瓣记录的loading加载)
目录结构
/app/page/banguim.vue:追更页面中的主界面渲染模块,传递useBangumi.ts的模块请求数据(模块来源于喵落阁,经过本人重新二开)/app/components/Bangumi/bgmCard.vue:追更页面的卡片主渲染模块(样式来自Mikuの鬆,模块来源于喵落阁,本模块经过本人重新二开)/app/composables/useBangumi.ts:追更页面中的api请求模块,具有subject_type与type两种请求方式(模块来源于喵落阁,经过本人重新二开)/app/types/bangumi:追更页面全局数据类型,本身作为数据加载以及页面引用(模块来源于喵落阁)
核心代码
追更页面渲染展示
<script setup lang="ts">
import type { BangumiCollectionItem } from '~/types/bangumi'
import { getPostDate } from '~/utils/time'
const props = defineProps<{
bangumiCollectionItem: BangumiCollectionItem
}>()
function handleClick() {
const url = `https://bgm.tv/subject/${props.bangumiCollectionItem.subject_id}`
window.open(url, '_blank')
}
</script>
<template>
<div class="banguimItem" >
<img
v-if="bangumiCollectionItem.subject.images?.common"
:src="bangumiCollectionItem.subject.images.common"
:alt="bangumiCollectionItem.subject.name"
class="banguimImage"
>
<div class="title">
<a :href="`https://bgm.tv/subject/${props.bangumiCollectionItem.subject_id}`">
{{ bangumiCollectionItem.subject.name_cn || bangumiCollectionItem.subject.name }}
</a>
</div>
<span class="dateSignpost">{{ getPostDate(bangumiCollectionItem.updated_at) }}</span>
<span class="score">
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 20.1l5.82 3.682c1.066.675 2.37-.322 2.09-1.584l-1.543-6.926 5.146-4.667c.94-.85.435-2.465-.799-2.567l-6.773-.602L13.29.89a1.38 1.38 0 0 0-2.581 0l-2.65 6.53-6.774.602C.052 8.126-.453 9.74.486 10.59l5.147 4.666-1.542 6.926c-.28 1.262 1.023 2.26 2.09 1.585L12 20.099z"></path>
</svg>
{{ bangumiCollectionItem.subject.score || '暂无' }}
</span>
</div>
</template>
<style scoped lang="scss">
// 变量定义
$main-color: var(--db-main-color);
$hover-color: var(--db-hover-color);
$text-color: var(--db--text-color);
$text-color-light: var(--db--text-color-light);
$border-radius: var(--thyuu--size-radius);
$card-normal-size: var(--thyuu--size-card-normal);
$small-size: var(--thyuu--size-small);
$animation: opacity .5s var(--animation-in) backwards, transform 1s var(--animation-in) backwards, filter .7s var(--animation-in);
// 卡片样式表
.banguimItem {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: flex-end;
align-items: center;
text-align: center;
width: 100%;
height: 100%;
padding: 1em;
gap: .5em;
margin: 0 20px 20px 0;
border-radius: $border-radius;
background: #000;
overflow: hidden;
position: relative;
.banguimImage {
position: absolute;
width: 100%;
height: 100%;
border-radius: 0;
-webkit-mask: linear-gradient(#0006, #000c, #0000);
transition: ease-in-out .3s;
object-fit: cover;
inset: 0;
}
.title {
order: -1;
z-index: 1;
flex: 100%;
position: relative;
padding: .5em 1em;
margin: 0;
border-radius: 1em;
line-height: 1;
font-weight: 400;
color: white;
width: auto;
margin-top: 2px;
font-size: 14px;
line-height: 1.4;
color: $text-color;
&::before {
content: "\e667";
display: inline-block;
text-indent: 0;
margin: 0 .25em 0 0;
rotate: 45deg;
scale: .75;
transition: rotate .5s;
}
a {
color: hsl(var(--thyuu--main-color));
}
}
.dateSignpost, .score {
position: relative;
padding: .5em 1em;
margin: 0;
border-radius: 1em;
line-height: 1;
font-weight: 400;
color: white;
width: auto;
background: #f5c518;
color: #000;
border-radius: 4px;
line-height: 1;
padding: 3px 5px;
font-size: 12px;
display: flex;
margin-bottom: 2px;
font-weight: 900;
color: #ffffffb3 !important;
background: #ffffff1c !important;
-webkit-backdrop-filter: saturate(1.8) blur(20px);
backdrop-filter: saturate(1.8) blur(20px);
font-size: $small-size !important;
}
.dateSignpost:after {
all: unset;
content: '标记';
margin: 0 0 0 .5em;
}
.score {
svg {
fill: #f5c518;
margin-right: 5px;
}
}
}
</style>
追更页面主体卡片渲染展示模块
提示
该版本已被弃用,不再维护
<script setup lang="ts">
import type { BangumiCollectionItem } from '~/types/bangumi'
import { getPostDate } from '~/utils/time'
const props = defineProps<{
bangumiCollectionItem: BangumiCollectionItem
}>()
function handleClick() {
const url = `https://bgm.tv/subject/${props.bangumiCollectionItem.subject_id}`
window.open(url, '_blank')
}
</script>
<template>
<div class="banguimItem" >
<img
v-if="bangumiCollectionItem.subject.images?.common"
:src="bangumiCollectionItem.subject.images.common"
:alt="bangumiCollectionItem.subject.name"
class="banguimImage"
>
<div class="title">
<a :href="`https://bgm.tv/subject/${props.bangumiCollectionItem.subject_id}`">
{{ bangumiCollectionItem.subject.name_cn || bangumiCollectionItem.subject.name }}
</a>
</div>
<span class="dateSignpost">{{ getPostDate(bangumiCollectionItem.updated_at) }}</span>
<span class="score">
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 20.1l5.82 3.682c1.066.675 2.37-.322 2.09-1.584l-1.543-6.926 5.146-4.667c.94-.85.435-2.465-.799-2.567l-6.773-.602L13.29.89a1.38 1.38 0 0 0-2.581 0l-2.65 6.53-6.774.602C.052 8.126-.453 9.74.486 10.59l5.147 4.666-1.542 6.926c-.28 1.262 1.023 2.26 2.09 1.585L12 20.099z"></path>
</svg>
{{ bangumiCollectionItem.subject.score || '暂无' }}
</span>
</div>
</template>
<style scoped lang="scss">
// 变量定义
$main-color: var(--db-main-color);
$hover-color: var(--db-hover-color);
$text-color: var(--db--text-color);
$text-color-light: var(--db--text-color-light);
$border-radius: var(--thyuu--size-radius);
$card-normal-size: var(--thyuu--size-card-normal);
$small-size: var(--thyuu--size-small);
$animation: opacity .5s var(--animation-in) backwards, transform 1s var(--animation-in) backwards, filter .7s var(--animation-in);
// 卡片样式表
.banguimItem {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: flex-end;
align-items: center;
text-align: center;
width: 100%;
height: 100%;
padding: 1em;
gap: .5em;
margin: 0 20px 20px 0;
border-radius: $border-radius;
background: #000;
overflow: hidden;
position: relative;
.banguimImage {
position: absolute;
width: 100%;
height: 100%;
border-radius: 0;
-webkit-mask: linear-gradient(#0006, #000c, #0000);
transition: ease-in-out .3s;
object-fit: cover;
inset: 0;
}
.title {
order: -1;
z-index: 1;
flex: 100%;
position: relative;
padding: .5em 1em;
margin: 0;
border-radius: 1em;
line-height: 1;
font-weight: 400;
color: white;
width: auto;
margin-top: 2px;
font-size: 14px;
line-height: 1.4;
color: $text-color;
&::before {
content: "\e667";
display: inline-block;
text-indent: 0;
margin: 0 .25em 0 0;
rotate: 45deg;
scale: .75;
transition: rotate .5s;
}
a {
color: hsl(var(--thyuu--main-color));
}
}
.dateSignpost, .score {
position: relative;
padding: .5em 1em;
margin: 0;
border-radius: 1em;
line-height: 1;
font-weight: 400;
color: white;
width: auto;
background: #f5c518;
color: #000;
border-radius: 4px;
line-height: 1;
padding: 3px 5px;
font-size: 12px;
display: flex;
margin-bottom: 2px;
font-weight: 900;
color: #ffffffb3 !important;
background: #ffffff1c !important;
-webkit-backdrop-filter: saturate(1.8) blur(20px);
backdrop-filter: saturate(1.8) blur(20px);
font-size: $small-size !important;
}
.dateSignpost:after {
all: unset;
content: '标记';
margin: 0 0 0 .5em;
}
.score {
svg {
fill: #f5c518;
margin-right: 5px;
}
}
}
</style>
追更页面全局api加载模块
// import type { BangumiApiResponse } from '../types/bangumi'
// export type ContentType = 'anime' | 'game' | 'real' | 'book' | 'music'
// export type CollectionType = keyof typeof TYPE_ID_MAP
// export const ITEMS_PER_PAGE = 20
// 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 = 'kemiao'
// const subjectType = computed(() => contentType === 'anime' ? 2 : contentType === 'game' ? 4 : contentType === 'book' ? 1 : contentType === 'music' ? 3 : 6)
// 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: () =>
// `bangumi-${contentType}-${collectionType.value}-page-${page.value}`,
// },
// )
// const totalPages = computed(() =>
// data.value ? Math.ceil(data.value.total / ITEMS_PER_PAGE) : 0,
// )
// return {
// data,
// status,
// error,
// totalPages,
// }
// }
import type { BangumiApiResponse } from '~/types/bangumi'
export type ContentType = keyof typeof TYPE_SUBJECT_MAP
export type CollectionType = keyof typeof TYPE_ID_MAP
export const ITEMS_PER_PAGE = 20
const TYPE_SUBJECT_MAP = {
book: 1,
anime: 2,
music: 3,
game: 4,
} as const
const TYPE_ID_MAP = {
wish: 1,
collect: 2,
do: 3,
on_hold: 4,
dropped: 5,
} as const
export default function useBangumiCollection(
contentType: Ref<ContentType> = ref('anime'),
collectionType: Ref<CollectionType> = ref('wish'),
page: Ref<number> = ref(1),
) {
const username = '1152095'
const subjectType = computed(() => TYPE_SUBJECT_MAP[toValue(contentType)])
const typeId = computed(() => TYPE_ID_MAP[toValue(collectionType)])
const offset = computed(() => (page.value - 1) * ITEMS_PER_PAGE)
const { data, status, error, refresh } = 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: () =>
`bangumi-${contentType}-${collectionType.value}-page-${page.value}`,
},
)
const totalPages = computed(() =>
data.value ? Math.ceil(data.value.total / ITEMS_PER_PAGE) : 0,
)
return {
data,
status,
error,
totalPages,
refresh,
}
}
追更页面全局数据类型及数据存储库
// Bangumi API 响应类型
export interface BangumiApiResponse {
data: BangumiCollectionItem[]
total: number
limit: number
offset: number
}
// 单个收藏项
export interface BangumiCollectionItem {
updated_at: string // ISO 8601 格式时间
comment: string | null
tags: Tag[]
subject: Subject
subject_id: number
vol_status: number
ep_status: number
subject_type: number // 2=动画,4=游戏
type: number // 收藏类型
rate: number // 用户评分
private: boolean
}
// 作品主体信息
export interface Subject {
date: string // YYYY-MM-DD
images: {
small: string
grid: string
large: string
medium: string
common: string
}
name: string// 日文原名
name_cn: string// 中文译名
short_summary: string
tags: Tag[]
score: number // 社区评分
type: number // 作品类型
id: number // 作品ID
eps: number // 集数
volumes: number // 卷数
collection_total: number // 收藏人数
rank: number // 排名
}
export interface Tag {
name: string
count: number
total_cont: number
}
效果展示


评论加载中...