侧栏与标签页的联动效果在后台管理中很常用,这个是以element ui为基础写的标签页与侧边导航联动的项目部分。
在整体布局layout页面中,测栏,其中最重要的两个参数default-active设为$router.path,则它会根据路由来激活样式,index设为遍历数组的url,跟路由地址一一匹配。
思路是遍历导航栏数组,在数组中有标题,图标,路由地址等信息,与路由信息相对应,点击每个导航项目时会将该条数组项目添加到store里,删除则反之。这主要是一级路由的列表,那么其他页面的标签我是通过监听路由,在里面包不包含,不包含就从当前的路由元信息里取。
layout.vue文件
<template>
<div class="layoutContent">
<el-menu :default-active="$route.path" :router="true" active-text-color="#409eff" class="el-menu-vertical-demo"
:class="{ showMenu: isCollapse }" mode="vertical">
<!-- 因为要一直开着首页标签,所以我写死了 -->
<el-menu-item @click="$router.push({ path: '/' })" index="/">
<i class="el-icon-menu"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-menu-item v-for="(item, index) in list1" :key="item.title" :index="item.url"
@select="menuSelect(index, indexPath)" @click="mainToPath(item)">
<i :class="`el-icon-${item.icon}`"></i>
<span slot="title">{{ item.title }}</span>
</el-menu-item>
</el-menu>
<div class="layout">
<div class="navbar">
<i class="el-icon-s-unfold" @click="unfold"></i>
<div class="user">
<i class="el-icon-refresh" style="font-size: 20px" @click="refresh()"></i>
<el-image style="width: 30px; height: 30px; border-radius: 50%"
src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"></el-image>
<el-row class="block-col-2">
<el-col :span="12">
<el-dropdown trigger="click">
<span class="el-dropdown-link">
<i class="el-icon-arrow-down" style="font-size: 16px; margin-left: 10px"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(item, index) in list2" :icon="`el-icon-${item.icon}`"
@click.native="setting(index)" :key="item.title">{{ item.title
}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
</el-row>
</div>
</div>
<Head-Tag></Head-Tag>
<el-dialog title="提示" :visible.sync="dialogVisible" width="90%">
<span>确认退出吗</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="mini">取 消</el-button>
<el-button type="primary" size="mini" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
<router-view v-if="isRresh"></router-view>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
//tag组件
import HeadTag from "@/components/headTag.vue";
export default {
components: {
HeadTag,
},
data() {
return {
isCollapse: false,
isRresh: true,
dialogVisible: false,
index: "",
list1: [
{ title: "", icon: "menu", url: "/***" },
{ title: "", icon: "document", url: "/***" },
{ title: "", icon: "setting", url: "" },
],
};
},
watch: {
["$route.path"]: function (newVal, olderVal) {
if (newVal === "/") return;
if (newVal === "/editUser")
this.$route.meta.title = JSON.parse(
window.sessionStorage.getItem("title")
).title;
for (let j = 0; j < this.list1.length; j++) {
// 若上面列表里没有包含当前的路由信息,则从当前路由元信息里取
if (newVal && !this.list1[j].url.includes(newVal)) {
this.$store.commit("mutationSelectTags", this.$route.meta);
}
}
},
["$store.state.stateTagsList"]: function (newVal, olderVal) {
// 若tag中的数据条为0,就跳转到首页
if (newVal.length === 0) {
this.$router.push({ path: "/" }).catch((err) => { });
}
},
},
methods: {
mainToPath(value) {
// 一级主跳转并选择tag
this.$store.commit("mutationSelectTags", value);
this.$router.push({ path: value.url }).catch((err) => { });
},
unfold() {
// 侧栏开关
this.isCollapse = !this.isCollapse;
},
...mapState({ state: "state" }),
...mapMutations({
closeAll: "mutationCloseAllTag",
}),
setting(index) {
//设置小功能,可有可无
if (index === 0) {
// 关闭全部tag
this.closeAll();
}
if (index === 1) {
if (this.$router.history.current.path === "/") {
this.$message("您已在首页了!");
return;
}
this.$router.push({ path: "/" }).catch((err) => { });
}
if (index === 2) this.dialogVisible = true;
},
},
};
</script>
<style>
.el-tabs__content {
display: none;
}
</style>
<style lang="scss" scoped>
.el-menu-item {
padding-left: 1vh !important;
}
.layoutContent {
display: flex;
.el-menu-vertical-demo {
width: 0px;
overflow: hidden;
transition: all 0.3s;
}
.showMenu {
width: 118px;
}
}
.layout {
background-color: rgb(247, 248, 250);
height: 100vh;
width: 70%;
// flex-shrink: 1;
flex-grow: 1;
.tabs {
background-color: white;
margin-top: 2px;
}
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 50px;
background-color: white;
font-size: 25px;
padding: 0 20px;
box-sizing: border-box;
}
.user {
display: flex;
align-items: center;
}
.el-icon-refresh {
margin-right: 10px;
}
}
.el-dropdown-link {
cursor: pointer;
color: #409eff;
}
.el-icon-arrow-down {
font-size: 12px;
}
.demonstration {
display: block;
color: #8492a6;
font-size: 14px;
margin-bottom: 20px;
}
::v-deep .el-tabs {
background-color: #fff;
}
::v-deep .el-tabs--card>.el-tabs__header .el-tabs__item {
background-color: #fff;
border-radius: 0px 0px 10px 10px !important;
}
::v-deep .el-tabs--card>.el-tabs__header .el-tabs__nav,
::v-deep .el-tabs--card>.el-tabs__header {
border: none !important;
background-color: #eaf4fe !important;
}
::v-deep .el-tabs--card>.el-tabs__header .el-tabs__item.is-active {
border: none !important;
border-radius: 10px 10px 0px 0px !important;
background: transparent !important;
}
</style>
tags.vue文件
<template>
<div class="app-tag">
<el-tag size="medium" :disable-transitions="true" :effect="$route.path === '/' ? 'dark' : 'plain'" :hit="true"
@click="$router.push({ path: '/' })">
<i class="el-icon-custom"></i>
首页
</el-tag>
<el-tag closable size="medium" v-for="(tag, index) in tags" :key="index" :disable-transitions="true"
:effect="$route.path === tag.url ? 'dark' : 'plain'" :hit="true" @close="handleClose(tag, index)"
@click="handleClick(tag)">
<i :class="`el-icon-${tag.icon}`"></i>
{{ tag.title }}
</el-tag>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
data() {
return {
tags: [],
}
},
created() {
//stateTagsList的值默认为空数组
this.tags = this.stateTagsList;
},
computed: {
...mapState(['stateTagsList'])
},
mounted() {
// 返回时删除二级tags目录
let _this = this
window.addEventListener("popstate", function (e) {
_this.closeCurrent()
}, false);
},
methods: {
...mapMutations({
close: 'mutationCloseTag',
closeCurrent: 'mutationCloseCurrentTag'
}),
handleClose(tag, index) {
this.close(tag) // 删除当前tag
if (this.$router.path === tag?.url) { // 如果关闭的标签不是当前路由的话,不做路由跳转
return
} else {
if (index === (this.tags.length - 1)) { // 关闭最后一个标签,则路由跳转至最后一个
this.$router.push({ path: this.tags[index]?.url })
} else { // 路由跳转至下一个标签页
if (index === 0) {
this.$router.push({ path: this.tags[0]?.url })
} else {
this.$router.push({ path: this.tags[index - 1]?.url })
}
}
}
},
// 点击tags具体标签
handleClick(tag) {
this.$router.push({ path: tag?.url }).catch(err => { })
}
}
}
</script>
<style lang="scss" scoped>
.app-tag {
padding: 0 10px;
margin-bottom: 1vh;
display: flex;
align-items: center;
flex-wrap: nowrap;
overflow-x: scroll;
padding-bottom: 4px;
.el-tag {
cursor: pointer;
border: 0;
border-radius: 0 0 10px 10px;
background-color: #fff;
color: black;
::v-deep .el-tag__close {
color: black;
}
}
.el-tag--dark {
border-radius: 0;
background-color: #eaf4fe;
color: #409eff;
::v-deep .el-tag__close {
color: #409eff;
}
}
}</style>
store文件
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
stateTagsList: [],
},
getters: {},
mutations: {
mutationSelectTags(state, data) {
// 选择tag标签
let result = false;
for (let i = 0; i < state.stateTagsList.length; i++) {
if (state.stateTagsList[i].url === data.url) {
return (result = true);
}
}
result === false ? state.stateTagsList.push(data) : "";
},
mutationCloseTag(state, data, _router) {
// 单击关闭tag标签
let result = state.stateTagsList.findIndex(
(item) => item.url === data.url
);
state.stateTagsList.splice(result, 1);
},
mutationCloseAllTag(state, data) {
// 关闭全部tag
state.stateTagsList.splice(0, state.stateTagsList.length);
},
mutationCloseCurrentTag(state, data) {
// 返回时关闭一个tag
state.stateTagsList.splice(-1, 1);
},
},
actions: {},
modules: {},
});