vitepress動態導航

丁少华發表於2024-12-09

前言

我需要根據動態設定導航。
比如根據不同的路由設定不同的頂部導航。
但是vitepress並沒有相關配置,但是我們可以透過攔截主題修改全域性狀態來完成這個功能!

核心知識

建立檔案,xxx/docs/.vitepress/theme/index.js
注意檔名和路徑不能寫錯,意思是覆蓋預設主題配置!

import DefaultTheme from "vitepress/theme";
import { watch } from 'vue'

export default {
    extends: DefaultTheme,
    enhanceApp({ app, router, siteData }) {
        watch(
            () => router.route.path,
            (val) => {
                // 動態修改站點資料(畢竟使用useData不生效),比如這裡動態設定navbar
                siteData.value = {
                    ...siteData.value,
                    themeConfig: {
                        ...siteData.value.themeConfig,
                        nav: [
                            { text: "Html", link: "/vue/intro" },
                            { text: "Css", link: "/vuex/intro" },
                        ]
                    }
                }
            }
        );
    },
};

完整試例

建立導航配置

nav-group.ts

import holyTrinityImg from "./assets/img/docss/holy-trinity.webp";
import feFwLibImg from "./assets/img/docss/fe-fw-lib.png";
import feErgImg from "./assets/img/docss/fe-erg.png";

const navGroup= {
  holyTrinity: {
    title: "前端三劍客",
    content: "html、css、js的愛恨情仇",
    icon: holyTrinityImg,
    nav: [
      { text: "Html", link: "/html/intro" },
      { text: "Css", link: "/css/intro" },
    ],
  },
  feFwAndLib: {
    title: "前端框架與庫",
    content: "讓你事半功倍",
    icon: feFwLibImg,
    nav: [
      { text: "Vue", link: "/vue/intro" },
      { text: "Vuex", link: "/vuex/intro" },
    ],
  },
};

export default navGroup;

動態設定導航

我們根據不同路由,匹配並展示不同的導航
xxx/docs/.vitepress/theme/index.ts

import DefaultTheme from "vitepress/theme";
import Home from "./home.vue";
import { watch } from 'vue'
import navGroup from "./nav-group";

const getNavs = (currentRoute, siteBaseUrl) => {
  siteBaseUrl = siteBaseUrl.slice(0, -1);
  currentRoute = currentRoute.replace(/\.[^/.]+$/, "");
  for (const key in navGroup) {
    const match = navGroup[key].nav?.find(it => (siteBaseUrl + it.link) === currentRoute);
    if (match) return navGroup[key].nav; // 返回找到的根鍵,例如 'holyTrinity'
  }
  return null;
}

export default {
  extends: DefaultTheme,
  enhanceApp({ app, router, siteData }) {
    watch(
      () => router.route.path,
      (val) => {
        const nav = getNavs(val, siteData.value.base);
        console.log(nav);
        
        // 動態修改站點資料(畢竟使用useData不生效),比如這裡動態設定navbar
        if (nav) {
          siteData.value = {
            ...siteData.value,
            themeConfig: {
              ...siteData.value.themeConfig,
              nav
            }
          }
        }
      }
    );
    app.component("customHome", Home);
  },
};

自定義home頁

xxx/docs/.vitepress/theme/home.vue

<template>
  <div class="home">
    <div class="other"></div>
    <div class="docss">
      <template v-for="key in Object.keys(navGroup)" :key="key">
        <div class="docs">
          <img :src="navGroup[key].icon" />
          <div class="content">{{ navGroup[key].content }}</div>
          <div class="title" @click="goByNavGroup(navGroup[key])">
            {{ navGroup[key].title }}
          </div>
        </div>
      </template>
    </div>
  </div>
</template>

<script setup name="customHome" lang="ts">
// @ts-nocheck
import navGroup from "./nav-group";
import { useRouter, useData } from "vitepress";

const data = useData()
const router = useRouter();

const goByNavGroup = (item) => {
  const [{ link }] = item.nav;
  let base = data.site.value.base
  base = base.slice(0, -1);
  router.go(base + link);

};
</script>

<style scoped lang="scss">
.home {
  width: 100%;
  max-width: 1024px;
  background-color: rgba($color: #000000, $alpha: 0.01);
  height: 100vh;
  margin: auto;

  .other {
    height: 100px;
  }

  .docss {
    margin: auto;
    display: flex;
    flex-wrap: wrap;

    .docs {
      padding: 0 16px;
      // height: 180px;
      margin-bottom: 20px;
      cursor: pointer;

      img {
        width: 100%;
        border-radius: 4px;
        background-color: rgba($color: #000000, $alpha: 0.1);
      }

      .title {
        text-align: center;
        font-weight: 700;
      }

      .content {
        text-align: center;
        font-size: 13px;
      }
    }

    // 媒體查詢:當螢幕寬度小於600px時
    @media screen and (max-width: 600px) {
      .docs {
        width: 50%;

        img {
          height: 90px;
        }
      }
    }

    // 媒體查詢:當螢幕寬度在400px到600px之間時
    @media screen and (min-width: 600px) and (max-width: 800px) {
      .docs {
        width: 33%;

        img {
          height: 110px;
        }
      }
    }

    // 媒體查詢:當螢幕寬度大於800px時
    @media screen and (min-width: 800px) {
      .docs {
        width: 25%;

        img {
          height: 130px;
        }
      }
    }
  }
}
</style>

預覽效果

相關文章