Plume主题增强之首页增加最新文章模块
介绍
在首页以卡片网格形式展示各分类下的最新文章,每个分类最多显示 3 篇。
- 每张卡片包含分类名称(可点击跳转到分类页)和文章列表(标题 + 相对时间)
- 时间显示格式:7 天内显示"今天 / 昨天 / N 天前",超过 7 天显示"月-日"
- 响应式布局:桌面端 3 列,平板端 2 列,移动端 1 列
效果预览

项目结构总览
docs/
├── index.md ← 修改:添加 <LatestPosts /> 标签
└── .vuepress/
├── components/
│ └── LatestPosts.vue ← 新增:最新文章组件
├── config.ts ← 修改:在 extendsPage 中注入 createTime 到路由元数据
└── client.ts ← 修改:注册 LatestPosts 为全局组件| 文件 | 操作 | 说明 |
|---|---|---|
docs/.vuepress/components/LatestPosts.vue | 新增 | 最新文章卡片组件,包含逻辑与样式 |
docs/.vuepress/config.ts | 修改 | 在 extendsPage 中将 createTime 注入路由元数据 |
docs/.vuepress/client.ts | 修改 | 通过 app.component 注册 LatestPosts 为全局组件 |
docs/index.md | 修改 | 在首页内容中添加 <LatestPosts /> 标签 |
实现过程
1. 获取全站路由数据
VuePress 的 useRoutes() 返回一个包含所有页面路由信息的响应式对象。每个路由条目的 meta 字段即来自 config.ts 中 extendsPage 注入的数据,包含 title 和 createTime:
const routes = useRoutes()
// routes.value 结构示例:
// {
// '/network/routing-switching/bgp/': {
// meta: { title: '什么是BGP?', createTime: '2025/01/01 00:00:00' },
// loader: () => import('...') // 异步加载页面数据
// },
// ...
// }2. 排除目录页(异步 frontmatter 加载)
@vuepress/plugin-catalog 自动生成的目录页在 routeMeta 中没有 isCatalogPage 字段,该字段存在于页面的 frontmatter 里。因此需要在 onMounted 中异步调用每个路由的 loader() 加载完整页面数据,再读取 frontmatter:
onMounted(async () => {
const promises = Object.entries(routes.value).map(async ([path, route]) => {
if (route.loader) {
const pageChunk = await route.loader() // 异步加载页面 chunk
if (pageChunk?.data?.frontmatter) {
return [path, pageChunk.data.frontmatter]
}
}
return [path, null]
})
const results = await Promise.all(promises) // 并发加载所有页面
results.forEach(([path, fm]) => {
if (fm) pageFrontmatters.value[path] = fm
})
})3. 过滤与分类
allPosts computed 属性负责从全站路由中筛选出有效文章(排除博客列表页、404、目录页),并按 createTime 倒序排列:
const allPosts = computed(() => {
return Object.entries(routes.value)
.filter(([path, route]) => {
// 排除特殊页面
if (path.includes('/blog/') || path === '/' || path.includes('/404')) return false
// 必须有 createTime
if (!route.meta?.createTime) return false
// 排除 isCatalogPage
if (pageFrontmatters.value[path]?.isCatalogPage) return false
return true
})
.map(([path, route]) => ({
path,
title: route.meta.title || path,
createTime: route.meta.createTime,
}))
.sort((a, b) => new Date(b.createTime) - new Date(a.createTime))
})categoriesWithPosts 再按预定义的 categoryConfig 路径前缀过滤,每个分类取前 3 篇:
const categoriesWithPosts = computed(() =>
categoryConfig.map(config => ({
...config,
posts: allPosts.value
.filter(post => post.path.startsWith(config.path)) // 路径前缀匹配
.slice(0, 3), // 最多3篇
}))
)4. 相对时间格式化
formatDate 函数将 ISO 时间字符串转换为对用户友好的相对时间,7 天内显示「今天 / 昨天 / N 天前」,更早的显示「月-日」:
function formatDate(dateStr) {
const diffDays = Math.floor((Date.now() - new Date(dateStr)) / 86400000)
if (diffDays === 0) return '今天'
if (diffDays === 1) return '昨天'
if (diffDays <= 7) return `${diffDays}天前`
const d = new Date(dateStr)
return `${String(d.getMonth() + 1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
}使用方法
第一步:修改 config.ts
在 docs/.vuepress/config.ts 的 extendsPage 中确保包含 createTime 字段:
export default defineUserConfig({
extendsPage: (page) => {
page.routeMeta = {
title: page.title,
createTime: page.frontmatter.createTime,
}
},
})第二步:新增组件文件
将 LatestPosts.vue 创建到 docs/.vuepress/components/ 目录下。
第三步:配置分类
打开 LatestPosts.vue,修改 <script setup> 中的 categoryConfig 数组,填写需要展示的分类名称和对应路径:
const categoryConfig = [
{ name: '路由交换', path: '/network/routing-switching/' },
{ name: 'Kubernetes', path: '/compute/kubernetes/' },
// ... 更多分类
]第四步:修改 client.ts
在 docs/.vuepress/client.ts 中注册全局组件:
import { defineClientConfig } from 'vuepress/client'
import LatestPosts from './components/LatestPosts.vue'
export default defineClientConfig({
enhance({ app }) {
app.component('LatestPosts', LatestPosts)
},
})第五步:修改 docs/index.md
在首页 docs/index.md 中添加组件标签(放在 frontmatter 下方的 Markdown 内容区域):
<LatestPosts />