有一部分页面没有左侧菜单,顶部导航几乎所有页面都有,特殊的搜索页面,登录页是独立的,
一、最终实现思路(非常重要)
- 登录页、搜索页:独立页面,无任何公共布局(头 / 左 / 底全没有)
- 首页 home:走 Layout 布局 → 只有顶部导航 + 内容 + 底部,【隐藏左侧菜单】免登录
- 栏目 A (menuA):走 Layout → 顶 + 左侧菜单 + 内容 + 底部,免登录
- noSidePage:走 Layout → 顶 + 内容 + 底部,无左侧菜单,免登录
- Admin/OrderManage:走 Layout → 顶 + 左侧菜单 + 内容 + 底部,【必须登录】
控制规则统一用路由 meta:
isHideSide:true= 隐藏左侧菜单requireAuth:true= 需要登录验证
一、目录结构不变
plaintext
src
├── App.vue
├── router/index.js
├── views
├── Login/index.vue # 独立页面
├── Search/index.vue # 独立页面
├── Layout/
│ ├── index.vue
│ └── components/Header.vue、Sidebar.vue、Footer.vue
├── Home/index.vue # 首页:Layout+无左侧+免登
├── MenuA/index.vue # 栏目A:Layout+有左侧+免登
├── NoSidePage/index.vue # 普通无侧边:Layout+无左侧+免登
├── Admin/index.vue # 管理页:Layout+有左侧+需登录
├── OrderManage/index.vue # 订单页:Layout+有左侧+需登录
1. App.vue(完全不变)
vue
<template>
<router-view />
</template>
<script setup>
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,body,#app{
height:100%;
}
</style>
2. router/index.js【关键改动:首页加 isHideSide:true】
js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
redirect: '/home'
},
// 独立页面,不使用Layout
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login/index.vue')
},
{
path: '/search',
name: 'Search',
component: () => import('@/views/Search/index.vue')
},
// 公共Layout容器
{
path: '/',
component: () => import('@/views/Layout/index.vue'),
children: [
// ★首页:免登 + 隐藏左侧菜单 isHideSide:true
{
path: 'home',
component: () => import('@/views/Home/index.vue'),
meta: { title: '首页', isHideSide: true }
},
// 栏目A:免登 + 显示左侧菜单(无任何meta标记)
{
path: 'menuA',
component: () => import('@/views/MenuA/index.vue'),
meta: { title: '栏目A' }
},
// 普通无侧边页面:免登、无左侧
{
path: 'noSidePage',
component: () => import('@/views/NoSidePage/index.vue'),
meta: { title: '无侧边页面', isHideSide: true }
},
// 需要登录、显示左侧菜单
{
path: 'admin',
component: () => import('@/views/Admin/index.vue'),
meta: { title: '管理员后台', requireAuth: true }
},
{
path: 'orderManage',
component: () => import('@/views/OrderManage/index.vue'),
meta: { title: '订单管理', requireAuth: true }
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
// 已登录禁止进登录页,跳首页
if(to.path === '/login' && token){
next('/home')
return
}
// 仅requireAuth页面校验登录
if(to.meta.requireAuth){
if(token) next()
else next({path:'/login', query:{redirect:to.fullPath}})
}else{
next()
}
})
export default router
3. Layout/index.vue(代码不变,依靠 meta 自动隐藏侧边)
vue
<template>
<div class="layout-wrap">
<LayoutHeader />
<div class="layout-body">
<!-- isHideSide=true 就不渲染左侧菜单 -->
<LayoutSidebar v-if="!route.meta.isHideSide"/>
<div class="layout-right" :class="{fullWidth: route.meta.isHideSide}">
<div class="layout-content">
<router-view/>
</div>
<LayoutFooter/>
</div>
</div>
</div>
</template>
<script setup>
import LayoutHeader from './components/Header.vue'
import LayoutSidebar from './components/Sidebar.vue'
import LayoutFooter from './components/Footer.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
</script>
<style scoped>
.layout-wrap{
height:100vh;
display:flex;
flex-direction:column;
}
.layout-body{
flex:1;
display:flex;
overflow:hidden;
}
.layout-right{
flex:1;
display:flex;
flex-direction:column;
overflow:auto;
}
.layout-right.fullWidth{
flex:1 1 100%;
}
.layout-content{
flex:1;
padding:15px;
}
</style>
3.1 Layout/components/Header.vue(顶部导航不变)
vue
<template>
<header class="header">
<div class="logo">网站顶部导航</div>
<div class="search">
<input v-model="key" placeholder="搜索" @keyup.enter="toSearch"/>
</div>
<div class="user">
<span v-if="token">已登录</span>
<button v-if="token" @click="logout">退出</button>
<button v-else @click="$router.push('/login')">去登录</button>
</div>
</header>
</template>
<script setup>
import {ref,onMounted} from 'vue'
import {useRouter} from 'vue-router'
const router = useRouter()
const key = ref('')
const token = ref('')
onMounted(()=>{
token.value = localStorage.getItem('token')
})
const toSearch = ()=>{
router.push({path:'/search',query:{k:key.value}})
}
const logout = ()=>{
localStorage.removeItem('token')
router.push('/home')
}
</script>
<style scoped>
.header{
height:60px;
background:#409EFF;
display:flex;
align-items:center;
padding:0 20px;
color:#fff;
justify-content:space-between;
}
input{padding:6px 10px;border-radius:4px;border:none;}
</style>
3.2 Layout/components/Sidebar.vue(左侧菜单,自动根据当前路由切换菜单)
vue
<template>
<aside class="sidebar">
<ul>
<li v-for="item in menuList" :key="item.path">
<router-link :to="item.path">{{item.name}}</router-link>
</li>
</ul>
</aside>
</template>
<script setup>
import {ref,watch} from 'vue'
import {useRoute} from 'vue-router'
const route = useRoute()
const menuConfig = {
home: [
{path:'/home',name:'首页'},
{path:'/menuA',name:'栏目A'}
],
menuA: [
{path:'/menuA',name:'栏目A详情'},
{path:'/home',name:'返回首页'}
],
admin: [
{path:'/admin',name:'管理员首页'},
{path:'/orderManage',name:'订单管理'}
],
orderManage: [
{path:'/orderManage',name:'订单列表'},
{path:'/admin',name:'返回管理首页'}
]
}
const menuList = ref([])
function setMenu(path){
if(path.includes('home')) menuList.value = menuConfig.home
else if(path.includes('menuA')) menuList.value = menuConfig.menuA
else if(path.includes('admin')) menuList.value = menuConfig.admin
else if(path.includes('orderManage')) menuList.value = menuConfig.orderManage
else menuList.value = []
}
setMenu(route.path)
watch(()=>route.path,val=>setMenu(val))
</script>
<style scoped>
.sidebar{
width:220px;
background:#304156;
height:100%;
}
li{
line-height:46px;
}
a{
display:block;
padding:0 20px;
color:#bfcbd9;
text-decoration:none;
}
a.router-link-active{
background:#409EFF;
color:#fff;
}
</style>
3.3 Layout/components/Footer.vue(底部不变)
vue
<template>
<footer class="footer">
©2025 网站版权所有
</footer>
</template>
<style scoped>
.footer{
height:50px;
line-height:50px;
text-align:center;
background:#f5f5f5;
border-top:1px solid #eee;
}
</style>
4. 独立页面代码
4.1 Login/index.vue(独立无布局)
vue
<template>
<div class="login-box">
<div class="content">
<h3>用户登录</h3>
<input v-model="form.username" placeholder="账号">
<input v-model="form.pwd" type="password" placeholder="密码">
<button @click="loginBtn">登录</button>
</div>
</div>
</template>
<script setup>
import {reactive} from 'vue'
import {useRouter,useRoute} from 'vue-router'
const router = useRouter()
const route = useRoute()
const form = reactive({
username:'',
pwd:''
})
const loginBtn = ()=>{
localStorage.setItem('token','user-token')
const redirect = route.query.redirect || '/home'
router.push(redirect)
}
</script>
<style scoped>
.login-box{
height:100vh;
background:#f5f7fa;
display:flex;
align-items:center;
justify-content:center;
}
.content{
width:360px;
padding:30px;
background:#fff;
}
input{
width:100%;
margin:8px 0;
padding:10px;
border:1px solid #ddd;
}
button{
width:100%;
padding:10px;
background:#409EFF;
color:#fff;
border:none;
}
</style>
4.2 Search/index.vue(独立无布局)
vue
<template>
<div class="search-page">
<h2>搜索结果页(独立页面)</h2>
<p>搜索关键词:{{$route.query.k}}</p>
<button @click="$router.push('/home')">返回首页</button>
</div>
</template>
<style>
.search-page{padding:30px;}
</style>
5. 各个业务页面内容
5.1 Home/index.vue(首页:头部 + 内容 + 底部,无左侧菜单|免登)
vue
<template>
<div>
<h2>【首页】只有顶部导航+底部,没有左侧菜单,免登录访问</h2>
</div>
</template>
5.2 MenuA/index.vue(栏目 A:头 + 左菜单 + 内容 + 底|免登)
vue
<template>
<div>
<h2>栏目A页面:带左侧菜单,免登录</h2>
</div>
</template>
5.3 NoSidePage/index.vue(无头左,头 + 内容 + 底|免登)
vue
<template>
<div>
<h2>自定义无侧边页面:头部+底部,无左侧菜单</h2>
</div>
</template>
5.4 Admin/index.vue(带左侧菜单,需要登录)
vue
<template>
<div>
<h2>管理员后台:带左侧菜单,必须登录</h2>
</div>
</template>
5.5 OrderManage/index.vue(带左侧菜单,需要登录)
vue
<template>
<div>
<h2>订单管理页面:带左侧菜单,必须登录</h2>
</div>
</template>
页面分类汇总表
表格
| 页面 | 布局来源 | 左侧菜单 | 登录权限 | meta 配置 |
|---|---|---|---|---|
| Login、Search | 独立一级路由 | 无 | 不用鉴权 | 无 meta |
| Home(首页) | Layout 布局 | ❌隐藏 | 免登录 | isHideSide:true |
| MenuA | Layout 布局 | ✅显示 | 免登录 | 仅 title |
| NoSidePage | Layout 布局 | ❌隐藏 | 免登录 | isHideSide:true |
| Admin、OrderManage | Layout 布局 | ✅显示 | 必须登录 | requireAuth:true |
访问效果:
/home→ 上下布局(头 + 内容 + 底)/menuA→ 左菜单 + 右上内容 + 底/admin未登录→自动跳登录页,登录后正常显示左侧菜单