阅读时间统计
自动计算并显示文章的预估阅读时间。
组件实现
vue
<!-- .vitepress/theme/components/ReadingTime.vue -->
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed } from 'vue'
const { page } = useData()
interface Props {
wordsPerMinute?: number
template?: string
}
const props = withDefaults(defineProps<Props>(), {
wordsPerMinute: 300, // 中文约 300 字/分钟
template: '{min} 分钟阅读'
})
const readingTime = computed(() => {
const content = page.value.content
if (!content) return 0
// 计算字数(中文)
const chineseChars = (content.match(/[\u4e00-\u9fa5]/g) || []).length
// 计算英文单词
const englishWords = (content.match(/[a-zA-Z]+/g) || []).length
// 总字数(英文单词计为 1 字)
const totalWords = chineseChars + englishWords
// 计算分钟数
const minutes = Math.ceil(totalWords / props.wordsPerMinute)
return Math.max(1, minutes)
})
const displayText = computed(() => {
return props.template.replace('{min}', String(readingTime.value))
})
</script>
<template>
<span class="reading-time">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
<span>{{ displayText }}</span>
</span>
</template>
<style scoped>
.reading-time {
display: inline-flex;
align-items: center;
gap: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.85rem;
}
.reading-time svg {
opacity: 0.7;
}
</style>注册全局组件
ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import ReadingTime from './components/ReadingTime.vue'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('ReadingTime', ReadingTime)
}
}在文档中使用
方式一:在模板中使用
ts
// .vitepress/theme/index.ts
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import ReadingTime from './components/ReadingTime.vue'
export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
'doc-before': () => h(ReadingTime)
})
}
}方式二:在 Markdown 中使用
markdown
---
title: 我的文章
---
<ReadingTime />
## 文章内容
...高级:带字数统计
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed } from 'vue'
const { page } = useData()
const stats = computed(() => {
const content = page.value.content || ''
// 中文字符
const chineseChars = (content.match(/[\u4e00-\u9fa5]/g) || []).length
// 英文单词
const englishWords = (content.match(/[a-zA-Z]+/g) || []).length
// 代码块行数
const codeLines = (content.match(/```[\s\S]*?```/g) || [])
.reduce((acc, block) => acc + block.split('\n').length - 2, 0)
// 总字数
const totalWords = chineseChars + englishWords
// 阅读时间(分钟)
const readingMinutes = Math.max(1, Math.ceil(totalWords / 300))
return {
words: totalWords,
chinese: chineseChars,
english: englishWords,
codeLines,
readingMinutes
}
})
</script>
<template>
<div class="article-stats">
<span class="stat">
<svg width="16" height="16" viewBox="0 0 24 24">
<path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
</svg>
{{ stats.words }} 字
</span>
<span class="stat">
<svg width="16" height="16" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
{{ stats.readingMinutes }} 分钟
</span>
<span class="stat" v-if="stats.codeLines > 0">
<svg width="16" height="16" viewBox="0 0 24 24">
<polyline points="16 18 22 12 16 6"></polyline>
<polyline points="8 6 2 12 8 18"></polyline>
</svg>
{{ stats.codeLines }} 行代码
</span>
</div>
</template>
<style scoped>
.article-stats {
display: flex;
gap: 1rem;
padding: 0.75rem 0;
margin-bottom: 1rem;
border-bottom: 1px solid var(--vp-c-divider);
}
.stat {
display: inline-flex;
align-items: center;
gap: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.85rem;
}
.stat svg {
stroke: currentColor;
stroke-width: 2;
fill: none;
}
</style>在列表页显示
vue
<!-- 在博客列表中使用 -->
<script setup lang="ts">
import ReadingTime from './ReadingTime.vue'
const posts = [
{ title: '文章 1', content: '...' },
{ title: '文章 2', content: '...' }
]
function getReadingTime(content: string) {
const words = (content.match(/[\u4e00-\u9fa5]|[a-zA-Z]+/g) || []).length
return Math.max(1, Math.ceil(words / 300))
}
</script>
<template>
<article v-for="post in posts" :key="post.title">
<h2>{{ post.title }}</h2>
<span class="time">{{ getReadingTime(post.content) }} 分钟阅读</span>
</article>
</template>