Skip to content

自定义主题

当默认主题无法满足需求时,可以创建完全自定义的主题。

扩展默认主题

最常见的方式是扩展默认主题并添加自定义功能。

创建主题入口

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 变更
特性最低版本说明
enhanceAppv0.20.0核心钩子,一直支持
setup()v1.0.0新增的组合式 API 钩子
布局插槽v0.22.0部分插槽为后续版本新增
Layout 组件v1.0.0支持异步组件导入
CSS 变量v0.20.0主题样式系统

下一步

学习 CSS 变量覆盖 来精细控制样式。

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献