创建自定义布局
创建自定义布局实现独特的页面展示效果。
布局基础
使用 frontmatter 切换布局
yaml
---
layout: page
---VitePress 内置三种布局:
doc- 文档布局(带侧边栏)page- 简单页面(无侧边栏)home- 首页布局
创建自定义布局
1. 创建布局组件
创建 .vitepress/theme/layouts/CustomLayout.vue:
vue
<script setup lang="ts">
import { useData } from 'vitepress'
const { frontmatter } = useData()
</script>
<template>
<div class="custom-layout">
<header class="header">
<h1>{{ frontmatter.title }}</h1>
<p v-if="frontmatter.subtitle">{{ frontmatter.subtitle }}</p>
</header>
<main class="content">
<Content />
</main>
<footer class="footer">
<p>© 2026 我的站点</p>
</footer>
</div>
</template>
<style scoped>
.custom-layout {
max-width: 900px;
margin: 0 auto;
padding: 2rem;
}
.header {
text-align: center;
margin-bottom: 3rem;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
.content {
min-height: 60vh;
}
.footer {
margin-top: 4rem;
padding-top: 2rem;
border-top: 1px solid var(--vp-c-divider);
text-align: center;
color: var(--vp-c-text-2);
}
</style>2. 注册布局
ts
// .vitepress/theme/index.ts
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import CustomLayout from './layouts/CustomLayout.vue'
export default {
extends: DefaultTheme,
Layout: () => {
// 根据条件返回不同布局
return h(DefaultTheme.Layout)
}
}
// 通过 frontmatter 使用
// 在页面中使用:
// ---
// layout: page
// ---扩展默认布局
使用插槽
vue
<!-- .vitepress/theme/index.ts -->
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import MyFooter from './components/MyFooter.vue'
import AdBanner from './components/AdBanner.vue'
export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
// 文档内容前
'doc-before': () => h(AdBanner),
// 文档内容后
'doc-after': () => h(MyFooter),
// 侧边栏前
'sidebar-nav-before': () => h('div', { class: 'sidebar-tip' }, '提示内容'),
// 导航栏后
'nav-bar-content-after': () => h('div', { class: 'nav-extra' }, '额外内容')
})
}
}可用插槽
| 插槽 | 说明 |
|---|---|
layout-top | 布局顶部 |
layout-bottom | 布局底部 |
nav-bar-title-before | 标题前 |
nav-bar-title-after | 标题后 |
nav-bar-content-before | 导航内容前 |
nav-bar-content-after | 导航内容后 |
sidebar-nav-before | 侧边栏前 |
sidebar-nav-after | 侧边栏后 |
doc-before | 文档前 |
doc-after | 文档后 |
aside-top | 大纲顶部 |
aside-bottom | 大纲底部 |
博客列表布局
vue
<!-- .vitepress/theme/layouts/BlogLayout.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useData } from 'vitepress'
interface Post {
title: string
path: string
date: string
excerpt: string
tags: string[]
}
const { frontmatter } = useData()
const posts = ref<Post[]>(frontmatter.value.posts || [])
const selectedTag = ref<string | null>(null)
const filteredPosts = computed(() => {
if (!selectedTag.value) return posts.value
return posts.value.filter(p => p.tags.includes(selectedTag.value!))
})
const allTags = computed(() => {
const tags = new Set<string>()
posts.value.forEach(p => p.tags.forEach(t => tags.add(t)))
return Array.from(tags)
})
</script>
<template>
<div class="blog-layout">
<header class="blog-header">
<h1>博客</h1>
<div class="tags">
<button
:class="{ active: !selectedTag }"
@click="selectedTag = null"
>
全部
</button>
<button
v-for="tag in allTags"
:key="tag"
:class="{ active: selectedTag === tag }"
@click="selectedTag = tag"
>
{{ tag }}
</button>
</div>
</header>
<div class="posts">
<article
v-for="post in filteredPosts"
:key="post.path"
class="post-card"
>
<time class="date">{{ post.date }}</time>
<h2 class="title">
<a :href="post.path">{{ post.title }}</a>
</h2>
<p class="excerpt">{{ post.excerpt }}</p>
<div class="tags">
<span v-for="tag in post.tags" :key="tag" class="tag">
{{ tag }}
</span>
</div>
</article>
</div>
</div>
</template>
<style scoped>
.blog-layout {
max-width: 800px;
margin: 0 auto;
padding: 2rem 1.5rem;
}
.blog-header {
margin-bottom: 2rem;
}
.blog-header h1 {
font-size: 2rem;
margin-bottom: 1rem;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.tags button {
padding: 0.25rem 0.75rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 16px;
cursor: pointer;
transition: all 0.2s;
}
.tags button.active,
.tags button:hover {
background: var(--vp-c-brand-1);
color: white;
border-color: var(--vp-c-brand-1);
}
.post-card {
padding: 1.5rem 0;
border-bottom: 1px solid var(--vp-c-divider);
}
.date {
font-size: 0.85rem;
color: var(--vp-c-text-3);
}
.title {
margin: 0.5rem 0;
}
.title a {
color: var(--vp-c-text-1);
text-decoration: none;
}
.title a:hover {
color: var(--vp-c-brand-1);
}
.excerpt {
color: var(--vp-c-text-2);
margin-bottom: 0.75rem;
}
.tag {
font-size: 0.75rem;
padding: 0.125rem 0.5rem;
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
border-radius: 4px;
}
</style>使用自定义布局
在 frontmatter 中指定
yaml
---
layout: ../../.vitepress/theme/layouts/BlogLayout.vue
posts:
- title: 文章标题
path: /blog/post-1
date: 2026-04-01
excerpt: 文章摘要
tags: [Vue, VitePress]
---在 Markdown 中使用组件
markdown
---
layout: page
---
<script setup>
import BlogLayout from '../.vitepress/theme/layouts/BlogLayout.vue'
</script>
<BlogLayout />学习检查清单
- [ ] 理解了布局系统
- [ ] 创建了自定义布局组件
- [ ] 使用了布局插槽
- [ ] 实现了博客列表布局
- [ ] 在页面中应用了自定义布局
下一步
继续学习 开发自定义主题,创建完整的自定义主题。