创建标签页
先展示最终效果
一、创建标签页
- 创建一个
Tabs/index.vue
页面
<template>
<div>
<el-tabs v-model="editableTabsValue" type="card" closable>
<el-tab-pane
:key="item.name"
v-for="item in editableTabs"
:label="item.title"
:name="item.name"
>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
export default {
name: "Tabss",
data() {
return {
editableTabsValue: "1",
editableTabs: [
{
title: "首页",
name: "1",
content: "首页",
},
{
title: "Tab 1",
name: "2",
content: "Tab 1 content",
},
{
title: "Tab 2",
name: "3",
content: "Tab 2 content",
},
],
tabIndex: 1,
};
},
};
</script>
<style scoped>
div{
height: auto;
}
</style>
- 修改Index.vue页面,在页面的路由锚点
router-view
上方引入此页面
页面效果
二、点击菜单展示新标签页
1、将标签数据作为全局使用
(1)将数据放到store.js中,作为全局
import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './module/moduleA.js';
import moduleB from './module/moduleB.js';
Vue.use(Vuex)
const state = {
username: '牛牛',
userState: 0,
menu_data: [],
isLoadRoute: false,
editableTabsValue: '1',
editableTabs: [
{
title: '首页',
name: '首页',
content: '首页'
},
{
title: 'Tab 1',
name: '2',
content: 'Tab 1 content'
},
{
title: 'Tab 2',
name: '3',
content: 'Tab 2 content'
}
]
}
const mutations = {
setLoadRoute(state, data) {
state.isLoadRoute = data
},
setUser(state, name) {
state.username = name
},
setUserState(state, data) {
state.userState += data
},
setMenuData(state, data) {
state.menu_data = data
},
}
const getters = {
getUserState(state) {
let data;
if (state.userState == 0) {
data = '无效'
} else {
data = state.userState + '级'
}
return data;
}
}
const modules = {
a: moduleA,
b: moduleB
}
export default new Vuex.Store({
state,
mutations,
getters,
modules
})
(2)Tabs/index.vue
中的这两个数据 editableTabsValue
和 editableTabs
就从store中获取
注意:
- 如果你的在data中这样写,可能会无法正常显示,建议用computed 方式来写。
<template>
<div>
<el-tabs v-model="editableTabsValue" type="card" closable>
<el-tab-pane
:key="item.name"
v-for="item in editableTabs"
:label="item.title"
:name="item.name"
>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
export default {
name: "Tabs",
data() {
return {};
},
computed: {
editableTabsValue: {
get() {
return this.$store.state.editableTabsValue;
},
set(val) {
this.$store.state.editableTabsValue = val;
},
},
editableTabs: {
get() {
return this.$store.state.editableTabs;
},
set(val) {
this.$store.state.editableTabs = val;
},
},
},
};
</script>
<style scoped>
div {
height: auto;
}
</style>
页面效果是一样的
2、菜单点击增加标签页
(1)在 store/index.js
中将写死的数据editableTabs 内容删除,只剩下首页的那条
(2)在mutations添加 editableTabs 数据变更的方法 addEditableTabs,因为我定义的菜单数据的时候,没有title属性,所以这里我都用title来代表
参考代码:
import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './module/moduleA.js';
import moduleB from './module/moduleB.js';
Vue.use(Vuex)
const state = {
username: '牛牛',
userState: 0,
menu_data: [],
isLoadRoute: false,
editableTabsValue: '1',
editableTabs: [
{
title: '首页',
name: '首页',
content: '首页'
}
]
}
const mutations = {
setLoadRoute(state, data) {
state.isLoadRoute = data
},
setUser(state, name) {
state.username = name
},
setUserState(state, data) {
state.userState += data
},
setMenuData(state, data) {
state.menu_data = data
},
addEditableTabs(state, tab) {
state.editableTabs.push({
title: tab.name,
name: tab.name
})
state.editableTabsValue = tab.name
}
}
const getters = {
getUserState(state) {
let data;
if (state.userState == 0) {
data = '无效'
} else {
data = state.userState + '级'
}
return data;
}
}
const modules = {
a: moduleA,
b: moduleB
}
export default new Vuex.Store({
state,
mutations,
getters,
modules
})
(3)给Aside/index.vue
菜单增加点事件,selectMenu方法
<template>
<div style="height: 100%">
<el-menu
background-color="#545c64"
text-color="#ffffff"
active-text-color="#ffd04b"
class="el-menu-vertical-demo"
router
>
<el-menu-item
:index="item.path"
v-for="item in menu_data"
:key="item.name"
@click="selectMenu(item)"
>
<i :class="item.icon"></i>{{ item.name }}
</el-menu-item>
</el-menu>
</div>
</template>
<script>
export default {
name: "Aside",
data() {
return {};
},
computed: {
menu_data: {
get() {
return this.$store.state.menu_data;
},
},
},
methods: {
selectMenu(item) {
this.$store.commit("addEditableTabs", item);
},
},
};
</script>
<style scoped>
.el-icon-location,
.el-icon-document,
.el-icon-setting {
display: inline-flex;
align-items: center;
justify-content: center;
}
</style>
页面效果
问题:出现重复菜单名称标签
3、处理重复标签
在store/index.js
文件中的addEditableTabs 中判断,如果数据中已经有了,则不重复添加,只需切换即可
页面效果
现在就只有两个标签了,目前展示正常,但是没法关闭
4、关闭标签页
标签页添加关闭事件
在
el-tabs
标签中添加@tab-remove="removeTab"
,在method添加对应的方法
<template>
<div>
<el-tabs
v-model="editableTabsValue"
type="card"
closable
@tab-remove="removeTab"
>
<el-tab-pane
:key="item.name"
v-for="item in editableTabs"
:label="item.title"
:name="item.name"
>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
export default {
name: "Tabs",
data() {
return {};
},
methods: {
removeTab(targetName) {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue = activeName;
this.editableTabs = tabs.filter((tab) => tab.name !== targetName);
},
},
computed: {
editableTabsValue: {
get() {
return this.$store.state.editableTabsValue;
},
set(val) {
this.$store.state.editableTabsValue = val;
},
},
editableTabs: {
get() {
return this.$store.state.editableTabs;
},
set(val) {
this.$store.state.editableTabs = val;
},
},
},
};
</script>
<style scoped>
div {
height: auto;
}
</style>
页面效果
问题:
- 选中菜单,侧边栏没有进行对应的高亮展示
- 关闭了“一级菜单2”,但是页面内容还停留在“一级菜单2”,其实就是路由没有变
三、点击标签页操作
问题1:点击标签页选中菜单进行高亮展示
- 在Aside/index.vue中给
el-menu
设置属性default-active
:default-active="this.$store.state.editableTabsValue"
- 将原来代码
el-menu-item
的index设置为"item.name"
:index="item.name"
- 加入路由跳转代码
因原来菜单点击,会根据index属性来跳转,index原来是path(路由地址),现在index属性更改为name了,则跳转不会生效,修改原来的router.js的代码,动态创建路由加入name属性(name:item.name),方便跳转。
修改router/index.js
代码
let oRouters = router.options.routes;
const buildRouter = ()=>{
let data = store.state.menu_data;
data.forEach(item=>{
let new_router = {
path:item.path,
name:item.name,
component:() => import('./components/'+item.component+'.vue')
}
oRouters[0].children.push(new_router);
})
router.addRoutes(oRouters)
}
在Aside/index.vue
的 selectMenu方法中,加入路由跳转代码(根据name跳转),仅需一行代码即可。
selectMenu(item){
//点击菜单跳转路由
this.$router.push({name:item.name})
this.$store.commit("addEditableTabs",item);
}
页面效果,可以实现点击标签页,菜单跟着高亮显示了
Aside/index.vue 页面代码
<template>
<div style="height: 100%">
<el-menu
background-color="#545c64"
text-color="#ffffff"
active-text-color="#ffd04b"
class="el-menu-vertical-demo"
router
:default-active="this.$store.state.editableTabsValue"
>
<el-menu-item
:index="item.name"
v-for="item in menu_data"
:key="item.name"
@click="selectMenu(item)"
>
<i :class="item.icon"></i>{{ item.name }}
</el-menu-item>
</el-menu>
</div>
</template>
<script>
export default {
name: "Aside",
data() {
return {};
},
computed: {
menu_data: {
get() {
return this.$store.state.menu_data;
},
},
},
methods: {
selectMenu(item) {
//点击菜单跳转路由
this.$router.push({ name: item.name });
this.$store.commit("addEditableTabs", item);
},
},
};
</script>
<style scoped>
.el-icon-location,
.el-icon-document,
.el-icon-setting {
display: inline-flex;
align-items: center;
justify-content: center;
}
</style>
问题2:点击标签页路由也要跳转
上面遗留的问题2,点击标签页,标签页下方的页面没有跟着跳转,下面修改这个问题
- 在Tabs页面的
el-tabs
添加tab-click
事件 - 添加方法,利用路由的name来跳转
<template>
<div>
<el-tabs
v-model="editableTabsValue"
type="card"
closable
@tab-remove="removeTab"
@tab-click="clickTab"
>
<el-tab-pane
:key="item.name"
v-for="item in editableTabs"
:label="item.title"
:name="item.name"
>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
export default {
name: "Tabs",
data() {
return {};
},
methods: {
removeTab(targetName) {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue = activeName;
this.editableTabs = tabs.filter((tab) => tab.name !== targetName);
},
clickTab(target) {
this.$router.push({ name: target.name });
},
},
computed: {
editableTabsValue: {
get() {
return this.$store.state.editableTabsValue;
},
set(val) {
this.$store.state.editableTabsValue = val;
},
},
editableTabs: {
get() {
return this.$store.state.editableTabs;
},
set(val) {
this.$store.state.editableTabs = val;
},
},
},
};
</script>
<style scoped>
div {
height: auto;
}
</style>
效果展示
四、解决bug
- 关闭菜单后,菜单对应的路由没有跟着跳转
仅需在关闭标签的方法removeTab,最后加上以下代码
this.$router.push({name:activeName})
- 首页不允许关闭
在关闭标签的方法removeTab ,执行关闭之前,加入以下代码
if(targetName=='首页'){
return ;
}
- 完整removeTab 代码
removeTab(targetName) {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
if(targetName=='首页'){
return ;
}
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue = activeName;
this.editableTabs = tabs.filter(tab => tab.name !== targetName);
this.$router.push({name:activeName})
}
- 还有一个潜藏的bug就是打开、关闭的顺序问题,先关闭前面的菜单会报错,后面解决
报错代码如下
Uncaught runtime errors:
Avoided redundant navigation to current location: "/index/menu2".
NavigationDuplicated: Avoided redundant navigation to current location: "/index/menu2".
at createRouterError (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:1720:15)
at createNavigationDuplicatedError (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:1708:15)
at HTML5History.confirmTransition (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:1946:18)
at HTML5History.transitionTo (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:1887:8)
at HTML5History.push (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:2165:10)
at eval (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:2510:22)
at new Promise (<anonymous>)
at VueRouter.push (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:2509:12)
at VueComponent.removeTab (webpack-internal:///./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/components/Tabs/index.vue?vue&type=script&lang=js:29:20)
at invokeWithErrorHandling (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2903:26)