1. Realize the effect
2. Implementation principle
vuex, realizes the state management of the current active item, the current tab list, the translateX of the current tab, the current cache page, and the current route.
Save the data in vuex to sessionStorage to avoid page refresh loss, and clear the data when the browser is closed.
Through ref positioning, get the current window width and all the widths of the tab tags of the current route, judge the two, and realize the processing of multiple tabs exceeding the window width.
When the tab page is clicked, the corresponding activation item is obtained, and the selected state of the left menu bar is dynamically realized, and the watch is used to monitor, updateActiveName and updateOpened.
When the tab tag is closed, splice deletes the current tab, and if it is the last item deleted, jumps to the previous page of the item. When the length is 1, jump to the home page.
3. Main code
store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
catch_components: [],
activePath: '/index',
openNames: [],
activeName: "",
tranx: "-0",
tabList: [
{ path: '/index', label: '首页', name: '首页' }
]
},
mutations: {
//清空vuex数据
clearTabs(state) {
state.catch_components = []
state.activePath = '/homepage'
state.openNames = []
state.activeName = ""
state.tranx = "-0"
state.tabList = [
{ path: '/homepage', label: '首页', name: 'home' }
]
},
// 跳转页面执行
selectMenu(state, submenu) {
var activePath = submenu.path
var oldTabList = state.tabList
var result = oldTabList.some(item => {
if (item.path === activePath) {
return true
}
})
if (!result) {
oldTabList.push({
path: submenu.path,
name: submenu.name,
label: submenu.label,
index: submenu.index,
subName: submenu.subName
})
}
state.activePath = activePath
state.tabList = oldTabList
state.activeName = submenu.subName + "-" + submenu.index
state.openNames = [submenu.subName]
},
// 添加keepalive缓存
addKeepAliveCache(state, val) {
if (val === '/homepage') {
return
}
if (state.catch_components.indexOf(val) === -1) {
state.catch_components.push(val)
}
},
// 删除keepalive缓存
removeKeepAliveCache(state, val) {
let cache = state.catch_components
for (let i = 0; i < cache.length; i++) {
if (cache[i] === val) {
cache.splice(i, 1);
}
}
state.catch_components = cache
},
setTranx(state, val) {
console.log(val)
state.tranx = val
},
//关闭菜单
closeTab(state, val) {
state.activePath = val.activePath
state.tabList = val.tabList
state.openNames = val.openNames
state.activeName = val.activeName
},
// 点击标签选择菜单
changeMenu(state, val) {
state.activePath = val.path
state.activeName = val.subName + "-" + val.index
state.openNames = [val.subName]
}
},
})
page code
computed: {
...mapState({
activePath: (state) => state.activePath, // 已选中菜单
tabList: (state) => state.tabList, // tags菜单列表
catch_components: (state) => state.catch_components, // keepalive缓存
openNames: (state) => state.openNames,
activeName: (state) => state.activeName,
tranx: (state) => state.tranx,
}),
},
watch: {
openNames() {
this.$nextTick(() => {
this.$refs.asideMenu.updateOpened();
});
},
activeName() {
this.$nextTick(() => {
this.$refs.asideMenu.updateActiveName();
});
},
},
handleClose(tab, index) {
var oldOpenNames = this.$store.state.openNames,
oldActiveName = this.$store.state.activeName,
oldActivePath = this.$store.state.activePath,
oldTabList = this.$store.state.tabList;
let length = oldTabList.length - 1;
for (let i = 0; i < oldTabList.length; i++) {
let item = oldTabList[i];
if (item.path === tab.path) {
oldTabList.splice(i, 1);
}
}
// 删除keepAlive缓存
this.$store.commit("removeKeepAliveCache", tab.path);
if (tab.path !== oldActivePath) {
return;
}
if (length === 1) {
this.$store.commit("closeTab", {
activePath: "/index",
tabList: oldTabList,
});
this.$router.push({ path: oldTabList[index - 1].path });
return;
}
if (index === length) {
oldActivePath = oldTabList[index - 1].path;
oldOpenNames = [oldTabList[index - 1].subName];
oldActiveName =
oldTabList[index - 1].subName + "-" + oldTabList[index - 1].index;
this.$store.commit("closeTab", {
activePath: oldActivePath,
tabList: oldTabList,
openNames: oldOpenNames,
activeName: oldActiveName,
});
this.$router.push({ path: oldTabList[index - 1].path });
} else {
oldActivePath = oldTabList[index].path;
oldOpenNames = [oldTabList[index].subName];
oldActiveName =
oldTabList[index].subName + "-" + oldTabList[index].index;
this.$store.commit("closeTab", {
activePath: oldActivePath,
tabList: oldTabList,
openNames: oldOpenNames,
activeName: oldActiveName,
});
this.$router.push({ path: oldTabList[index].path });
}
this.getTrans(2);
},
changeMenu(item) {
var oldActivePath = this.$store.state.activePath;
if (oldActivePath === item.path) {
return;
}
this.$store.commit("changeMenu", item);
this.$router.push({ path: item.path });
this.$nextTick(() => {
this.getTrans(0);
});
},
selectMenu(item, i, subName) {
// 加入keepalive缓存
this.$store.commit("addKeepAliveCache", item.path);
var submenu = {
path: item.path,
name: item.title,
label: item.title,
index: i,
subName: subName,
};
this.$store.commit("selectMenu", submenu);
this.$router.push({ path: item.path });
this.$nextTick(() => {
this.getTrans(0);
});
},
getTrans(e) {
let width = 0;
if (this.$refs.tags) {
width = this.$refs.tags.clientWidth;
}
this.tabList.map((item, index) => {
if (item.path === this.activePath) {
this.currentIndex = index;
}
if (this.$refs[`tag${index}`] && this.$refs[`tag${index}`][0]) {
this.$set(
this.tabList[index],
"width",
this.$refs[`tag${index}`][0].$el.clientWidth + 4
);
}
});
let list = this.tabList.filter((item, index) => {
return index <= this.currentIndex;
});
let totalWidth = list.reduce((total, currentValue) => {
return total + Number(currentValue.width);
}, 0);
let totalAllWidth = this.tabList.reduce((total, currentValue) => {
return total + Number(currentValue.width);
}, 0);
if (e == 0) {
if (Number(width) > Number(totalWidth) || Number(width) == 0) {
this.setTranx(-0);
return false;
}
this.setTranx(Number(width) - Number(totalWidth) - 60);
} else if (e == 1) {
if (Number(width) > Number(totalAllWidth)) {
return false;
}
this.setTranx(Number(width) - Number(totalAllWidth) - 60);
} else {
if (
Number(width) > Number(totalAllWidth) &&
this.$store.state.tranx < 0
) {
this.setTranx(-0);
}
}
},
setTranx(val) {
this.$store.commit("setTranx", val);
},
<Menu
ref="asideMenu"
:active-name="activeName"
:open-names="openNames"
accordion
theme="light"
:style="{ width: 'auto' }"
:class="isCollapsed ? 'collapsed-menu' : 'menu-item'"
>
<MenuItem
@click.native="selectMenu({ path: '/index', title: '首页' })"
name="index"
key="index"
>
<Icon type="ios-paw"></Icon>
<span class="menuTitle">首页</span>
</MenuItem>
<Submenu
v-for="(item, index) in menuMap"
:name="index"
:key="index"
class="sub_title"
>
<template slot="title">
<svg class="icon" aria-hidden="true" v-if="item.fonticon">
<use :xlink:href="item.fonticon"></use>
</svg>
<Icon :type="item.icon" v-else />
<span class="menuTitle">{
{ item.title }}</span>
</template>
<template v-if="item.children">
<MenuItem
v-for="(each, i) in item.children"
:name="index + '-' + i"
:key="index + '-' + i"
@click.native="selectMenu(each, i, index)"
><span class="menuTitle">{
{ each.title }}</span>
</MenuItem>
</template>
</Submenu>
</Menu>
<Row class="head-tags">
<div class="head-left left" @click="setTranx(0)">
<Icon type="ios-rewind" size="30" color="#ffc0cb" />
</div>
<div class="tags-box">
<div
ref="tags"
class="tags-box-scroll"
:style="{ transform: `translateX(${tranx}px)` }"
>
<Tag
:ref="'tag' + index"
class="tags-item"
:class="{ 'tags-item-active': activePath === item.path }"
v-for="(item, index) in tabList"
:key="index"
:name="item.path"
:closable="item.path !== '/index'"
@click.native="changeMenu(item)"
@on-close="handleClose(item, index)"
>{
{ item.label }}</Tag
>
</div>
</div>
<div class="head-left right" @click="getTrans(1)">
<Icon type="ios-fastforward" size="30" color="#ffc0cb" />
</div>
</Row>