557 lines
14 KiB
Markdown
557 lines
14 KiB
Markdown
---
|
||
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>
|
||
```
|
||
|
||
  这里需要使用`withBase`拼接路由,防止存在设置了`base`根路径的情况。通过`isNested`来控制是否存在二级菜单。
|
||
::: warning
|
||
这里只考虑了最多存在二级菜单的情况。
|
||
:::
|
||
|
||
  在`.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);
|
||
},
|
||
};
|
||
```
|
||
|
||
  在`.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,
|
||
})
|
||
```
|
||
|
||
  在文档所在目录的`index.md`中配置:
|
||
```md
|
||
---
|
||
layout: doc
|
||
title: Web前端
|
||
description: Web前端开发技术文档
|
||
---
|
||
|
||
<script setup>
|
||
import { routers } from '../.vitepress/theme/router'
|
||
</script>
|
||
|
||
<MenuList :routers=routers[0] :isNested=true />
|
||
```
|
||
|
||
  这样后续只需要维护路由配置清单文件即可自动生成路由菜单。
|
||
|
||
## 3.2 博客元数据
|
||
  可以通过在每个博客中添加`frontmatter`元数据,在最顶部添加博客属性,例如创建日期、字数、时长和是否精品等信息。
|
||
### 3.2.1 定义元数据
|
||
  在每个博客最上面添加元数据:
|
||
```md
|
||
---
|
||
title: Docker简介和安装
|
||
date: 2025-12-15
|
||
isGreat: true
|
||
---
|
||
```
|
||
|
||
  这里可以自定义添加任何`key: value`形式的字段信息。
|
||
::: tip
|
||
建议手动加上每个博客的创建日期
|
||
:::
|
||
|
||
### 3.2.2 博客信息组件
|
||
  在`.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>
|
||
```
|
||
|
||
  这里统计了正文部分的文字和图片数量,并通过相应规则计算阅读时间。
|
||
  通过`page.frontmatter`获取自定义的元数据信息并在博客最上面显示。
|
||
|
||
### 3.2.3 注册组件
|
||
  在`.vitepress/theme/index.ts`注册组件(参考3.1节内容)。
|
||
|
||
### 3.2.4 自定义布局组件
|
||
  需要将博客信息组件放在自定义布局组件中。
|
||
  在`.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>
|
||
```
|
||
|
||
  更多插槽可以参考[Github源码](https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/Layout.vue)。
|
||
  同样需要注册组件。
|
||
|
||
### 3.2.5 配置布局组件
|
||
  在`.vitepress/config.mts`中配置布局:
|
||
```ts
|
||
import DefaultTheme from 'vitepress/theme'
|
||
import MyLayout from './components/MyLayout.vue';
|
||
|
||
export default {
|
||
extends: DefaultTheme,
|
||
Layout: MyLayout,
|
||
};
|
||
```
|
||
|
||
## 3.3 热力图
|
||
  使用`vue3-calendar-heatmap`组件在首页绘制博客热力图。
|
||
### 3.3.1 安装
|
||
```sh
|
||
pnpm install vue3-calendar-heatmap
|
||
```
|
||
|
||
### 3.3.2 获取数据
|
||
  使用`VitePress`提供的[createContentLoader](https://vitepress.dev/zh/guide/data-loading)来获取所有的`md`博客数据。
|
||
  在`.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
|
||
}
|
||
})
|
||
}
|
||
})
|
||
```
|
||
|
||
  通过判断路径是否包含`html`来确认是博客文档,并且筛选出元数据中包含日期的博客,然后返回相应内容。
|
||
|
||
### 3.3.3 博客统计组件
|
||
  在`.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>
|
||
```
|
||
|
||
  根据`posts.data.js`提供的博客数据进行日期分组,并统计已坚持天数和已创作博客数。
|
||
|
||
### 3.3.4 注册组件
|
||
  在`.vitepress/theme/index.ts`注册组件(参考3.1节内容)。
|
||
|
||
### 3.3.5 使用组件
|
||
  在`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 配置
|
||
  在`.vitepress/config.mts`中配置:
|
||
```ts
|
||
import { withMermaid } from 'vitepress-plugin-mermaid'
|
||
|
||
// https://vitepress.dev/reference/site-config
|
||
export default withMermaid({
|
||
title: "拾光记"
|
||
})
|
||
```
|
||
|
||
### 3.4.3 使用
|
||
  在`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 配置
|
||
  在`.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);
|
||
}
|
||
};
|
||
```
|