导航组件,在layout文件夹下面新建
ResponsiveNavbar.vue
<template><nav class="navbar" :class="{ 'navbar--mobile': isMobile }"><div class="navbar-container"><!-- Logo --><div class="navbar-logo"><router-link to="/" class="logo-link"><span class="logo-text">YourLogo</span></router-link></div><!-- 桌面端导航菜单 --><div v-if="!isMobile" class="navbar-menu"><ul class="menu-list"><li v-for="(item, index) in menuItems" :key="index" class="menu-item"@click="activeMenu(index)"><router-link :to="item.path" class="menu-link":class="{ 'menu-link--active': activeItem === index || item.active }">{{ item.name }}</router-link></li></ul></div><!-- 移动端汉堡菜单按钮 --><div v-else class="navbar-hamburger" @click="toggleMobileMenu"><span class="hamburger-line"></span><span class="hamburger-line"></span><span class="hamburger-line"></span></div></div><!-- 移动端下拉菜单 --><div v-if="isMobile && showMobileMenu" class="navbar-mobile-menu"><ul class="mobile-menu-list"><li v-for="(item, index) in menuItems" :key="index" class="mobile-menu-item"@click="closeMobileMenu"><router-link :to="item.path" class="mobile-menu-link">{{ item.name }}</router-link></li></ul></div></nav>
</template><script setup>
import { ref, onMounted, onUnmounted } from 'vue';
//从路由配置里面获取路由
import {routes} from '@/router';//根据layout过滤,得到需要展示的导航
const menuItems = [];
routes.filter(d=>{if(d.layout){let menu = {name: d.layout.name,path: d.path,active: false}if(window.location.pathname == d.path){menu.active = true}menuItems.push(menu)}
})// 响应式状态
const isMobile = ref(false);
const showMobileMenu = ref(false);
const activeItem = ref(null);const activeMenu = (index) => {activeItem.value = indexmenuItems.filter(d => {d.active = false})
}// 检测屏幕宽度变化
const checkScreenSize = () => {isMobile.value = window.innerWidth < 768;if (!isMobile.value) {showMobileMenu.value = false; // 如果从移动端切换到PC端,关闭移动菜单}
};// 切换移动端菜单显示状态
const toggleMobileMenu = () => {showMobileMenu.value = !showMobileMenu.value;
};// 关闭移动端菜单
const closeMobileMenu = () => {showMobileMenu.value = false;
};// 生命周期钩子
onMounted(() => {checkScreenSize();window.addEventListener('resize', checkScreenSize);
});onUnmounted(() => {window.removeEventListener('resize', checkScreenSize);
});
</script><style scoped>
/* 基础样式 */
.navbar {background-color: #ffffff;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);position: fixed;top: 0;left: 0;width: 100%;z-index: 1000;transition: all 0.3s ease;
}.navbar-container {display: flex;justify-content: space-between;align-items: center;padding: 0 2rem;height: 70px;max-width: 1200px;margin: 0 auto;
}.logo-link {text-decoration: none;color: #333;font-weight: bold;font-size: 1.5rem;
}.logo-text {color: #4a6cf7;
}/* 桌面端菜单样式 */
.menu-list {display: flex;list-style: none;margin: 0;padding: 0;
}.menu-item {margin: 0 1rem;position: relative;
}.menu-link {color: #333;text-decoration: none;font-weight: 500;padding: 0.5rem 0;transition: all 0.3s ease;position: relative;
}.menu-link:hover {color: #4a6cf7;
}/* 桌面端悬停效果 */
.menu-link::after {content: '';position: absolute;bottom: 0;left: 0;width: 0;height: 2px;background-color: #4a6cf7;transition: width 0.3s ease;
}.menu-link:hover::after {width: 100%;
}.menu-link--active {color: #4a6cf7;
}.menu-link--active::after {width: 100%;
}/* 移动端汉堡菜单 */
.navbar-hamburger {display: flex;flex-direction: column;justify-content: space-between;width: 30px;height: 20px;cursor: pointer;
}.hamburger-line {width: 100%;height: 2px;background-color: #333;transition: all 0.3s ease;
}/* 移动端菜单 */
.navbar-mobile-menu {position: absolute;top: 67px;left: 0;width: 100%;background-color: #ffffff;box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);z-index: 999;
}.mobile-menu-list {list-style: none;padding: 0;margin: 0;
}.mobile-menu-item {border-bottom: 1px solid #f0f0f0;
}.mobile-menu-link {display: block;padding: 1rem 2rem;color: #333;text-decoration: none;transition: background-color 0.3s ease;
}.mobile-menu-link:hover {background-color: #f5f5f5;
}/* 响应式调整 */
@media (max-width: 768px) {.navbar-container {padding: 0 1rem;}.logo-text {font-size: 1.2rem;}
}/* 移动端导航栏样式 */
.navbar--mobile {height: auto;padding: 0;
}.navbar--mobile .navbar-container {height: auto;padding: 1rem;
}.navbar--mobile .menu-list {flex-direction: column;
}
</style>
修改App.vue
<template><ResponsiveNavbar /><main><!-- 你的页面内容 --><router-view /></main>
</template><script lang="ts">
import { defineComponent } from "vue";
import ResponsiveNavbar from '@/layout/ResponsiveNavbar.vue';
export default defineComponent({name: "app",components: {ResponsiveNavbar}
});
</script>
在router/index.ts添加路由
export const routes = [{path: '/',name: 'Home',component: () => import('@/views/home/HomeView.vue'),layout: {name: "首页"}},{path: '/blog',name: 'Blog',component: () => import('@/views/blog/blog.vue'),meta: { requiresAuth: false },layout: {name: "文章"}},{path: '/friend',name: 'Friend',component: () => import('@/views/friend/friend.vue'),layout: {name: "友链"}},{path: '/about',name: 'About',component: () => import('@/views/about/about.vue'),meta: { requiresAuth: false },layout: {name: "关于"}},{path: '/message',name: 'Message',component: () => import('@/views/message/message.vue'),meta: { requiresAuth: false },layout: {name: "留言板"}}
}]
layout为导航选项,如果该路由是导航,那必须要写,否则就不用写
效果图
以上仅为展示效果,具体细节还需自己更改