外观
使用 VitePress 打造个人前端导航网站
原文地址:使用 VitePress 打造个人前端导航网站 - 掘金 (juejin.cn)
修改 VitePress
主题
因为 layout: doc
主要是提供给文档使用的,其页面宽度有限,同时为了更好的样式隔离,为其添加一个 layoutClass
方便我们更好的去自定义样式
在 docs/.vitepress/theme
目录下新建 index.ts
文件
ts
import { h, App } from 'vue'
import { useData } from 'vitepress'
import Theme from 'vitepress/theme'
export default Object.assign({}, Theme, {
Layout: () => {
const props: Record<string, any> = {}
// 获取 frontmatter
const { frontmatter } = useData()
/* 添加自定义 class */
if (frontmatter.value?.layoutClass) {
props.class = frontmatter.value.layoutClass
}
return h(Theme.Layout, props)
}
})
添加页面和样式
在 docs/nav
目录下新建 index.md
frontmatter 用于配置页面信息,也可以添加一些自定义信息
md
---
layoutClass: m-nav-layout
head: [
['link', { rel: 'stylesheet', href: '/css/nav.css' }],
]
---
<script setup>
import MNavLinks from './components/MNavLinks.vue'
import { NAV_DATA } from './data'
</script>
# 前端导航
<MNavLinks v-for="{title, items} in NAV_DATA" :title="title" :items="items"/>
在 /public/css/
目录下新建 nav.css
用外部文件引入css
,这样也不会影响其他页面
css
/* 修改 layout 最大宽度 */
.container {
max-width: var(--vp-layout-max-width) !important;
}
.content-container,
.content {
max-width: 100% !important;
}
.m-nav-link .box {
background-color: #f6f6f7;
border: 1px solid transparent;
/* 添加透明的边框 */;
}
.m-nav-link .box:hover {
background-color: #ffffff;
border: 1px solid #000000;
/* 添加边框 */;
}
.vp-doc a {
text-decoration: none;
/* 取消下划线 */
color: inherit;
/* 继承父元素的字体颜色 */;
}
编写导航内容组件
为了让这个导航网站与整个站点风格相符,我选择了首页的 features
作为参考并进行了改造。
在 docs/nav/components
目录下新建 type.ts
ts
export interface NavLink {
/** 站点图标 */
icon?: string | { svg: string }
/** 站点名称 */
title: string
/** 站点名称 */
desc?: string
/** 站点链接 */
link: string
}
在 docs/nav/components
目录下新建 MNavLink.vue
html
<script setup lang="ts">
import { computed } from 'vue'
import { NavLink } from './type'
const props = defineProps<{
icon?: NavLink['icon']
title?: NavLink['title']
desc?: NavLink['desc']
link: NavLink['link']
}>()
const svg = computed(() => {
if (typeof props.icon === 'object') return props.icon.svg
return ''
})
</script>
<template>
<a v-if="link" class="m-nav-link" :href="link" target="_blank" rel="noreferrer">
<article class="box">
<div class="box-header">
<div v-if="svg" class="icon" v-html="svg"></div>
<div v-else-if="icon && typeof icon === 'string'" class="icon">
<img :src="icon" :alt="title" onerror="this.parentElement.style.display='none'" />
</div>
<h6 v-if="title" class="title">{{ title }}</h6>
</div>
<p v-if="desc" class="desc">{{ desc }}</p>
</article>
</a>
</template>
<style lang="scss" scoped>
.m-nav-link {
display: block;
border: 1px solid var(--vp-c-bg-soft);
border-radius: 8px;
height: 100%;
cursor: pointer;
transition: all 0.3s;
&:hover {
background-color: var(--vp-c-bg-soft);
}
.box {
display: flex;
flex-direction: column;
padding: 16px;
height: 100%;
color: var(--vp-c-text-1);
&-header {
display: flex;
align-items: center;
}
}
.icon {
display: flex;
justify-content: center;
align-items: center;
margin-right: 12px;
border-radius: 6px;
width: 48px;
height: 48px;
font-size: 24px;
background-color: var(--vp-c-mute);
transition: background-color 0.25s;
:deep(svg) {
width: 24px;
fill: currentColor;
}
:deep(img) {
border-radius: 4px;
width: 24px;
}
}
.title {
overflow: hidden;
flex-grow: 1;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 48px;
font-size: 16px;
font-weight: 600;
}
.desc {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
flex-grow: 1;
margin: 10px 0 0;
line-height: 20px;
font-size: 12px;
color: var(--vp-c-text-2);
}
}
@media (max-width: 960px) {
.m-nav-link {
.box {
padding: 8px;
}
.icon {
width: 40px;
height: 40px;
}
.title {
line-height: 40px;
font-size: 14px;
}
}
}
</style>
为了自动生成分类目录,需要使用 @mdit-vue/shared
中的 slugify
方法对 title
进行格式化
bash
npm i -D @mdit-vue/shared
npm i -D sass
在 docs/nav/components
目录下新建 MNavLinks.vue
html
<script setup lang="ts">
import { computed } from 'vue'
import { slugify } from '@mdit-vue/shared'
import MNavLink from './MNavLink.vue'
import type { NavLink } from './type'
const props = defineProps<{
title: string
items: NavLink[]
}>()
const formatTitle = computed(() => {
return slugify(props.title)
})
</script>
<template>
<h2 v-if="title" :id="formatTitle" tabindex="-1">
{{ title }}
<a class="header-anchor" :href="`#${formatTitle}`" aria-hidden="true">#</a>
</h2>
<div class="m-nav-links">
<MNavLink
v-for="{ icon, title, desc, link } in items"
:key="link"
:icon="icon"
:title="title"
:desc="desc"
:link="link"
/>
</div>
</template>
<style lang="scss" scoped>
.m-nav-links {
--gap: 10px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
grid-row-gap: var(--gap);
grid-column-gap: var(--gap);
grid-auto-flow: row dense;
justify-content: center;
margin-top: var(--gap);
}
@each $media, $size in (500px: 140px, 640px: 155px, 768px: 175px, 960px: 200px, 1440px: 240px) {
@media (min-width: $media) {
.m-nav-links {
grid-template-columns: repeat(auto-fill, minmax($size, 1fr));
}
}
}
@media (min-width: 960px) {
.m-nav-links {
--gap: 20px;
}
}
</style>
在 docs/nav
目录下新建 data.ts
,后续维护只需要编辑data文件即可。
ts
import type { NavLink } from './components/type'
type NavData = {
title: string
items: NavLink[]
}
export const NAV_DATA: NavData[] = [
{
title: '常用工具',
items: [
{
icon: 'https://caniuse.com/img/favicon-128.png',
title: 'Can I use',
desc: '前端 API 兼容性查询',
link: 'https://caniuse.com'
},
{
icon: 'https://tinypng.com/images/apple-touch-icon.png',
title: 'TinyPNG',
desc: '在线图片压缩工具',
link: 'https://tinypng.com'
}
]
},
{
title: 'AI 导航',
items: [
{
icon: '/icons/chatgpt.png',
title: 'ChatGPT(最强)',
link: 'https://chat.openai.com/chat'
},
{
icon: 'https://www.notion.so/images/logo-ios.png',
title: 'Notion AI(笔记)',
link: 'https://www.notion.so'
}
]
}
]