首先上图给大家看看实现效果:
然后直接上核心代码:
<!-- 左侧菜单栏 -->
<div class="aside">
<el-menu
class="menu"
:default-active="$route.path"
@open="handleOpen"
@close="handleClose"
router
>
<template v-for="(item, index) in menu">
<!-- 遍历生成菜单的时候根据对象是否有subMenu来判断是否有二级菜单,没有就直接生成一级菜单 -->
<el-submenu
v-if="item.subMenu"
:index="item.subMenu[0].path"
:key="index + '-Susuk'"
>
<template slot="title">
<span slot="title">{{ item.title }}</span>
</template>
<el-menu-item-group>
<el-menu-item
v-for="item2 in item.subMenu"
:key="item2.id"
:index="item2.path"
>{{ item2.title }}</el-menu-item
>
</el-menu-item-group>
</el-submenu>
<el-menu-item v-else :index="item.path" :key="index + '-Suk'">{{
item.title
}}</el-menu-item>
</template>
</el-menu>
</div>
<!-- 右侧内容区 -->
<div class="content">
<el-tabs
v-model="tabsValue"
type="card"
closable
@tab-remove="removeTab"
@tab-click="tabClick"
>
<el-tab-pane
v-for="item in tabs"
:key="item.name"
:label="item.title"
:name="item.name"
>
<component :is="item.component"></component>
</el-tab-pane>
</el-tabs>
</div>
首先我们要开启el-menu的router模式,这样index填写的就是我们的路由路径,点哪个菜单就会跳转到对应的路由:
然后根据element官网给出的结构改造成自己的,el-menu内包裹的结构大概是这样的:
1.只有一级菜单(也就是没有往下展开的,只有一层),直接用
<!-- index就是路由路径 -->
<el-menu-item v-else :index="item.path" :key="index + '-Suk'">{{item.title}}</el-menu-item>
2.有二级菜单(也就是可以展开,展开后又是一层)
<el-submenu
v-if="item.subMenu"
:index="item.subMenu[0].path"
:key="index + '-Susuk'"
>
<!-- 这是一级菜单上面的标题 -->
<template slot="title">
<span slot="title">{{ item.title }}</span>
</template>
<!-- 这是展开后二级菜单的菜单组,里面可以有多个菜单 -->
<el-menu-item-group>
<el-menu-item
v-for="item2 in item.subMenu"
:key="item2.id"
:index="item2.path"
>{{ item2.title }}</el-menu-item
>
</el-menu-item-group>
</el-submenu>
基本结构就是这样,然后菜单的数据结构是这样的:
/**
* title: 菜单标题
* component:对应的子组件页面
* path:子组件对应的路由
*/
export default [
{
title: "用户管理",
component: "User",
path: "/home/user",
},
{
title: "管理员管理",
component: "Admin",
path: "/home/admin",
},
{
title: "词库分类管理",
component: "RepositoryCategory",
path: "/home/repositoryCategory",
},
{
title: "词库管理",
component: "Repository",
path: "/home/repository",
},
{
title: '词汇分类管理',
component: 'WordCategory',
path: '/home/wordCategory'
},
{
title: "词汇管理",
component: "Word",
path: "/home/word",
},
{
title: "词汇声调管理",
component: "WordTone",
path: "/home/wordTone",
},
{
title: "词汇敬语等级管理",
component: "WordHonorificLevel",
path: "/home/wordHonorificLevel",
},
{
title: '系统管理',
// subMenu就是二级菜单
subMenu: [
{
title: "数据字典管理",
component: "DataDirectory",
path: "/home/dataDirectory",
},
{
title: "任务计划管理",
component: "TaskPlan",
path: "/home/taskPlan",
},
],
},
]
将菜单项里面的路由配置到路由规则中:
// 配制路由规则
const routes = [
{
path: '/',
redirect: '/login'
},
// 登录页面
{
path: '/login',
component: () => import('@/views/login/Login')
},
{
path: '/home',
redirect: '/home/user'
},
// 后台首页
{
path: '/home',
component: () => import('@/views/layout/Home'),
/**
* tab子页面
* meta:{
* component:tab显示的子组件
* title:tab的标题
* path:tab对应的路由路径
* }
*/
children: [
// 用户管理
{
path: '/home/user',
component: () => import('@/views/layout/user/User'),
meta: {
tabInfo: {
component: 'User',
title: '用户管理',
path: '/home/user'
}
}
},
// 管理员管理
{
path: '/home/admin',
component: () => import('@/views/layout/admin/Admin'),
meta: {
tabInfo: {
component: 'Admin',
title: '管理员管理',
path: '/home/admin'
}
}
},
// 词库分类管理
{
path: '/home/repositoryCategory',
component: () =>
import('@/views/layout/repositoryCategory/RepositoryCategory'),
meta: {
tabInfo: {
component: 'RepositoryCategory',
title: '词库分类管理',
path: '/home/repositoryCategory'
}
}
},
// 词库管理
{
path: '/home/repository',
component: () => import('@/views/layout/repository/Repository'),
meta: {
tabInfo: {
component: 'Repository',
title: '词库管理',
path: '/home/repository'
}
}
},
// 词汇分类管理
{
path: '/home/wordCategory',
component: () => import('@/views/layout/wordCategory/WordCategory'),
meta: {
tabInfo: {
component: 'WordCategory',
title: '词汇分类管理',
path: '/home/wordCategory'
}
}
},
// 词汇声调管理
{
path: '/home/wordTone',
component: () => import('@/views/layout/wordTone/WordTone'),
meta: {
tabInfo: {
component: 'WordTone',
title: '词汇声调管理',
path: '/home/wordTone'
}
}
},
// 词汇敬语等级管理
{
path: '/home/wordHonorificLevel',
component: () =>
import('@/views/layout/wordHonorificLevel/WordHonorificLevel'),
meta: {
tabInfo: {
component: 'WordHonorificLevel',
title: '词汇敬语等级管理',
path: '/home/wordHonorificLevel'
}
}
},
// 词汇管理
{
path: '/home/word',
component: () => import('@/views/layout/word/Word'),
meta: {
tabInfo: {
component: 'Word',
title: '词汇管理',
path: '/home/word'
}
}
},
// 数据字典管理
{
path: '/home/dataDirectory',
component: () =>
import('@/views/layout/system/dataDirectory/DataDirectory'),
meta: {
tabInfo: {
component: 'DataDirectory',
title: '数据字典管理',
path: '/home/dataDirectory'
}
}
},
// 任务计划管理
{
path: '/home/taskPlan',
component: () => import('@/views/layout/system/taskPlan/TaskPlan'),
meta: {
tabInfo: {
component: 'TaskPlan',
title: '任务计划管理',
path: '/home/taskPlan'
}
}
}
]
}
]
右侧内容区采用了tabs嵌套动态组件,我们要一次性导入所有子页面组件,然后通过监听路由的变化切换右边对应的子组件页面,路由每次变化都会向tabs数组中插入一个tab,右边遍历出来就多一个tab:
data () {
return {
tabsValue: '', // 当前显示的选项卡对应的子组件路由
tabs: [], // tabs选项卡列表 name就是路由path comp决定该tab显示哪个子组件页面 title:tab标题
menu: menu // 菜单列表
}
},
watch: {
// 监听路由变化
$route: {
handler (newValue) {
// tab去重处理
// 判断子组件名和路由元信息中的子组件名相同
const index = this.tabs.findIndex(
item => item.component === newValue.meta.tabInfo.component
)
// 如果tab存在,则只切换当前tab,不添加
if (index !== -1) {
this.tabsValue = this.tabs[
this.tabs.findIndex(
item => item.component === newValue.meta.tabInfo.component
)
].name
return
}
// 路由变化后将当前路由和默认高亮的菜单保持一致,避免页面刷新还是默认的值
this.defaultActive = newValue.path
// 添加tab信息到集合列表,添加一个顺序往后排一个
this.tabs.push({
title: newValue.meta.tabInfo.title,
component: newValue.meta.tabInfo.component,
name: newValue.path
})
// 每次添加后取最后一个的name显示新点击的tab
this.tabsValue = this.tabs[this.tabs.length - 1].name
},
// 页面首次加载也监听路由的值
immediate: true
}
},
当tab被点击时,判断点击的tab的name(name就是path)是不是和当前$route.path一致,如果不一致就高亮对应的菜单,一致就啥都不做,直接跳转路由触发监听器将跳转后的新path赋值给默认高亮值defaultValue
// tab被点击时触发
tabClick ({ name }) {
if (name === this.$route.path) return
this.$router.push(name) // 触发路由监听,改变对应高亮的菜单
},
监听一级菜单的展开,如果展开了就默认跳转到第一个子菜单,同时高亮:
// 监听一级菜单的展开
handleOpen (key) {
// console.log(key, keyPath);
this.$router.push(key)
},
监听tab的删除:
// 删除tab
removeTab (targetPath) {
// 如果只剩一个tab时,就不能再删除了
if (this.tabs.length < 2) {
return this.$message({
type: 'warning',
message: '至少要有一个标签页!',
duration: 2000
})
}
const tabs = this.tabs
let activePath = this.tabsValue
// 在所有tab中选出删除的目标标签页的上一个或者下一个,相邻的就是下一个当前tab
tabs.forEach((tab, index) => {
if (tab.name === targetPath) {
const nextTab = tabs[index - 1] || tabs[index + 1]
// 将下一个tab高亮
activePath = nextTab.name
// 删除掉要删除的目标tab
this.tabs.splice(index, 1)
}
})
// 路由跳转到删除后的下一个tab,触发路由监听改变左边对应菜单栏的高亮
this.$router.push(activePath)
}
今晚先总结到这吧,加班到11点才回到家写一篇,太TM的困了,睡觉...