很久以前就想在我的vitepress站点上加入官方没有的字数统计以及目录中加入一个返回上一级的按钮

VitePress 更新日期显示组件 | Ceil.Top的启发,加上最近正好处于熟悉vue的阶段,就正式开始了我开发流程

先贴几个用的到的官方文档

官方提供的Api:运行时 API | VitePress

扩展默认主题、插槽扩展默认主题 | VitePress

扩展主题

先按照文档中扩展自己的布局

...\docs\.vitepress\theme中定义一个MyLayout.vue

1
2
3
4
5
6
7
8
9
<script setup>
import DefaultTheme from 'vitepress/theme'
const { Layout } = DefaultTheme
</script>

<template>
<Layout>
</Layout>
</template>

找到index.ts or index.js,修改其中的 Layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// https://vitepress.dev/guide/custom-theme
import { h } from 'vue'
import MyLayout from './MyLayout.vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'



export default {
extends: DefaultTheme,
Layout: MyLayout,
// Layout: () => {
// return h(DefaultTheme.Layout, null, {
// // https://vitepress.dev/guide/extending-default-theme#layout-slots
// })
// },
enhanceApp({ app, router, siteData }) {
// ...
}
} satisfies Theme

此时就可以在MyLayout.vue使用插槽添加扩展自己的布局了

自定义布局需要参考自定义主题 | VitePress,使用<Content />转化文章,而不是使用<Layout>默认布局

添加字数统计

效果预览:

image-20241016034813359

在你能找到的地方添加一个vue文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vitepress'

const wordCount = ref(0)
const router = useRouter()
const route = useRoute()

const calculateWordCount = () => {
const content = document.querySelector('.vp-doc')
if (content) {
// 计算字数,去除空白符
const text = content.innerText.match(/\S/g);
wordCount.value = text?text.length:0;

}

}

onMounted(() => {
// 初次加载时计算字数
calculateWordCount()
// 监听整个 route 对象
watch(
() => route.path,
(newPath, oldPath) => {
// console.log('路由变化到:', newPath)
calculateWordCount() // 路由变化后重新计算字数
}
)
})
</script>

<template>
<div>
<hr>
<p>本篇笔记字数: {{ wordCount }}</p>
</div>
</template>

实现原理,通过获取Dom元素(document.querySelector('.vp-doc'))得到文章的innerText长度,并通过正则表达式过滤,只留下非空字符(const text = content.innerText.match(/\S/g);)。

组件挂载时回调函数获取字数并通过模板语法插入模板,并通过监听路由变化实现不同文章的字数统计切换

在刚刚的MyLayout.vue中引入字数统计的vue文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup>
import DefaultTheme from 'vitepress/theme'

import ContentSize from './extend/ContentSize.vue' //引入vue文件

const { Layout } = DefaultTheme
</script>

<template>
<Layout>

<template #aside-ads-after> //插槽插入指定位置
<ContentSize /> //c
</template>

</Layout>
</template>
官网介绍的插槽可使用的位置

默认主题布局的全部可用插槽如下:

  • layout: 'doc'(默认) 在 frontmatter 中被启用时:

    • doc-top
    • doc-bottom
    • doc-footer-before
    • doc-before
    • doc-after
    • sidebar-nav-before
    • sidebar-nav-after
    • aside-top
    • aside-bottom
    • aside-outline-before
    • aside-outline-after
    • aside-ads-before
    • aside-ads-after
  • layout: 'home'在 frontmatter 中被启用时:

    • home-hero-before
    • home-hero-info-before
    • home-hero-info
    • home-hero-info-after
    • home-hero-actions-after
    • home-hero-image
    • home-hero-after
    • home-features-before
    • home-features-after
  • layout: 'page'在 frontmatter 中被启用时:

    • page-top
    • page-bottom
  • 当未找到页面 (404) 时:

    • not-found
  • 总是启用:

    • layout-top
    • layout-bottom
    • nav-bar-title-before
    • nav-bar-title-after
    • nav-bar-content-before
    • nav-bar-content-after
    • nav-screen-content-before
    • nav-screen-content-after

返回上一级

效果预览:

image-20241016035129123

返回上一级的vue文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<script setup>
import { ref, watch,onMounted } from 'vue'
import { useRouter, useRoute } from 'vitepress'

const router = useRouter()
const route = useRoute()
const BeforeUrl = ref(router.path)
const isNoMain = ref(route.path==='/main'? false:true)

onMounted(() => {

// 监听整个 route 对象
watch(() => route.path, (newPath, oldPath) => {
const path = route.path;

isNoMain.value = path==='/main'? false:true;

BeforeUrl.value = getStringBeforeNSlashes(path);

})
})


function getStringBeforeNSlashes(str) {
let currentIndex = str.length;
let last = str.lastIndexOf('/')
let start = str.indexOf('/')

// 此时是根目录
if(last === start){
return '/main';
}

for (let i = 0; i < 1; i++) {
const slashIndex = str.lastIndexOf('/', currentIndex - 1);

if (slashIndex === -1) {
return '/main'; // 如果没有更多的 '/', 返回整个字符串
}

currentIndex = slashIndex; // 更新当前索引为找到的 '/'
}

return str.substring(0, currentIndex); // 返回倒数第 n 个 '/' 之前的子字符串
}

const goBack = () => {
if (BeforeUrl.value) {
router.go(BeforeUrl.value)
}
}
</script>

<template >
<!-- 不是main时触发 -->
<div v-if="isNoMain">
<hr>
<button @click="()=>goBack()">返回上一级</button>
<hr>
</div>

</template>

此时需要在MyLoyot加入这个vue文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup>
import DefaultTheme from 'vitepress/theme'
import ContentSize from './extend/ContentSize.vue'
import Return from './extend/Return.vue'
const { Layout } = DefaultTheme
</script>

<template>
<Layout>
<template #sidebar-nav-before>
<Return />
</template>
<template #aside-ads-after>
<ContentSize />
</template>
</Layout>
</template>

解析有时间再出