Files
blog-press/docs/Others/VitePress.md
2026-02-26 19:01:03 +08:00

557 lines
14 KiB
Markdown
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.

---
title: VitePress简介和使用
date: 2025-12-18
---
# 一、简介
  [VitePress](https://vitepress.dev/zh/)是由 Vue 团队开发的静态网站生成器 (SSG),基于 Vite前端构建工具和 Vue 3 构建,专门用于快速搭建文档网站、博客或个人主页。它继承了 Vite 的极速开发体验,同时具备 Vue 的组件化能力,语法上兼容 Markdown 并支持扩展 Vue 组件,是替代 VuePress 的新一代工具。
# 二、快速开始
## 2.1 安装
```sh
pnpm add -D vitepress@next
```
## 2.2 初始化
```sh
pnpm vitepress init
```
::: tip
建议按照官网配置,文档目录设置为`./docs`
:::
## 2.3 运行
```sh
pnpm run docs:dev
```
## 2.4 使用
  `docs`文件夹新建`md`文档,并在`.vitepress/config.mts`中配置`nav``sidebar`路径。
# 三、优化
## 3.1 路由组件
  每次新建文档时,都需要在`.vitepress/config.mts`重复`nav``sidebar`路径,有时还需要在文档目录的`index.md`配置导航链接每次都要重复写3次很麻烦因此可以构造一个路由配置清单和路由组件每次只需更新一次路由配置清单即可。
  `.vitepress/theme/router`文件夹下新建路由清单文件:
```ts
export const routers = [
{
text: '🌐 Web前端',
items: [
{
text: '🌿 Vue',
items: [
{ text: 'Test', link: '/Web-Front/Vue/Test' }
]
},
{
text: '🔄 其他',
items: [
{ text: 'Test', link: '/Web-Front/Others/Test' }
]
},
]
},
{
text: '🖥️ Web后端',
items: [
{
text: '🍃 SpringBoot',
items: [
{ text: 'Test', link: '/Web-Backend/SpringBoot/Test' }
]
},
{
text: '🐍 FastAPI',
items: [
{ text: 'Test', link: '/Web-Backend/FastAPI/Test' }
]
},
{
text: '🔄 其他',
items: [
{ text: 'Test', link: '/Web-Backend/Others/Test' }
]
},
]
},
{
text: '🚀 DevOps',
items: [
{ text: 'Test', link: '/DevOps/Test' }
]
}
];
```
  这里列举了有二级菜单和只有一级菜单的两种情况。
  `.vitepress/theme/components`文件夹下新建路由组件`MenuList.vue`
```vue
<template>
<div v-if="isNested">
<div v-for="category in props.routers.items" :key="category.text">
<h3>{{ category.text }}</h3>
<ul>
<li v-for="item in category.items" :key="item.text">
<a :href="withBase(item.link)">{{ item.text }}</a>
</li>
</ul>
</div>
</div>
<div v-else>
<ul>
<li v-for="item in props.routers.items" :key="item.text">
<a :href="withBase(item.link)">{{ item.text }}</a>
</li>
</ul>
</div>
</template>
<script setup>
import { withBase } from 'vitepress'
import { defineProps } from 'vue'
const props = defineProps({
routers: {
required: true,
type: Object
},
isNested: {
type: Boolean,
default: false
}
})
</script>
```
&emsp;&emsp;这里需要使用`withBase`拼接路由,防止存在设置了`base`根路径的情况。通过`isNested`来控制是否存在二级菜单。
::: warning
这里只考虑了最多存在二级菜单的情况。
:::
&emsp;&emsp;在`.vitepress/theme/index.ts`注册组件:
```ts
import MenuList from './components/MenuList.vue'
import type { EnhanceAppContext } from 'vitepress'
export default {
enhanceApp({ app }: EnhanceAppContext) {
app.component("MenuList", MenuList);
},
};
```
&emsp;&emsp;`.vitepress/config.mts`中配置`nav``sidebar`
```ts
export default defineConfig({
themeConfig: {
nav: [
{ text: '🏠 Home', link: '/' },
...routers
],
sidebar: {
'/Web-Front/': [routers[0]],
'/Web-Backend/': [routers[1]],
'/DevOps/': [routers[2]]
},
},
lastUpdated: true,
})
```
&emsp;&emsp;在文档所在目录的`index.md`中配置:
```md
---
layout: doc
title: Web前端
description: Web前端开发技术文档
---
<script setup>
import { routers } from '../.vitepress/theme/router'
</script>
<MenuList :routers=routers[0] :isNested=true />
```
&emsp;&emsp;这样后续只需要维护路由配置清单文件即可自动生成路由菜单。
## 3.2 博客元数据
&emsp;&emsp;可以通过在每个博客中添加`frontmatter`元数据,在最顶部添加博客属性,例如创建日期、字数、时长和是否精品等信息。
### 3.2.1 定义元数据
&emsp;&emsp;在每个博客最上面添加元数据:
```md
---
title: Docker简介和安装
date: 2025-12-15
isGreat: true
---
```
&emsp;&emsp;这里可以自定义添加任何`key: value`形式的字段信息。
::: tip
建议手动加上每个博客的创建日期
:::
### 3.2.2 博客信息组件
&emsp;&emsp;`.vitepress/theme/components`文件夹下新建博客信息组件`ArticleMetadata.vue`
```vue
<template>
<div class="title">
<span>{{ page.frontmatter.title }}</span>
</div>
<div v-if="!page.filePath.includes('index.md')" class="blog-stats">
<div v-if="page.frontmatter.isGreat" class="item">
</svg>
精品
</div>
<div class="item">
</svg>
日期: {{ page.frontmatter.date.split('T')[0] }}
</div>
<div class="item">
</svg>
字数: {{ formatNumberUnit(wordCount) }}
</div>
<div>
</svg>
时长: {{ readTime }} 分钟
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, onMounted, watch } from 'vue'
import { countWord, formatNumberUnit } from '../utils'
import { useData } from 'vitepress'
const wordCount = ref(0)
const imageCount = ref(0)
// 获取页面数据
const { page } = useData()
// 文字阅读时间
const wordTime = computed(() => {
return ((wordCount.value / 275) * 60)
})
// 图片阅读时间
const imageTime = computed(() => {
const n = imageCount.value
if (imageCount.value <= 10) {
// 等差数列求和
return n * 13 + (n * (n - 1)) / 2
}
return 175 + (n - 10) * 3
})
// 阅读时间
const readTime = computed(() => {
return Math.ceil((wordTime.value + imageTime.value) / 60)
})
const analyze = () => {
// 选择文档内容区域
const docDomContainer = window.document.querySelector('#VPContent')
// 统计图片数量
const imgs = docDomContainer?.querySelectorAll<HTMLImageElement>('.content-container .main img')
imageCount.value = imgs?.length || 0
// 统计文字数量textContent提取纯文本
const words = docDomContainer?.querySelector('.content-container .main')?.textContent || ''
wordCount.value = countWord(words)
}
watch(() => page.value.title, () => {
// 路径变化时执行
analyze()
})
onMounted(() => {
// 初始化时执行一次
analyze()
})
</script>
<style scoped>
.blog-stats {
margin-bottom: 10px;
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.title {
margin-bottom: 5px;
padding: 10px 0;
text-align: center;
font-size: 32px;
color: #fff;
font-weight: bold;
background: -webkit-linear-gradient(10deg, #3DC8F0 5%, #8036FA 15%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.icon {
display: inline-block;
transform: translate(0px , 2px);
}
</style>
```
&emsp;&emsp;这里统计了正文部分的文字和图片数量,并通过相应规则计算阅读时间。
&emsp;&emsp;通过`page.frontmatter`获取自定义的元数据信息并在博客最上面显示。
### 3.2.3 注册组件
&emsp;&emsp;`.vitepress/theme/index.ts`注册组件参考3.1节内容)。
### 3.2.4 自定义布局组件
&emsp;&emsp;需要将博客信息组件放在自定义布局组件中。
&emsp;&emsp;`.vitepress/theme/components`文件夹下新建自定义布局组件`MyLayout.vue`
```vue
<template>
<DefaultTheme.Layout v-bind="$attrs">
<template #doc-before>
<ArticleMetadat />
</template>
</DefaultTheme.Layout>
</template>
<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
import ArticleMetadat from './ArticleMetadata.vue';
</script>
```
&emsp;&emsp;更多插槽可以参考[Github源码](https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/Layout.vue)。
&emsp;&emsp;同样需要注册组件。
### 3.2.5 配置布局组件
&emsp;&emsp;`.vitepress/config.mts`中配置布局:
```ts
import DefaultTheme from 'vitepress/theme'
import MyLayout from './components/MyLayout.vue';
export default {
extends: DefaultTheme,
Layout: MyLayout,
};
```
## 3.3 热力图
&emsp;&emsp;使用`vue3-calendar-heatmap`组件在首页绘制博客热力图。
### 3.3.1 安装
```sh
pnpm install vue3-calendar-heatmap
```
### 3.3.2 获取数据
&emsp;&emsp;使用`VitePress`提供的[createContentLoader](https://vitepress.dev/zh/guide/data-loading)来获取所有的`md`博客数据。
&emsp;&emsp;`.vitepress/data`新建`posts.data.js`
```js
import { createContentLoader } from 'vitepress'
export default createContentLoader('./**/*.md', {
includeSrc: true, // 包含原始 markdown 源
render: true, // 包含渲染的整页 HTML
transform(rawData) {
return rawData.filter(page => {
return page.url.includes('.html') && page.frontmatter.date
}).map((page) => {
// 对每个页面进行处理
return {
// 返回你需要的页面数据
title: page.frontmatter.title,
date: page.frontmatter.date,
url: page.url
}
})
}
})
```
&emsp;&emsp;通过判断路径是否包含`html`来确认是博客文档,并且筛选出元数据中包含日期的博客,然后返回相应内容。
### 3.3.3 博客统计组件
&emsp;&emsp;`.vitepress/theme/components`文件夹下新建博客信息组件`StatsChart.vue`
```vue
<template>
<div v-if="!isMobile()">
<calendar-heatmap
:values="heatmapData"
:end-date="new Date()"
no-data-text="暂无记录"
tooltip-unit=""
:round="2"
:locale="{
less: '少于',
more: '多于',
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
days: ['日', '一', '二', '三', '四', '五', '六'],
}"
class="blog-heatmap"
/>
<div class="heatmap-footer">
⏱️ 已坚持<span class="highlight">{{ getDaysDifference(new Date(), startBlogDate) }}</span>
| 📝 已创作<span class="highlight">{{ data.length }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { CalendarHeatmap } from 'vue3-calendar-heatmap';
import 'vue3-calendar-heatmap/dist/style.css';
import { data } from '../../data/posts.data.js'
import { isMobile, getDaysDifference, startBlogDate } from "../utils";
const dateCountMap = new Map<string, number>();
// 按日期分组统计
data.forEach(item => {
const date = item.date
if (dateCountMap.has(date)) {
dateCountMap.set(date, dateCountMap.get(date)! + 1);
} else {
dateCountMap.set(date, 1);
}
});
// 转换为目标格式
const heatmapData = Array.from(dateCountMap.entries()).map(([date, count]) => ({
date,
count
}))
</script>
<style scoped>
.blog-heatmap {
margin-top: 10px;
}
.blog-heatmap :deep(.vch__wrapper) {
font-family: 'CustomFont', sans-serif !important;
}
.blog-heatmap :deep(.vch__month__label) {
font-size: 8px !important;
}
.blog-heatmap :deep(.vch__day__label) {
font-size: 8px !important;
}
.heatmap-footer {
font-size: 15px;
color: #666;
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
}
.highlight {
font-weight: 700;
color: #6366f1;
margin: 0 2px;
font-size: 16px;
}
</style>
```
&emsp;&emsp;根据`posts.data.js`提供的博客数据进行日期分组,并统计已坚持天数和已创作博客数。
### 3.3.4 注册组件
&emsp;&emsp;`.vitepress/theme/index.ts`注册组件参考3.1节内容)。
### 3.3.5 使用组件
&emsp;&emsp;`docs/index.md`最后添加组件。
::: tip
如果在打包时遇到`Named export 'CalendarHeatmap' not found. The requested module 'vue3-calendar-heatmap' is a CommonJS module, which may not support all module.exports as named exports.`错误提示,说明`vue3-calendar-heatmap``CommonJS`模块,而你在代码中使用了`ES`模块的命名导入方式,需要在`config.mts`中添加以下配置:
```ts
export default defineConfig({
vite: {
ssr: {
noExternal: ['vue3-calendar-heatmap']
}
}
})
```
:::
## 3.4 Mermaid 图表
### 3.4.1 安装
```sh
pnpm i vitepress-plugin-mermaid mermaid -D
```
### 3.4.2 配置
&emsp;&emsp;`.vitepress/config.mts`中配置:
```ts
import { withMermaid } from 'vitepress-plugin-mermaid'
// https://vitepress.dev/reference/site-config
export default withMermaid({
title: "拾光记"
})
```
### 3.4.3 使用
&emsp;&emsp;在`Markdown`中使用:
```md
'''mermaid
graph TD
A[Vue 3] --> B[Compiler]
A --> C[Runtime]
A --> D[Reactivity]
B --> B1[Parser]
B --> B2[Transformer]
B --> B3[Codegen]
C --> C1[Virtual DOM]
C --> C2[Renderer]
C --> C3[Component]
D --> D1[Proxy]
D --> D2[Effect]
D --> D3[Dependency Graph]
B3 -->|生成| C1
C1 -->|Diff/Patch| C2
D3 -->|驱动更新| C1
'''
```
## 3.5 图片预览
### 3.5.1 安装
```sh
pnpm i vitepress-plugin-image-viewer viewerjs
```
### 3.5.2 配置
&emsp;&emsp;`.vitepress/theme/index.ts`中配置:
```ts
import imageViewer from "vitepress-plugin-image-viewer";
import "viewerjs/dist/viewer.min.css";
export default {
extends: DefaultTheme,
setup() {
const route = useRoute();
imageViewer(route);
}
};
```