Files
estel_docs/app/components/blog/BlogLists.vue
2025-08-15 22:06:27 +08:00

149 lines
4.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>