在 Vuepress Theme Hope 中展示站点信息
本页信息
页面访问量
0
评论数量 0
本页链接 点击复制
本教程将教你如何在 VuePress Theme Hope 博客中添加一个美观的站点信息展示组件,显示文章数量、标签数量、分类数量、评论数量以及网站运行时间。
效果预览
𝓈𝓲𝓽𝓮 𝓲𝓷𝓯𝓸
站点信息
0文章
0标签
0分类
0评论
🎉 本站已运行:
实现步骤
1:创建自定义插件
首先,我们需要创建一个 VuePress 插件,在构建时统计站点信息。
创建文件 .vuepress/plugins/site-info.js:
import { path } from '@vuepress/utils'
import { writeFileSync } from 'fs'
/**
* 站点统计插件
* 在编译时统计文章数量、标签、分类等信息
*/
export const siteInfoPlugin = () => ({
name: 'vuepress-plugin-site-info',
onInitialized(app) {
console.log('[site-info-plugin] Generating Site Info')
// 收集所有页面信息
const pages = app.pages
const blogPages = pages.filter(page => {
// 过滤出博客文章(排除首页和非文章页面)
return page.filePathRelative?.startsWith('blog/') &&
!page.frontmatter.home &&
!page.frontmatter.layout
})
// 统计数据
const stats = {
postCount: blogPages.length,
tags: new Set(),
categories: new Set(),
}
// 遍历所有文章收集信息
blogPages.forEach(page => {
// 收集标签
if (page.frontmatter.tags && Array.isArray(page.frontmatter.tags)) {
page.frontmatter.tags.forEach(tag => stats.tags.add(tag))
}
// 收集分类 - 从文件路径中提取(blog/ 目录下的第一级子目录)
if (page.filePathRelative) {
// 移除 'blog/' 前缀,然后取第一个目录段作为分类
const relativePath = page.filePathRelative.replace('blog/', '')
const category = relativePath.split('/')[0]
if (category && category !== relativePath) {
// 只有在有子目录的情况下才认为是分类
stats.categories.add(category)
}
}
})
// 转换为最终格式
const statsData = {
postCount: stats.postCount,
tagCount: stats.tags.size,
categoryCount: stats.categories.size,
updatedAt: new Date().toISOString()
}
// 生成 JSON 文件到 public 目录
const outputPath = path.resolve(app.dir.public(), 'site-info.json')
writeFileSync(outputPath, JSON.stringify(statsData, null, 2))
console.log('[site-info-plugin] Site Info Generated:\n', statsData)
}
})2:注册插件
在 .vuepress/config.js 中注册插件:
import { siteInfoPlugin } from './plugins/site-info.js'
export default defineUserConfig({
// ... 其他配置
plugins: [
// ... 其他插件
siteInfoPlugin(),
],
})3:创建站点信息组件
创建任意vue组件,如 .vuepress/components/SiteInfo.vue:
<template>
<div class="site-info-card">
<h3 class="site-info-title">📊 站点信息</h3>
<div class="site-info-content">
<!-- 文章数量 -->
<div class="site-info-item">
<span class="site-info-number">{{ stats.postCount ?? 0 }}</span>
<span class="site-info-label">文章</span>
</div>
<!-- 标签数量 -->
<div class="site-info-item">
<span class="site-info-number">{{ stats.tagCount ?? 0 }}</span>
<span class="site-info-label">标签</span>
</div>
<!-- 分类数量 -->
<div class="site-info-item">
<span class="site-info-number">{{ stats.categoryCount ?? 0 }}</span>
<span class="site-info-label">分类</span>
</div>
</div>
<!-- 网站运行时间 -->
<div class="site-running-time">
<span class="running-time-label">🎉 本站已运行:</span>
<span class="running-time-value">{{ runningTime }}</span>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const siteCreatedDate = ref(new Date('2024-01-01')) // 修改为你的站点创建日期
const runningTime = ref('')
let timer = null
const stats = ref({
postCount: 0,
tagCount: 0,
categoryCount: 0,
updatedAt: ''
})
// 计算运行时间
const updateRunningTime = () => {
const now = new Date()
const created = siteCreatedDate.value
const diff = now.getTime() - created.getTime()
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
const hours = Math.floor((diff / (1000 * 60 * 60)) % 24)
const minutes = Math.floor((diff / (1000 * 60)) % 60)
const seconds = Math.floor((diff / 1000) % 60)
runningTime.value = `${days}天 ${hours}小时 ${minutes}分 ${seconds}秒`
}
onMounted(async () => {
// 立即更新一次
updateRunningTime()
// 每秒更新一次运行时间
timer = setInterval(updateRunningTime, 1000)
try {
// 加载站点统计信息
const response = await fetch('/site-info.json')
const data = await response.json()
stats.value = data
} catch (error) {
console.error('加载站点信息失败:', error)
}
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<style scoped>
.site-info-card {
padding: 20px;
background: var(--body-bg-color);
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
margin: 20px 0;
}
.site-info-title {
margin: 0 0 20px 0;
font-size: 1.25rem;
color: var(--text-color);
text-align: center;
}
.site-info-content {
display: flex;
justify-content: space-around;
align-items: center;
flex-wrap: wrap;
gap: 15px;
}
.site-info-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 12px 20px;
background: rgba(125, 125, 125, 0.1);
border-radius: 8px;
min-width: 80px;
transition: background-color 0.3s ease;
}
.site-info-item:hover {
background: rgba(125, 125, 125, 0.15);
}
.site-info-number {
font-size: 1.5rem;
font-weight: bold;
color: var(--theme-color);
}
.site-info-label {
font-size: 0.875rem;
color: var(--text-color-light);
}
.site-running-time {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
padding: 12px 20px;
margin-top: 15px;
background: rgba(125, 125, 125, 0.08);
border-radius: 8px;
font-size: 0.875rem;
}
.running-time-label {
color: var(--text-color-light);
}
.running-time-value {
color: var(--theme-color);
font-weight: 600;
}
@media (max-width: 768px) {
.site-info-content {
gap: 10px;
}
.site-info-item {
padding: 8px 12px;
min-width: 70px;
}
.site-info-number {
font-size: 1.25rem;
}
}
</style>4:在页面中使用
在 Markdown 文件中直接使用
在你的 Markdown 文件(如 README.md)中直接使用组件:
<!-- 其他内容 -->
<script setup lang='ts'>
import SiteInfo from '/.vuepress/theme/components/SiteInfo.vue'
</script>
<SiteInfo />
<!-- 其他内容 -->在首页布局中使用
在任意布局文件中引入: 此处以布局文件 .vuepress/layouts/home.vue 为例
<template>
<div class="home-page">
<header>
<h1>我的博客</h1>
</header>
<SiteInfo />
<main>
<slot />
</main>
</div>
</template>
<script setup>
import SiteInfo from '../components/SiteInfo.vue'
</script>说明
插插件何时执行?
插件在每次构建或启动开发服务器时执行,修改文章后重新构建才会更新统计信息
分类如何统计?
自动统计 blog/ 目录下的第一级子目录(与vuepress-theme-hope 默认分类规则一致)
如何传递信息?
通过执行插件生成./vuepress/public/site-info.json储存站点统计信息,组件通过fetch获取数据并渲染
进阶配置
添加动画效果
为数字添加计数动画:
<script setup>
import { ref, computed } from 'vue'
const animatedNumber = (target) => {
const current = ref(0)
const step = () => {
if (current.value < target) {
current.value++
requestAnimationFrame(step)
}
}
step()
return current
}
</script>统计评论数
重要
以下教程基于Waline 评论系统(因为本站使用 Waline 评论系统),若不同请自行查找文档
<template>
<div class="site-info-card">
<h3 class="site-info-title">📊 站点信息</h3>
<div class="site-info-content">
<!-- 文章数量 -->
<div class="site-info-item">
<span class="site-info-number">{{ stats.postCount ?? 0 }}</span>
<span class="site-info-label">文章</span>
</div>
<!-- 标签数量 -->
<div class="site-info-item">
<span class="site-info-number">{{ stats.tagCount ?? 0 }}</span>
<span class="site-info-label">标签</span>
</div>
<!-- 分类数量 -->
<div class="site-info-item">
<span class="site-info-number">{{ stats.categoryCount ?? 0 }}</span>
<span class="site-info-label">分类</span>
</div>
<!-- 评论数量 -->
<div class="site-info-item">
<span class="site-info-number">{{ commentCount ?? 0 }}</span>
<span class="site-info-label">评论</span>
</div>
</div>
<!-- 网站运行时间 -->
<div class="site-running-time">
<span class="running-time-label">🎉 本站已运行:</span>
<span class="running-time-value">{{ runningTime }}</span>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const commentCount = ref(0)
const siteCreatedDate = ref(new Date('2024-01-01')) // 修改为你的站点创建日期
const runningTime = ref('')
let timer = null
const stats = ref({
postCount: 0,
tagCount: 0,
categoryCount: 0,
updatedAt: ''
})
// 计算运行时间
const updateRunningTime = () => {
const now = new Date()
const created = siteCreatedDate.value
const diff = now.getTime() - created.getTime()
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
const hours = Math.floor((diff / (1000 * 60 * 60)) % 24)
const minutes = Math.floor((diff / (1000 * 60)) % 60)
const seconds = Math.floor((diff / 1000) % 60)
runningTime.value = `${days}天 ${hours}小时 ${minutes}分 ${seconds}秒`
}
onMounted(async () => {
// 立即更新一次
updateRunningTime()
// 每秒更新一次运行时间
timer = setInterval(updateRunningTime, 1000)
try {
// 加载站点统计信息
const response = await fetch('/site-info.json')
const data = await response.json()
stats.value = data
const commentCountResponse = await fetch('https://your.waline.site/api/comment?type=count', { signal: controller.signal })
const commentCountData = await commentCountResponse.json()
commentCount.value = commentCountData.data
} catch (error) {
console.error('加载站点信息失败:', error)
}
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<style scoped>
.site-info-card {
padding: 20px;
background: var(--body-bg-color);
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
margin: 20px 0;
}
.site-info-title {
margin: 0 0 20px 0;
font-size: 1.25rem;
color: var(--text-color);
text-align: center;
}
.site-info-content {
display: flex;
justify-content: space-around;
align-items: center;
flex-wrap: wrap;
gap: 15px;
}
.site-info-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 12px 20px;
background: rgba(125, 125, 125, 0.1);
border-radius: 8px;
min-width: 80px;
transition: background-color 0.3s ease;
}
.site-info-item:hover {
background: rgba(125, 125, 125, 0.15);
}
.site-info-number {
font-size: 1.5rem;
font-weight: bold;
color: var(--theme-color);
}
.site-info-label {
font-size: 0.875rem;
color: var(--text-color-light);
}
.site-running-time {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
padding: 12px 20px;
margin-top: 15px;
background: rgba(125, 125, 125, 0.08);
border-radius: 8px;
font-size: 0.875rem;
}
.running-time-label {
color: var(--text-color-light);
}
.running-time-value {
color: var(--theme-color);
font-weight: 600;
}
@media (max-width: 768px) {
.site-info-content {
gap: 10px;
}
.site-info-item {
padding: 8px 12px;
min-width: 70px;
}
.site-info-number {
font-size: 1.25rem;
}
}
</style>总结
通过以上步骤,你就可以在 VuePress Theme Hope 博客中添加一个功能完善的站点信息展示组件。这个组件不仅能够展示站点的基本统计信息,还能实时显示网站运行时间,提升用户体验。
如果你有任何问题或建议,欢迎在评论区留言!

