自定义主题
当默认主题无法满足需求时,可以创建完全自定义的主题。
扩展默认主题
最常见的方式是扩展默认主题并添加自定义功能。
创建主题入口
在 docs/.vitepress/theme/index.ts 中:
ts
import DefaultTheme from 'vitepress/theme'
import './style.css'
import MyLayout from './MyLayout.vue'
export default {
extends: DefaultTheme,
// 覆盖默认布局
Layout: MyLayout,
// 增强应用
enhanceApp({ app, router, siteData }) {
// 注册全局组件
app.component('MyComponent', MyComponent)
// 注册插件
// app.use(myPlugin)
}
}添加自定义样式
在 docs/.vitepress/theme/style.css 中:
css
/* 覆盖 CSS 变量 */
:root {
--vp-c-brand-1: #646cff;
--vp-c-brand-2: #535bf2;
--vp-c-brand-3: #3b3bdb;
}
/* 添加自定义样式 */
.custom-block {
border-radius: 8px;
}使用布局插槽
默认主题提供了多个插槽用于扩展布局:
vue
<!-- docs/.vitepress/theme/MyLayout.vue -->
<script setup>
import DefaultTheme from 'vitepress/theme'
const { Layout } = DefaultTheme
</script>
<template>
<Layout>
<!-- 导航栏前 -->
<template #nav-bar-title-before>
<span>🌟</span>
</template>
<!-- 导航栏后 -->
<template #nav-bar-title-after>
<span>📚</span>
</template>
<!-- 导航栏内容前 -->
<template #nav-bar-content-before>
<div class="custom-nav-item">自定义导航</div>
</template>
<!-- 侧边栏前 -->
<template #sidebar-nav-before>
<div class="custom-sidebar-top">侧边栏顶部</div>
</template>
<!-- 侧边栏后 -->
<template #sidebar-nav-after>
<div class="custom-sidebar-bottom">侧边栏底部</div>
</template>
<!-- 文档页脚后 -->
<template #doc-footer-before>
<div class="custom-doc-footer">自定义文档页脚</div>
</template>
<!-- 内容前 -->
<template #doc-before>
<div class="custom-doc-header">文档顶部</div>
</template>
<!-- 内容后 -->
<template #doc-after>
<div class="custom-doc-footer">文档底部</div>
</template>
<!-- 页面底部 -->
<template #layout-bottom>
<div class="custom-footer">自定义页脚</div>
</template>
</Layout>
</template>
<style>
.custom-nav-item {
padding: 0 12px;
}
</style>可用插槽列表
版本说明
本文档基于 VitePress v1.0.0+ 编写。部分插槽在早期版本中可能不可用。
| 插槽 | 位置 | 版本要求 |
|---|---|---|
layout-top | 页面顶部 | v0.22.0+ |
layout-bottom | 页面底部 | v0.22.0+ |
nav-bar-title-before | 导航栏标题前 | v0.22.0+ |
nav-bar-title-after | 导航栏标题后 | v0.22.0+ |
nav-bar-content-before | 导航栏内容前 | v0.22.0+ |
nav-bar-content-after | 导航栏内容后 | v0.22.0+ |
nav-screen-content-before | 移动端导航内容前 | v0.22.0+ |
nav-screen-content-after | 移动端导航内容后 | v0.22.0+ |
sidebar-nav-before | 侧边栏导航前 | v0.22.0+ |
sidebar-nav-after | 侧边栏导航后 | v0.22.0+ |
doc-before | 文档内容前 | v0.22.0+ |
doc-after | 文档内容后 | v0.22.0+ |
doc-footer-before | 文档页脚前 | v0.22.0+ |
aside-top | 右侧栏顶部 | v0.22.0+ |
aside-bottom | 右侧栏底部 | v0.22.0+ |
aside-outline-before | 大纲前 | v0.22.0+ |
aside-outline-after | 大纲后 | v0.22.0+ |
完全自定义主题
如需完全自定义主题,不继承默认主题:
ts
// docs/.vitepress/theme/index.ts
import Layout from './Layout.vue'
export default {
Layout,
enhanceApp({ app }) {
// 注册组件和插件
}
}vue
<!-- docs/.vitepress/theme/Layout.vue -->
<script setup>
import { useData } from 'vitepress'
const { page, frontmatter } = useData()
</script>
<template>
<div class="theme-container">
<header class="header">
<!-- 自定义头部 -->
</header>
<main class="main">
<Content />
</main>
<footer class="footer">
<!-- 自定义页脚 -->
</footer>
</div>
</template>注册全局组件
注册单个组件
ts
// docs/.vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import MyComponent from './components/MyComponent.vue'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('MyComponent', MyComponent)
}
}批量注册组件
ts
// docs/.vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import { loadGlobalComponents } from './components'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
loadGlobalComponents(app)
}
}ts
// docs/.vitepress/theme/components/index.ts
import type { App } from 'vue'
import MyComponent from './MyComponent.vue'
import AnotherComponent from './AnotherComponent.vue'
export function loadGlobalComponents(app: App) {
app.component('MyComponent', MyComponent)
app.component('AnotherComponent', AnotherComponent)
}在 Markdown 中使用
注册后,组件可在 Markdown 中直接使用:
markdown
# 我的页面
<MyComponent prop="value" />
<AnotherComponent>
插槽内容
</AnotherComponent>生命周期钩子
VitePress 主题支持生命周期钩子,可在特定时机执行代码:
ts
import DefaultTheme from 'vitepress/theme'
export default {
extends: DefaultTheme,
// 应用初始化时调用
enhanceApp({ app, router, siteData }) {
// 注册全局组件
// app.component('MyComponent', MyComponent)
// 注册全局属性
// app.provide('myData', { foo: 'bar' })
},
// 客户端应用挂载前调用(v1.0.0+)
setup() {
// 可以使用 Vue 组合式 API
// onMounted(() => { ... })
},
// 布局组件(v1.0.0+)
Layout: () => import('./Layout.vue'),
// 自定义 404 页面
NotFound: () => import('./NotFound.vue')
}enhanceApp 钩子详解
enhanceApp 是主题开发中最核心的钩子:
ts
export default {
extends: DefaultTheme,
enhanceApp({ app, router, siteData }) {
// app: Vue 应用实例
// router: VitePress 路由实例
// siteData: 站点配置数据
// 1. 注册全局组件
app.component('GlobalButton', GlobalButton)
// 2. 注册全局指令
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 3. 提供全局数据
app.provide('themeConfig', siteData.themeConfig)
// 4. 注册 Pinia 状态管理(v1.0.0+)
// import { createPinia } from 'pinia'
// app.use(createPinia())
// 5. 路由守卫
router.onBeforeRouteChange = (to) => {
// 页面切换前
console.log('Navigating to:', to)
}
router.onAfterRouteChanged = (to) => {
// 页面切换后
console.log('Navigated to:', to)
}
}
}主题开发进阶
数据获取与缓存
使用 useData 组合式函数获取站点数据:
vue
<script setup>
import { useData, useRoute } from 'vitepress'
// 页面数据
const { page, frontmatter, title, description, lang } = useData()
// 路由信息
const route = useRoute()
// 主题配置
const { theme } = useData()
</script>条件渲染组件
根据页面路径或 frontmatter 条件渲染:
vue
<!-- docs/.vitepress/theme/Layout.vue -->
<script setup>
import DefaultTheme from 'vitepress/theme'
import { useData } from 'vitepress'
import HomeFeatures from './components/HomeFeatures.vue'
import TableOfContents from './components/TableOfContents.vue'
const { Layout } = DefaultTheme
const { frontmatter, page } = useData()
</script>
<template>
<Layout>
<!-- 仅在首页显示 -->
<template #home-hero-after v-if="frontmatter.layout === 'home'">
<HomeFeatures />
</template>
<!-- 仅在文档页显示 -->
<template #aside-outline-after v-if="frontmatter.toc !== false">
<TableOfContents :depth="frontmatter.tocDepth || 2" />
</template>
<!-- 根据路径判断 -->
<template #doc-after v-if="page.filePath.startsWith('blog/')">
<BlogNavigation />
</template>
</Layout>
</template>混合默认主题样式
保留默认主题样式,同时添加自定义样式:
css
/* docs/.vitepress/theme/style.css */
/* 导入默认主题样式(v1.0.0+ 自动继承,无需显式导入) */
/* 1. 覆盖 CSS 变量 */
:root {
--vp-c-brand-1: #646cff;
--vp-c-brand-2: #535bf2;
--vp-c-brand-3: #3b3bdb;
}
/* 2. 深色模式适配 */
.dark {
--vp-c-brand-1: #818bff;
--vp-c-brand-2: #7273f5;
}
/* 3. 添加自定义样式 */
.custom-block {
border-radius: 8px;
}
/* 4. 响应式调整 */
@media (max-width: 768px) {
.custom-block {
padding: 12px;
}
}完整主题项目结构
docs/.vitepress/theme/
├── index.ts # 主题入口
├── Layout.vue # 布局组件
├── NotFound.vue # 404 页面
├── style.css # 全局样式
├── components/ # 组件目录
│ ├── index.ts # 组件导出
│ ├── HomeFeatures.vue # 首页特性
│ ├── BlogList.vue # 博客列表
│ └── TableOfContents.vue
├── composables/ # 组合式函数
│ ├── useBlog.ts # 博客数据
│ └── useSearch.ts # 搜索功能
├── styles/ # 样式文件
│ ├── variables.css # CSS 变量
│ ├── typography.css # 排版样式
│ └── dark.css # 深色模式
└── utils/ # 工具函数
├── formatDate.ts
└── readingTime.ts版本兼容性说明
版本要求
- 本文档基于 VitePress v1.0.0+ 编写
setup()钩子需要 v1.0.0 及以上版本- 部分插槽在不同版本可能有所不同
- 查看官方文档获取最新 API 变更
| 特性 | 最低版本 | 说明 |
|---|---|---|
enhanceApp | v0.20.0 | 核心钩子,一直支持 |
setup() | v1.0.0 | 新增的组合式 API 钩子 |
| 布局插槽 | v0.22.0 | 部分插槽为后续版本新增 |
Layout 组件 | v1.0.0 | 支持异步组件导入 |
| CSS 变量 | v0.20.0 | 主题样式系统 |
下一步
学习 CSS 变量覆盖 来精细控制样式。