Skip to content
文章目录

vitepress使用

vitepress 官网

vitepress 官网 (请优先看英文官网)

vitepress 中文翻译网站 (中文翻译网站内容是残缺的)

vitepress 版本

1.0.0-alpha.35

首页示例

md
---
layout: home

hero:
  name: 管理系统布局组件
  text: 适用于上下或左右分栏风格的布局
  tagline: 基于vue3, 仅支持现代浏览器,简洁、易扩展、好用
  image:
    src: /logo.png
    alt: 后台系统布局组件
  actions:
    - theme: brand
      text: 开始
      link: /articles/02-用法详解/
    - theme: alt
      text: 获取源代码
      link: https://gitee.com/free_pan/backend-layout-wk

features:
  - icon: ⚡️
    title: 简洁而纯粹,只为布局
  - icon: 📦
    title: 各布局组件之间逻辑隔离,使扩展变得简便
  - icon: 🛠️
    title: 除了vue3,再无任何其他依赖
---

右侧文章目录显示多级

PROJECT_ROOT/docs/.vitepress/config.ts

ts
module.exports = {
  themeConfig: {
    // 配置顶部的文字(不配置则是英文)
    outlineTitle: '文章目录',
    // 表示显示h2-h6的标题
    outline: 'deep',
  },
}

顶部导航

数据结构

ts
interface NavItem {
  /**
   * 文本
   */
  text: string
  /**
   * 跳转链接
   * 注意事项:如果要配置动态sidebar,则这个link必须以'/'结尾
   */
  link?: string
  /**
   * 高亮匹配路由
   */
  activeMatch?: string
  /**
   * 子导航
   */
  children?: NavItem[]
}

手动配置

PROJECT_ROOT/docs/.vitepress/config.ts

ts
import { UserConfig } from 'vitepress'

module.exports = {
  themeConfig: {
    nav: [
      { text: '作品集', link: '/articles/作品集/', activeMatch: '/作品集/' },
      {
        text: '作品集',
        link: '/articles/作品集/',
        activeMatch: '/作品集/',
        children: [
          // 省略...
        ],
      },
    ],
  },
} as UserConfig

动态生成

ts
import path from 'path'
import { readdirSync, statSync } from 'fs'

/**
 * 判断是否为markdown文件
 *
 * @param   {string}  fileName  文件名
 *
 * @return  {[boolean]}         有返回值则表示是markdown文件,否则不是
 */
function isMarkdownFile(fileName: string) {
  return fileName.match(/.+\.md$/)
}

interface NavGenerateConfig {
  /**
   * 是否启用路由匹配显示激活状态. 默认:false
   */
  enableDirActiveMatch: boolean
  /**
   * 需要遍历的目录. 默认:articles
   */
  dirName?: string
  /**
   * 最大遍历层级. 默认:1
   */
  maxLevel?: number
}

export function getNavData(navGenerateConfig: NavGenerateConfig) {
  const { enableDirActiveMatch, dirName = 'articles', maxLevel = 1 } = navGenerateConfig
  const dirFullPath = path.resolve(__dirname, `../${dirName}`)
  const result = getNavDataArr(dirFullPath, 1, maxLevel, enableDirActiveMatch)
  // console.log('navData')
  // console.log(result)
  return result
}
interface NavItem {
  text: string
  link?: string
  activeMatch?: string
  children?: NavItem[]
}
/**
 * 获取顶部导航数据
 *
 * @param   {string}     dirFullPath  当前需要遍历的目录绝对路径
 * @param   {number}     level        当前层级
 * @param   {number[]}   maxLevel     允许遍历的最大层级
 * @param   {boolean}    enableActiveMatch 是否启用路由匹配显示激活状态
 *
 * @return  {NavItem[]}               导航数据数组
 */
function getNavDataArr(dirFullPath: string, level: number, maxLevel: number, enableActiveMatch: boolean): NavItem[] {
  // 获取所有文件名和目录名
  const allDirAndFileNameArr = readdirSync(dirFullPath)
  const result: NavItem[] = []
  allDirAndFileNameArr.map((fileOrDirName: string, idx: number) => {
    const fileOrDirFullPath = path.join(dirFullPath, fileOrDirName)
    const stats = statSync(fileOrDirFullPath)
    const link = fileOrDirFullPath.split('docs')[1].replace('.md', '').replace(/\\/g, '/')
    const text = fileOrDirName.match(/^[0-9]{2}-.+/) ? fileOrDirName.substring(3) : fileOrDirName
    if (stats.isDirectory()) {
      // 当前为文件夹
      const dirData: NavItem = {
        text,
        link: `${link}/`,
      }
      if (level !== maxLevel) {
        dirData.children = getNavDataArr(fileOrDirFullPath, level + 1, maxLevel, enableActiveMatch)
      }
      if (enableActiveMatch) {
        dirData.activeMatch = link + '/'
      }
      result.push(dirData)
    } else if (isMarkdownFile(fileOrDirName)) {
      // 当前为文件
      const fileData: NavItem = {
        text,
        link: link,
      }
      if (enableActiveMatch) {
        fileData.activeMatch = link + '/'
      }
      result.push(fileData)
    }
  })
  return result
}

侧边导航

数据结构

ts
interface SideBarItem {
  /**
   *名称
   */
  text: string
  /**
   * 是否显示展开/收缩按钮
   */
  collapsible?: boolean
  /**
   * 默认是否收缩
   */
  collapsed?: boolean
  /**
   * 文章列表
   */
  items?: SideBarItem[]
  /**
   * 跳转链接
   */
  link?: string
}

手动配置

PROJECT_ROOT/docs/.vitepress/config.ts

ts
import { UserConfig } from 'vitepress'

module.exports = {
  themeConfig: {
    /**
     * 整体的sidebar是一个对象,这个对象的属性是与顶部导航的link值对应
     * 当点击的顶部导航的link与sidebar的某个属性完全匹配时,那么侧边就会显示对应属性的哪个sidebar
     */
    sidebar: {
      '/articles/01-作品集/': [
        {
          text: '工具类',
          items: [{ text: 'xxx', link: 'xxx' }],
        },
        {
          text: 'css/scss/less',
          items: [],
        },
        {
          text: '移动端',
          items: [],
        },
        {
          text: '算法',
          items: [],
        },
        {
          text: 'node',
          items: [],
        },
      ],
    },
  },
} as UserConfig

动态生成

ts
import path from 'path'
import { readdirSync, statSync } from 'fs'

/**
 * 判断是否为markdown文件
 *
 * @param   {string}  fileName  文件名
 *
 * @return  {[boolean]}         有返回值则表示是markdown文件,否则不是
 */
function isMarkdownFile(fileName: string) {
  return fileName.match(/.+\.md$/)
}

interface SidebarGenerateConfig {
  /**
   * 需要遍历的目录. 默认:articles
   */
  dirName?: string
  /**
   * 忽略的文件名. 默认: index.md
   */
  ignoreFileName?: string
  /**
   * 忽略的文件夹名称. 默认: ['demo','asserts']
   */
  ignoreDirNames?: string[]
}
export function getSidebarData(sidebarGenerateConfig: SidebarGenerateConfig = {}) {
  const {
    dirName = 'articles',
    ignoreFileName = 'index.md',
    ignoreDirNames = ['demo', 'asserts'],
  } = sidebarGenerateConfig
  // 获取目录的绝对路径
  const dirFullPath = path.resolve(__dirname, `../${dirName}`)
  const allDirAndFileNameArr = readdirSync(dirFullPath)
  const obj = {}
  allDirAndFileNameArr.map(dirName => {
    let subDirFullName = path.join(dirFullPath, dirName)
    const property = subDirFullName.split('docs')[1].replace(/\\/g, '/') + '/'
    const arr = getSideBarItemTreeData(subDirFullName, 1, 2, ignoreFileName, ignoreDirNames)
    obj[property] = arr
  })
  // console.log('sidebarData')
  // console.log(obj)
  return obj
}

interface SideBarItem {
  text: string
  collapsible?: boolean
  collapsed?: boolean
  items?: SideBarItem[]
  link?: string
}
function getSideBarItemTreeData(
  dirFullPath: string,
  level: number,
  maxLevel: number,
  ignoreFileName: string,
  ignoreDirNames: string[]
): SideBarItem[] {
  // 获取所有文件名和目录名
  const allDirAndFileNameArr = readdirSync(dirFullPath)
  const result: SideBarItem[] = []
  allDirAndFileNameArr.map((fileOrDirName: string, idx: number) => {
    const fileOrDirFullPath = path.join(dirFullPath, fileOrDirName)
    const stats = statSync(fileOrDirFullPath)
    if (stats.isDirectory()) {
      if (!ignoreDirNames.includes(fileOrDirName)) {
        // 当前为文件夹
        const dirData: SideBarItem = {
          text: fileOrDirName,
          collapsed: false,
        }
        if (level !== maxLevel) {
          dirData.items = getSideBarItemTreeData(fileOrDirFullPath, level + 1, maxLevel, ignoreFileName, ignoreDirNames)
        }
        if (dirData.items) {
          dirData.collapsible = true
        }
        result.push(dirData)
      }
    } else if (isMarkdownFile(fileOrDirName) && ignoreFileName !== fileOrDirName) {
      // 当前为文件
      const matchResult = fileOrDirName.match(/(.+)\.md/)
      const text = matchResult ? matchResult[1] : fileOrDirName
      const fileData: SideBarItem = {
        text,
        link: fileOrDirFullPath.split('docs')[1].replace('.md', '').replace(/\\/g, '/'),
      }
      result.push(fileData)
    }
  })
  return result
}

动态生成需要有对应的目录与文件结构

  • 所有的文章都必须在PROJECT_ROOT/docs/articles目录,并放入对应的子目录
  • PROJECT_ROOT/docs/articles的子目录是一级目录(如: 01-作品集),这一级目录会被用来生成顶部导航
  • PROJECT_ROOT/docs/articles/一级目录/index.md, 这是一级目录的首页, 该文件必须存在
  • PROJECT_ROOT/docs/articles/一级目录/二级目录,二级目录用于生成左侧导航
  • PROJECT_ROOT/docs/articles/一级目录/二级目录/xxx.md, xxx.md表示你的具体文章,文章必须放在二级目录下面
  • 无论是自动生成头部导航还是侧边导航,默认都会自动忽略index.md以及demo目录asserts目录

调用动态导航生成代码生成导航数据

ts
import { UserConfig } from 'vitepress'
import { getSidebarData, getNavData } from './navSidebarUtil'
module.exports = {
  themeConfig: {
    // 扫描目录自动生成顶部导航
    nav: getNavData({ enableDirActiveMatch: true }),
    // 扫描目录自动生成侧边导航数据
    sidebar: getSidebarData(),
  },
} as UserConfig

配置代码 demo 插件

用于显示代码运行效果以及源代码

pnpm add @pzy915/vite-plugin-vitepress-demo

PROJECT_ROOT/docs/vite.config.ts

ts
import VitePluginVitepressDemo from '@pzy915/vite-plugin-vitepress-demo'
import { defineConfig } from 'vite'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  // glob: './**/demo/**/*.{vue,jsx,tsx,js,ts}' 这里表示扫描所有demo目录下的vue,jsx,tsx,js,ts作为demo示例代码
  plugins: [vueJsx(), VitePluginVitepressDemo({ glob: './**/demo/**/*.{vue,jsx,tsx,js,ts}' })],
  server: {
    host: '0.0.0.0',
    // open: true,
  },
})

PROJECT_ROOT/docs/.vitepress/theme/index.ts

ts
import { Component } from 'vue'
import { type EnhanceAppContext, type Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import { AntdTheme } from '@pzy915/vite-plugin-vitepress-demo/theme'

export default {
  ...DefaultTheme,
  enhanceApp({ app, router, siteData }) {
    app.component('Demo', AntdTheme)
  },
} as Theme

在 md 中使用 demo 插件

会运行代码

md
<demo src="./demo/menu/TestMenu03.vue" title="" desc=""></demo>

如下这种配置方式,在展之后除了显示TestMenu03.vue的源代码外,还会显示Util.js的源代码,如果还有更多需要显示的源代码,则继续往otherSrcArr中添加即可

md
<demo src="./demo/menu/TestMenu03.vue" :otherSrcArr="['./demo/menu/Util.js']" title="" desc=""></demo>

如果只想展示源代码

md
<demo src="./demo/menu/TestMenu03.vue" raw title="" desc=""></demo>

加入第三方依赖

以加入Element-Plus为例

PROJECT_ROOT/docs/.vitepress/theme/index.ts

ts
import { Component } from 'vue'
import { type EnhanceAppContext, type Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

export default {
  ...DefaultTheme,
  enhanceApp({ app, router, siteData }) {
    app.use(ElementPlus)
  },
} as Theme

增加离线搜索功能

vitepress 离线搜索插件

参考资料

vitepress(三):自动生成目录

VitePress 侧边栏搭建 | VitePress 站点配置模板 repo