149 lines
4.0 KiB
Vue
149 lines
4.0 KiB
Vue
<script setup lang="ts">
|
||
// 定义组件参数类型
|
||
interface Props {
|
||
perPage?: number // 每页显示的文章数,默认 12
|
||
maxPages?: number // 最大页数,默认 15
|
||
}
|
||
|
||
// 接收 props,设置默认值
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
perPage: 12,
|
||
maxPages: 15
|
||
})
|
||
|
||
// 分页状态
|
||
const currentPage = ref(1)
|
||
|
||
// 获取所有博客文章
|
||
const { data: allArticles } = await useAsyncData('blog-all-articles', () => {
|
||
return queryCollection('blog')
|
||
.select('path', 'title', 'description', 'img', 'date')
|
||
.where('path', 'NOT LIKE', '%navigation%')
|
||
.all()
|
||
})
|
||
|
||
// 按时间排序的文章列表
|
||
const sortedArticles = computed(() => {
|
||
if (!allArticles.value) return []
|
||
|
||
return [...allArticles.value].sort((a, b) => {
|
||
// 如果没有日期,排在最后
|
||
if (!a.date && !b.date) return 0
|
||
if (!a.date) return 1
|
||
if (!b.date) return -1
|
||
|
||
// 按日期降序排列(最新的在前面)
|
||
const dateA = new Date(a.date)
|
||
const dateB = new Date(b.date)
|
||
return dateB.getTime() - dateA.getTime()
|
||
})
|
||
})
|
||
|
||
// 计算总页数,限制最大页数
|
||
const totalPages = computed(() => {
|
||
if (!sortedArticles.value) return 1
|
||
const calculatedPages = Math.ceil(sortedArticles.value.length / props.perPage)
|
||
return Math.min(calculatedPages, props.maxPages)
|
||
})
|
||
|
||
// 计算当前页显示的文章
|
||
const paginatedArticles = computed(() => {
|
||
if (!sortedArticles.value) return []
|
||
|
||
const startIndex = (currentPage.value - 1) * props.perPage
|
||
const endIndex = startIndex + props.perPage
|
||
|
||
return sortedArticles.value.slice(startIndex, endIndex)
|
||
})
|
||
|
||
// 格式化日期函数
|
||
const formatDate = (date: string | Date | null | undefined) => {
|
||
if (!date) return '未知时间'
|
||
try {
|
||
const d = new Date(date)
|
||
return d.toLocaleDateString('zh-CN', {
|
||
year: 'numeric',
|
||
month: 'long',
|
||
day: 'numeric'
|
||
})
|
||
} catch {
|
||
return '未知时间'
|
||
}
|
||
}
|
||
|
||
// 从路径中提取分类标签
|
||
const getCategoryFromPath = (path: string) => {
|
||
// 路径格式: /blog/技术栈/xxx 或 /blog/AI/xxx 或 /blog/生活/xxx
|
||
const match = path.match(/\/blog\/(.+?)\//)
|
||
return match ? match[1] : '技术栈'
|
||
}
|
||
|
||
// 转换文章数据为 UBlogPosts 需要的格式
|
||
const blogPosts = computed(() => {
|
||
if (!paginatedArticles.value) return []
|
||
|
||
return paginatedArticles.value.map(article => ({
|
||
title: article.title || '未命名文章',
|
||
description: article.description || '',
|
||
date: formatDate(article.date),
|
||
image: article.img || '/images/default-blog.jpg',
|
||
to: article.path,
|
||
badge: {
|
||
label: getCategoryFromPath(article.path),
|
||
color: 'primary' as const,
|
||
variant: 'subtle' as const
|
||
}
|
||
}))
|
||
})
|
||
|
||
// 监听页面变化,确保页面在有效范围内
|
||
watch(currentPage, (newPage) => {
|
||
if (newPage > totalPages.value && totalPages.value > 0) {
|
||
currentPage.value = totalPages.value
|
||
} else if (newPage < 1) {
|
||
currentPage.value = 1
|
||
}
|
||
})
|
||
|
||
// 当文章数据变化时,重置到第一页
|
||
watch(sortedArticles, () => {
|
||
if (currentPage.value > totalPages.value && totalPages.value > 0) {
|
||
currentPage.value = 1
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="w-full bg-gray-50 dark:bg-gray-900 min-h-screen">
|
||
<div class="max-w-5xl mx-auto md:px-6 lg:px-8 py-8">
|
||
<!-- 有文章时显示内容 -->
|
||
<div v-if="sortedArticles && sortedArticles.length > 0">
|
||
<div class="">
|
||
<UBlogPosts
|
||
orientation="vertical"
|
||
:posts="blogPosts"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 分页控件 -->
|
||
<div class="flex justify-center pt-8 border-t border-gray-200 dark:border-gray-700">
|
||
<div class="pt-6">
|
||
<UPagination
|
||
v-model:page="currentPage"
|
||
:total="sortedArticles.length"
|
||
:items-per-page="props.perPage"
|
||
:max="7"
|
||
size="md"
|
||
show-edges
|
||
:show-first="totalPages > 7"
|
||
:show-last="totalPages > 7"
|
||
color="primary"
|
||
variant="outline"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|