老规矩,先上效果图!
达成这个效果,首先我们先了解下原理
在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;"
>
<i class="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"
>
<i class="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;"
>
<i class="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 Vue from 'vue'
import Vuex from 'vuex'
/* eslint-disable */
Vue.use(Vuex)
export default new Vuex.Store({
state: {
openTab: [],
activeIndex: ''
},
mutations: {
add_tabs (state, data) {
this.state.openTab.push(data)
},
delete_tabs (state, route) {
let index = 0
for (let gohh of 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 Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import './assets/iconfont/iconfont.css'
import store from './store/index.js'
Vue.use(ElementUI)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: ' '
})
即把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 () {
return this.$store.state.openTab
},
activeIndex: {
get () {
return this.$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 = false
for (let item of this.openTab) {
if (item.name === to.name) {
this.$store.commit('set_active_index', to.path)
flag = true
break
}
}
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结束当前函数
讲解了上面的代码后大家应该可以理解明白后边的代码了
自己阅读理解能更多的提升自己
做好这些操作就可以实现效果啦!样式大家可以自己去定义,只要按照这个思路走一遍就没问题啦
希望我们共同进步,互相提升! 有更好更高效的方法还望大佬不吝赐教!