Skip to content

自动加载导航

介绍

因为比较懒,不想每次整理导航都需要去修改config文件,所以写了一个方法自动读取文件夹,然后自动生成导航。 先放一下我自己的config.ts的配置:

ts
import { defineConfig } from 'vitepress'
import markdownItKatex from 'markdown-it-katex';
import { navConfig } from '../config/nav'
import { sidebarConfig } from '../config/sidebar'
import { customElements } from '../config/katex'
// console.log(process.env.NODE_ENV)

// https://vitepress.dev/reference/site-config
export default defineConfig({
  title: "煊赟知镜",
  description: "\“\”,光明、温暖之意,象征着家庭中充满爱与关怀的氛围,每一个家庭成员在这里都能感受到如阳光般的温暖照耀。“赟”,美好之意,寓意着家庭生活的丰富多彩、美好幸福。",
  base: '/',
  outDir: '../dist/',
  srcDir: 'src',
  vite: {
    server: {
      host: '0.0.0.0',
      port: 5173,
      cors: true
    }
  },
  head: [
    ['meta', { name: 'theme-color', content: '#3eaf7c' }],
    ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
    ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }],
    ['meta', { name: 'msapplication-TileColor', content: '#000000' }],
    ['meta', { name: 'viewport', content: 'width=device-width,initial-scale=1,user-scalable=no' }],
    ['meta', { name: 'msapplication-TileImage', content: '/static/images/favicon.ico' }],
    ['link', { rel: 'apple-touch-icon', href: '/static/images/favicon.ico' }],
    ['link', { rel: 'mask-icon', href: '/static/images/favicon.ico', color: '#3eaf7c' }],
    ['link', { rel: 'icon', href: '/static/images/favicon.ico' }],
  ],
  themeConfig: {
    siteTitle: '煊赟知镜',
    logo: '/static/images/logo.png',

    nav: navConfig,
    sidebar: sidebarConfig,

    search: {
      provider: 'local'
    },

    outline: {
      // level: 'deep',
      level: [2, 3],
      label: '页面导航',
    },

    socialLinks: [
      { icon: 'github', link: 'https://github.com/trexwb' }
    ],

    docFooter: {
      prev: '上一篇',
      next: '下一篇'
    },

    footer: {
      copyright: '以上言论仅代表个人观点'
    }
  },
  sitemap: {
    hostname: 'https://xuanyun.wang/'
  },
  markdown: {
    config: (md) => {
      md.use(markdownItKatex)
    },
    lineNumbers: true,
    math: true,
    // defaultHighlightLang: 'php',
    image: {
      // 默认禁用图片懒加载
      lazyLoading: true
    }
  },
  vue: {
    template: {
      compilerOptions: {
        isCustomElement: (tag) => customElements.includes(tag)
      }
    }
  }
})

重要的代码就是nav: navConfigsidebar: sidebarConfig,关于数学函数katex这个部分我就不做解释了,网上很多。

  • nav.data.ts 遍历需要显示在顶部导航的目录,返回成vitepress需要的格式
ts
import { es } from 'element-plus/es/locales.mjs';
import fs from 'fs';
import path from 'path';

interface FileOrDir {
  text?: string;
  link?: string;
  items?: FileOrDir[];
}

const filterDirs = [
  'demo',
  'public',
  '文档',
  '编程物语',
  '课外知识'
];
const allowedDirs = [
  '笔记',
  '收藏'
]

function buildJsonFromNextLevel(directoryPath: string, depth: number = 0): FileOrDir[] | FileOrDir {
  const absoluteDirectoryPath = path.join(__dirname, directoryPath);
  const subDirectories = fs.readdirSync(absoluteDirectoryPath, { withFileTypes: true })
    .filter(dirent => dirent.isDirectory() && !filterDirs.includes(dirent.name) && allowedDirs.includes(dirent.name))
    .map(dirent => dirent.name);

  // 处理当前目录下的文件
  const filesInCurrentDir = fs.readdirSync(absoluteDirectoryPath)
    .filter(item => !fs.statSync(path.join(absoluteDirectoryPath, item)).isDirectory())
    .map(item => {
      const filePath = path.join(directoryPath, item);
      const fileNameWithoutExtension = path.parse(item).name;
      const textName = fileNameWithoutExtension === 'index' ? path.basename(path.dirname(filePath)) + '-导读' : fileNameWithoutExtension;
      const relativePath = path.relative(process.cwd(), filePath).replace('../src/', '');
      return { text: textName, link: `/${relativePath}` };
    });

  let result: FileOrDir[] = [];
  // 如果当前目录下只有一个文件,且不是根目录,则直接返回该文件
  if (filesInCurrentDir.length === 1 && depth > 0) {
    if (subDirectories.length === 0) {
      // 当前目录下只有一个文件
      result.push({ text: path.basename(directoryPath), link: filesInCurrentDir[0].link });
    } else {
      // 当前目录下有其他目录时
      result.push({
        text: path.basename(directoryPath),
        items: [
          { text: filesInCurrentDir[0].text, link: filesInCurrentDir[0].link }
        ]
      });
    }
  } else if (depth > 0) {
    // 当前目录下有多个文件或没有文件
    const tmpRus: FileOrDir = { text: path.basename(directoryPath), items: [] };
    for (const file of filesInCurrentDir) {
      (tmpRus.items as FileOrDir[]).push(file);
    }
    // 如果 items 数组为空,则移除 items 属性
    if (tmpRus.items && tmpRus.items.length === 0) {
      delete tmpRus.items;
    }
    result.push(tmpRus);
  }
  // 下级目录
  for (const subDirName of subDirectories) {
    const subDirPath = path.join(directoryPath, subDirName);
    const subDirData = buildJsonFromNextLevel(subDirPath, depth + 1);
    const subNameWithoutExtension = path.parse(subDirName).name;
    const items = subDirData as FileOrDir[];
    if(items.length > 0) {
      if(items.length === 1){
        result.push(...items)
      } else {
        result.push({
          text: subNameWithoutExtension,
          items: items
        })
      }
    }
  }
  return result;
}

const directoryPath = '../src/';
// 构建目录树
export const navData = buildJsonFromNextLevel(directoryPath);
// console.log('navData:', JSON.stringify(navData));
// export const navData = [
//   {
//     text: '笔记',
//     items: [
//       { text: '日常', link: '/notes/daily/index' },
//       {
//         text: '踩坑记',
//         items: [
//           { text: 'Debug清单', link: '/notes/debug/index' },
//           { text: '踩坑记录', link: '/notes/logs/index' },
//         ]
//       },
//     ]
//   },
//   {
//     text: '工具',
//     items: [
//       { text: '软件推荐', link: '/tools/share/index' },
//       { text: '在线工具', link: '/tools/online/index' },
//       { text: 'IDE配置', link: '/tools/ide/index' },
//       {
//         text: '密钥破解',
//         items: [
//           { text: '相关密钥', link: '/tools/secret/index' },
//           { text: '破解工具', link: '/tools/decryption/index' },
//         ]
//       },
//       {
//         text: '开源代码',
//         items: [
//           { text: 'Git代码', link: '/tools/resources/index' },
//         ]
//       },
//       {
//         text: 'AI',
//         items: [
//           { text: '豆包', link: 'https://www.doubao.com/'},
//           { text: '通义千问', link: 'https://tongyi.aliyun.com'},
//         ]
//       },
//     ]
//   },
// ]

// export default {
//   navData: navData
// }

export default {
  navData: navData,
  load() {
    return {
      navData: navData,
    }
  }
}
  • sidebar.data.ts 遍历需要根据目录显示在侧边导航的目录,返回成vitepress需要的格式
ts
import fs from 'fs';
import path from 'path';

interface FileOrDir {
  text?: string;
  collapsed?: boolean;
  link?: string;
  items?: FileOrDir[];
}

const filterDirs = [
  'demo',
  'public'
];

function buildJsonFromNextLevel(directoryPath: string, buildMenu: Record<string, FileOrDir[]> = {}, depth: string = '/'): FileOrDir[] | FileOrDir {
  const absoluteDirectoryPath = path.join(__dirname, directoryPath);
  const subDirectories = fs.readdirSync(absoluteDirectoryPath, { withFileTypes: true })
    .filter(dirent => dirent.isDirectory() && !filterDirs.includes(dirent.name))
    .map(dirent => dirent.name);

  // 处理当前目录下的文件
  const filesInCurrentDir = fs.readdirSync(absoluteDirectoryPath)
    .filter(item => !fs.statSync(path.join(absoluteDirectoryPath, item)).isDirectory())
    .map(item => {
      const filePath = path.join(directoryPath, item);
      const fileNameWithoutExtension = path.parse(item).name;
      const textName = fileNameWithoutExtension === 'index' ? path.basename(path.dirname(filePath)) + '-导读' : fileNameWithoutExtension;
      const relativePath = path.relative(process.cwd(), filePath).replace('../src/', '');
      return { text: textName, link: `/${relativePath}` };
    });

  let result: FileOrDir[] = [];
  // 如果当前目录下只有一个文件,且不是根目录,则直接返回该文件
  if (filesInCurrentDir.length === 1) {
    if (subDirectories.length === 0) {
      // 当前目录下只有一个文件
      result.push({ text: path.basename(directoryPath), link: filesInCurrentDir[0].link });
    } else {
      // 当前目录下有其他目录时
      result.push({
        text: path.basename(directoryPath),
        collapsed: false,
        items: [
          { text: filesInCurrentDir[0].text, link: filesInCurrentDir[0].link }
        ]
      });
    }
  } else {
    // 当前目录下有多个文件或没有文件
    const tmpRus: FileOrDir = { text: path.basename(directoryPath), collapsed: false, items: [] };
    for (const file of filesInCurrentDir) {
      (tmpRus.items as FileOrDir[]).push(file);
    }
    // 如果 items 数组为空,则移除 items 属性
    if (tmpRus.items && tmpRus.items.length === 0) {
      delete tmpRus.items;
    }
    result.push(tmpRus);
  }
  if(!buildMenu[depth]){
    buildMenu[depth] = result;
  } else {
    buildMenu[depth].push(...result);
  }

  for (const subDirName of subDirectories) {
    const subDirPath = path.join(directoryPath, subDirName);
    const subDirData = buildJsonFromNextLevel(subDirPath, buildMenu, `${depth}${subDirName}/`);
    const items = subDirData as FileOrDir[];
    if(items.length > 0) {
      if(!buildMenu[`${depth}${subDirName}/`]){
        buildMenu[`${depth}${subDirName}/`] = items;
      } else {
        buildMenu[`${depth}${subDirName}/`].push(...items);
      }
    }
  }
  return buildMenu;
}

const directoryPath = '../src/';
// 构建目录树
export const sidebarData = buildJsonFromNextLevel(directoryPath);
// console.log('sidebarData:', JSON.stringify(sidebarData));
// export const sidebarData = {
//   '/notes/daily/': [
//     {
//       text: '随笔',
//       collapsed: false,
//       items: [
//         { text: '随笔(待整理)', link: '/notes/daily/index' },
//         { text: 'demo', link: '/notes/daily/demo' },
//       ]
//     }, {
//       text: '2024',
//       collapsed: false,
//       items: [
//         { text: 'x x x x', link: '/notes/daily/2024/xxxx' },
//       ]
//     },
//   ],
//   '/tools/share/': [
//     {
//       text: '网络工具',
//       collapsed: false,
//       items: [
//         { text: 'PDF双页一键切割单页', link: '/tools/share/pdf2page' },
//       ]
//     }, {
//       text: '系统相关',
//       collapsed: false,
//       items: [
//         { text: 'x x x x', link: '/tools/share/xxxx' },
//       ]
//     }, {
//       text: '设计相关',
//       collapsed: false,
//       items: [
//         { text: 'x x x x', link: '/tools/share/xxxx' },
//       ]
//     }, {
//       text: '文字处理',
//       collapsed: false,
//       items: [
//         { text: 'x x x x', link: '/tools/share/xxxx' },
//       ]
//     }, {
//       text: '媒体处理',
//       collapsed: false,
//       items: [
//         { text: 'x x x x', link: '/tools/share/xxxx' },
//       ]
//     },
//   ],
// }

export default {
  sidebarData: sidebarData,
  load() {
    return {
      sidebarData: sidebarData,
    }
  }
}

以上言论仅代表个人观点