Vue如何实现el-menu与el-tabs联动,通过点击el-menu导航中的选项动态添加tab页面
老规矩,先上效果图!
达成这个效果,首先我们先了解下原理
在el-menu中有一个属性router,开发文档中写的非常清晰,选择该属性后即开启路由跳转,即点击el-menu中的子选项后会进行页面跳转,但是你必须将需要跳转的路由地址写为跟组件的子路由地址,否则点击跳转后会直接跳向路由地址对应的页面,这样就失去了我们想要实现的效果
接下来说下el-tabs,它的构成规则大家可以去看一下饿了么ui(element-ui)开发文档中的模板说明,它里面的子元素都是通过遍历数组出来的,我给大家看下模板
所以明确这个,大家就应该有了思路
下面我讲下原理,首先我们需要一个全局变量用来存储即将要跳转的路由地址是什么,将其构建成一个数组,这里可以用BUS总线机制但更为简洁高效的方式是使用Vuex,关于Vuex的开发文档大家可以简单了解下,其实很简单,不要觉得麻烦
这是它的构造图,我们就将Vuex简单的理解为一个全局变量,可以看到他的整体走向,首先从State开始,State的作用就是一个仓库,用来存储你想要存取的数据,通过Dispatch方法将数据派遣到Actions进行一些操作,之后Actions再向Mutations提交完成转变
原理很简单,这里我们可以省区中间Actions的步骤,直接从State仓库向Mutations提交完成一系列的操作
好了,有了这个全局变量后,接下来的操作就一切清晰明了了,下面是整个demo的设计思路
点击el-menu中的子选项(将每个子选项的index值改为要跳转页面的路由地址,例:/page1) ==> 将这些地址存入Vuex中的State仓库 ==> el-tabs中el-tab-pane的循环数组变为当前的State仓库(当你引用vuex后,State仓库中的数据会逐一派发给各个组件) ==> 在Mutaition中写明方法,将要跳转的路由地址对应的页面设为激活项(即el-tab-pane中激活的页面)
这样一说大家是不是思路就很清晰了! 下面开始上代码
首先我将整个页面拆分成了两大组件,分别是左侧的LeftMenu,和右侧主体页面TabInner(其中包含了顶部的导航栏和下面el-tabs展示的页面)
先看LeftMenu的代码
<el-menu:default-active="$route.path"class="el-menu-vertical-demo":collapse="isCollapse"
background-color="#1F2D3D"
text-color="#ffffff"
router><el-menu-item
index="/page1"class="homePage"
style="margin: 0 0 30px 0;"><iclass="iconfont" style="margin: 0 8px 0 0;"></i><span slot="title">首页</span></el-menu-item><el-menu-item
v-for="item of MenuList":key="item.id":index="item.index"><iclass="iconfont" style="margin: 0 8px 0 0;">{{item.icon}}</i><span slot="title">{{item.content}}</span></el-menu-item></el-menu>
大家可以看到el-menu中添加了router选项,即开启了路由跳转地址,:default-active为什么要等于$route.path呢,是因为这样可以根据你跳转的地址来动态的切换激活选项,如果你设为定值,大家可以自行看下控制台的报错信息
里面循环的data数据
MenuList:[{
index:'/page2',
content:'数据目录管理',
icon:'\ue619'},{
index:'/page3',
content:'数据产品管理',
icon:'\ue625'}]
icon是iconfont中的,若想使用请翻看我博客中关于iconfont如何加入在v-for循环的数据中
首页我单独放在了一个el-menu-item中,剩下的子页面用循环展示
<el-menu-item
index="/page1"class="homePage"
style="margin: 0 0 30px 0;"><iclass="iconfont" style="margin: 0 8px 0 0;"></i><span slot="title">首页</span></el-menu-item>
然后配置下路由地址,找到router.js或是模块化开发router文件夹下的index.js
{
path:'/',
component: Home,
redirect:'/page1',
children:[{
path:'/page1',
name:'首页',
component: page1,
meta:{ title:'首页'}},{
path:'/page2',
name:'数据目录管理',
component: page2,
meta:{ title:'数据目录管理'}},{
path:'/page3',
name:'数据产品管理',
component: page3,
meta:{ title:'数据产品管理'}}]}
将这些子页面设为根路径下的子路由,并将页面重定向设置为/page1(即redirect: ‘/page1’),这样可以在打开项目的时候直接展示首页
下一步安装Vuex
npm i vuex--save
安装好后,我们开始配置
首先在src目录下新建一个store文件夹,在里面创建一个index.js
在里面配置,这里我就不过多叙述了,大家按着我的来就可以
import Vuefrom'vue'import Vuexfrom'vuex'/* eslint-disable */
Vue.use(Vuex)exportdefaultnewVuex.Store({
state:{
openTab:[],
activeIndex:''},
mutations:{add_tabs(state, data){this.state.openTab.push(data)},delete_tabs(state, route){let index=0for(let gohhof state.openTab){if(gohh.route=== route){break}
index++}this.state.openTab.splice(index,1)},set_active_index(state, index){this.state.activeIndex= index}}})
写完后在main.js中引入vuex,这样就可以将数据派发到各个组件上
import Vuefrom'vue'import Appfrom'./App'import routerfrom'./router'import ElementUIfrom'element-ui'import'element-ui/lib/theme-chalk/index.css'import'./assets/iconfont/iconfont.css'import storefrom'./store/index.js'
Vue.use(ElementUI)
Vue.config.productionTip=false/* eslint-disable no-new */newVue({
el:'#app',
router,
store,
components:{ App},
template:'<App/>'})
即把store引入,并在下面注册
做好这些后我简单说下store里面的index.js中文件的内容大概是什么意思
首先state仓库中分别存储了两个信息,一个是存放所有跳转路由地址的数组openTab,另一个是el-tab-pane哟弄个来展示当前激活页面的activeindex
那么mutations中存放的add_tabs是点击左侧el-menu中选项时触发的方法,即接受当前跳转的路由地址并将这一信息推入openTab这个数组中, 而delete_tabs则是将开启的tab标签关掉并设置下一激活项,set_active_index是设置激活项的方法
配置好这些以后,我们在tabInner组件中(即右侧主题内容组件)开始编写代码
首先书写计算属性computed,将el-tab-pane需要循环使用的openTab数组和展示激活项的activeIndex引入过来
computed:{openTab(){returnthis.$store.state.openTab},
activeIndex:{get(){returnthis.$store.state.activeIndex},set(val){this.$store.commit('set_active_index', val)}}}
之后在el-tabs中写入
<el-tabs
v-model="activeIndex"
type="card"
@tab-click="clickTab"
@tab-remove="removeTab"
closable><el-tab-pane
v-for="item of openTab"
v-if="openTab.length":key="item.name":label="item.name":name="item.route"></el-tab-pane></el-tabs>
双向绑定activeIndex即可展示对应激活项
之后通过监听方法watch监听路由变化做事件处理
watch:{'$route'(to,from){let flag=falsefor(let itemofthis.openTab){if(item.name=== to.name){this.$store.commit('set_active_index', to.path)
flag=truebreak}}if(!flag){this.$store.commit('add_tabs',{route: to.path, name: to.name})this.$store.commit('set_active_index', to.path)}}}
这里为什么会定义一个值为布尔属性的变量flag呢,大家可以阅读下代码,意为,首先进行for循环,若此时openTab并未推入任何数据,是一个空数组,那么下面的if判断就不会成立,固flag不能变为true,从而进行下面的if(!flag)判断,若里面值为真才可进行其中的操作事件,此时flag仍未false,!flag即为true,我们提交两个方法,一个是当前路由地址推入state仓库中的openTab数组,另一个是设置el-tab-pane的激活项,即打开对应的页面
上面的话简单的可以理解为,若openTab中含有数据,那么我进行下面的判断,如果成立(意思就是左侧点击的导航项已经在el-tabs中打开了一个标签了,已经存在的页面就不会再打开了,直接进行页面的切换就可以了)然后flag变为true,并跳出循环,那么!flag就变成假了,下面的if判断也不会在做了。 但是如果openTab中并没有当前路由地址对应的页面信息,那么我就把这个信息存进去,并把el-tab-pane的激活项设置为他
mounted(){// 刷新时以当前路由做为tab加入tabs// 当前路由不是首页时,添加首页以及另一页到store里,并设置激活状态// 当当前路由是首页时,添加首页到store,并设置激活状态if(this.$route.path!=='/'&&this.$route.path!=='/page1'){this.$store.commit('add_tabs',{route:'/page1', name:'首页'})this.$store.commit('add_tabs',{route:this.$route.path, name:this.$route.name})this.$store.commit('set_active_index',this.$route.path)}else{this.$store.commit('add_tabs',{route:'/page1', name:'首页'})this.$store.commit('set_active_index','/page1')}}
同时我们也要再mounted中加入以下代码,这里写的很详细了,大家自行阅读下
做完这些后,我们需要把tab-click和tab-remove两个点击事件书写一下
首先是tab-click对应的方法clickTab
clickTab(tab){this.$router.push({path:this.activeIndex})
console.log(this.$route.path)}
点击事件后,直接推向当前对应的激活项
接下来是tab-remove对应的方法removeTab
removeTab(target){if(target=='/'||target=='/page1'){return}this.$store.commit('delete_tabs', target)if(this.activeIndex=== target){// 设置当前激活的路由if(this.openTab&&this.openTab.length>=1){
console.log('=============',this.openTab[this.openTab.length-1].route)this.$store.commit('set_active_index',this.openTab[this.openTab.length-1].route)this.$router.push({path:this.activeIndex})}}}
第一个判断是保证首页不关,如果当前路径是根路径或者是我首页的路径,那么直接return结束当前函数
讲解了上面的代码后大家应该可以理解明白后边的代码了
自己阅读理解能更多的提升自己
做好这些操作就可以实现效果啦!样式大家可以自己去定义,只要按照这个思路走一遍就没问题啦
希望我们共同进步,互相提升! 有更好更高效的方法还望大佬不吝赐教!