vue点击菜单实现多标签页tab,打开关闭多个页面(右击关闭功能)
vue点击菜单实现多标签页tab,打开关闭多个页面(右击关闭功能)
·
1.页面显示效果
2.App.vue代码
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: "",
data() {
return {};
},
created() {
// 防止页面刷新,vuex里面的菜单数据消失
if (sessionStorage.getItem("visitedMenus")) {
this.$store.commit(
"setVisitedMenus",
JSON.parse(sessionStorage.getItem("visitedMenus"))
);
this.$store.commit(
"setVisitedMenusName",
JSON.parse(sessionStorage.getItem("menuNames"))
);
}
window.addEventListener("beforeunload", () => {
sessionStorage.setItem(
"visitedMenus",
JSON.stringify(this.$store.state.visitedMenus)
);
sessionStorage.setItem(
"menuNames",
JSON.stringify(this.$store.state.menuNames)
);
});
},
watch: {
$route() {
this.addRouteMenu();
},
},
methods: {
addRouteMenu() {
const route = this.$route;
this.$store.commit({
type: "addRouteMenus",
route,
});
},
},
};
</script>
3.store关于vuex的js文件代码
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import storage from "@/api/storage.js"
import { changeMeta } from "@/router/common.js";
const store = new Vuex.Store({
state: {
visitedMenus: [],
menuNames: []
},
mutations: {
//退出登录状态时
logout(state) {
storage.removeStorage()
state.visitedMenus = []
state.menuNames = []
},
setVisitedMenus(state, value) {
state.visitedMenus = value
},
setVisitedMenusName(state, value) {
state.menuNames = value
},
addRouteMenus(state, routeMenu) {
if (routeMenu.route.path != '/login' && routeMenu.route.path != '/register') {
let hasMenu = state.visitedMenus.some(item => item.path == routeMenu.route.path)//判断此路由是否在已存储的路由数据中
if (!hasMenu) {
// 如果存储的路由数据中,没有此路由就添加
state.visitedMenus.push(Object.assign({}, {
path: routeMenu.route.path,
titleText: routeMenu.route.meta.titleText,
query: routeMenu.route.query,
}))
state.menuNames.push(routeMenu.route.name)
changeMeta(routeMenu.route.path, "saveKeep"); //保持数据缓存应该在路由导航前设置,否则还会保持原来的数据。好像可以删除没啥用
}
}
},
},
actions: {
closeMenu({ commit, state }, routeMenu) {
let deletekey;
for (const [key, item] of state.visitedMenus.entries()) {
if (item.path == routeMenu.page.path) {
deletekey = key;
state.visitedMenus.splice(key, 1);
state.menuNames.splice(key, 1);
break;
}
}
return Promise.resolve({
visitedMenus: state.visitedMenus,
deletekey: deletekey,
deletPath: routeMenu.page.path,
})
},
closeAllMenu({ commit, state },) {
// 删除所有,只保留第一个
let firstPage = state.visitedMenus.slice(0, 1)
state.visitedMenus.splice(1);
state.menuNames.splice(1);
return Promise.resolve(firstPage[0])
},
closeRightMenu({ commit, state }, page) {
// 删除所有,只保留第一个
let index = state.visitedMenus.findIndex(v => v.path == page.clikedMenu.path) + 1
if (index > 0 && index < state.visitedMenus.length) {
state.visitedMenus.splice(index);
state.menuNames.splice(index);
}
return Promise.resolve({})
}
}
})
export default store
4.router文件下的common.js代码
import router from "@/router/index.js";
export const changeMeta = (nowPath, fromType) => {
let firstModule = nowPath.split("/")[1];
let routes = router.options.routes;
let firstPathIndex = routes.findIndex((v) => v.path.indexOf(firstModule) != -1);
// 获取路由里面的二级path
let secondModule = nowPath.split("/")[2];//路由只能有一个/代表的内容,否则失效
let secondPathIndex = routes[firstPathIndex].children.findIndex((v) => v.path == secondModule);
if (fromType == 'cancelKeep') {
// 点击左侧栏或者最顶部headr
router.options.routes[firstPathIndex].children[secondPathIndex].meta.keepAlive = false
} else if (fromType == 'saveKeep') {
router.options.routes[firstPathIndex].children[secondPathIndex].meta.keepAlive = true
}
}
5.router文件下的index.js是路由文件。部分代码示例如下
{
// 系统管理模块
path: '/system',
name: 'S',
component: SystemManagement,
children: [{
path: 'user',
name: 'S01',
component: User,
meta: { titleText: '用户列表', keepAlive: true }
}
]
}
6.顶部菜单代码。@/api/storage.js该文件主要是本地存存储的方法调用,可自行写
<template>
<div>
<div class="header">
<el-menu
:default-active="curr_menu"
class="el-menu-demo"
mode="horizontal"
text-color="#8896C5"
active-text-color="#1761fd"
:router="true"
>
<el-menu-item index="/system">系统管理</el-menu-item>
</el-menu>
<div class="headerRight">
<p class="userName">{{ userInfo.nickName }}</p>
<el-avatar :size="50">{{ userInfo.username }}</el-avatar>
<p class="logout" @click="logout">退出</p>
</div>
</div>
<div>
<div class="m-tab-container">
<div>
<router-link
class="m-tab-item"
:to="item"
:key="item.path"
:class="isActive(item) ? 'active' : ''"
v-for="item in Array.from(visitedMenus)"
@click.middle.native="closeSelectedMenu(item)"
@contextmenu.prevent.native="openMenu(item, $event)"
>
{{ item.titleText }}
<span
class="m-icon-close el-icon-close"
@click.prevent.stop="closeSelectedMenu(item)"
></span>
</router-link>
</div>
<ul
v-show="visible"
:style="{ left: left + 'px', top: top + 'px' }"
class="contextmenu"
>
<!-- <li @click="refreshSelectedTag(selectedMenu)">刷新</li> -->
<li @click="closeSelectedMenu(selectedMenu)">关闭</li>
<!-- <li @click="closeRightMenus()">关闭右边</li> -->
<li @click="closeAllMenus(selectedMenu)">全部关闭</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import storage from "@/api/storage.js";
import { changeMeta } from "@/router/common.js";
export default {
name: "Header",
data() {
return {
curr_menu: "",
userInfo: {},
visitedMenus: [],
visible: false,
top: 0,
left: 0,
selectedMenu: {},
};
},
created() {
this.visitedMenus = this.$store.state.visitedMenus;
this.matchRoute();
this.userInfo = storage.getUser();
},
watch: {
$route() {
this.matchRoute();
},
visible(value) {
if (value) {
document.body.addEventListener("click", this.closeMenu);
} else {
document.body.removeEventListener("click", this.closeMenu);
}
},
},
methods: {
//把当前地址栏路由跟侧导航栏匹配
matchRoute() {
const path = this.$route.matched[0].path;
switch (path) {
case "/system":
this.curr_menu = "/system";
break;
}
},
logout() {
this.$store.commit("logout");
this.$router.push("/login");
},
isActive(route) {
// 判断当前地址栏地址,并激活样式
return route.path == this.$route.path;
},
closeSelectedMenu(page) {
if (this.visitedMenus.length <= 1) {
this.$message.warning("请至少保留一个!");
return false;
}
this.$store.dispatch({ type: "closeMenu", page }).then((pages) => {
let deletekey = pages.deletekey;
let visitedMenus = pages.visitedMenus;
let deletPath = pages.deletPath;
if (deletPath) {
changeMeta(deletPath, "cancelKeep"); //取消数据缓存好像可以删除没啥用
}
if (this.isActive(page)) {
// slice() 方法可从已有的数组中返回选定的元素。
// slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。
// 注意: slice() 方法不会改变原始数组。
let latestPage;
if (visitedMenus.length != 0) {
// 删除最后一个,默认前一个。不是最后一个,默认后一个
if (deletekey == visitedMenus.length) {
latestPage = visitedMenus.slice(-1)[0];
} else {
latestPage = visitedMenus.slice(deletekey, deletekey + 1)[0];
}
this.$router.push(latestPage);
} else {
// 已删除完,默认登陆成功后的页面。由于动态设置的接口返回导航数据,没有中文名称,不做限制
this.$message.warning("请至少保留一个!");
}
}
});
},
openMenu(tag, e) {
const menuMinWidth = 320;
const offsetLeft = this.$el.getBoundingClientRect().left; // container margin left
const offsetWidth = this.$el.offsetWidth; // container width
const maxLeft = offsetWidth - menuMinWidth; // left boundary
const left = e.clientX - offsetLeft + 230; // 15: margin right
if (left > maxLeft) {
this.left = maxLeft;
} else {
this.left = left;
}
this.top = e.clientY;
this.visible = true;
this.selectedMenu = tag;
},
refreshSelectedTag(page) {
// this.$nextTick(() => {
// this.$router.replace({
// path: page.path,
// path: '/redirect' + fullPath //?/redirect
// });
// });
},
closeRightMenus(clikedMenu) {
let selectedMenu = this.selectedMenu;
this.$store
.dispatch({ type: "closeRightMenu", clikedMenu })
.then((page) => {
// let deletekey = pages.deletekey;
// let visitedMenus = pages.visitedMenus;
// let deletPath = pages.deletPath;
// if (deletPath) {
// changeMeta(deletPath, "cancelKeep"); //取消数据缓存好像可以删除没啥用
// }
let visitedMenus = this.$store.state.visitedMenus;
let index = visitedMenus.findIndex(v.path == selectedMenu.path);
if (index < 0) {
this.$router.push(visitedMenus.slice(-1)[0]);
} else {
}
this.visitedMenus = visitedMenus;
});
},
closeAllMenus(view) {
this.$store.dispatch({ type: "closeAllMenu" }).then((page) => {
changeAllMeta(page.path, "cancelKeep"); //取消数据缓存好像可以删除没啥用
this.visitedMenus = this.$store.state.visitedMenus;
if (!this.isActive(page)) {
this.$router.push(page);
}
});
},
closeMenu() {
this.visible = false;
},
},
};
</script>
<style rel="stylesheet/scss" lang="scss">
.m-tab-container {
background: #fff;
height: 45px;
border-top: 1px solid #d8dce5;
border-bottom: 1px solid #d8dce5;
overflow: hidden;
white-space: nowrap;
& > div {
overflow: auto;
overflow-y: hidden;
}
a.m-tab-item {
text-decoration: none;
}
.m-tab-item {
display: inline-block;
position: relative;
height: 32px;
line-height: 32px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 14px;
margin-left: 5px;
margin-top: 6px;
&:first-of-type {
margin-left: 18px;
}
&.active {
background-color: #1c5ffd;
color: #fff;
border-color: #1c5ffd;
&::before {
content: "";
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 2px;
}
}
.m-icon-close {
width: 20px;
height: 20px;
vertical-align: 2px;
border-radius: 50%;
text-align: center;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transform-origin: 100% 50%;
&:before {
display: inline-block;
vertical-align: -3px;
}
&:hover {
background-color: #b4bccc;
color: #fff;
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}
</style>
7.布局文件重要代码
<el-main>
<keep-alive :include="menuNames">
<router-view
v-if="$route.meta.keepAlive"
:key="$route.fullPath"
></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
</el-main>
export default {
name: "",
components: {
},
data() {
return { menuNames: this.$store.state.menuNames };
},
created() {},
computed: {},
};
</script>
8.警告js路由文件中的name值要和vue文件中的name值一样
更多推荐
已为社区贡献4条内容
所有评论(0)