web管理系统中使用Vuex与keep-alive将单页面改为多标签页面
写在前面
web管理系统,页面越来越多,每次只能打开一个,导致比较频繁的来回切换,所以试着实现多标签页。Vue CLI
默认创建的是单页面应用,一开始也没有考虑多标签的情况(以前写Java用jsp的时候倒是多标签页)。这是个0.1版本,先这样用着,后续会再查资料看看其他更好的方案,陆续优化(flag立的倒是挺快哈哈哈哈),个人存档,不喜勿喷,欢迎指教。
思路
- Vuex存储已打开的页面集合
tags
,保持缓存的白名单集合keepAliveList
,当前选中页面的urlcurrentTab
-
Container.vue
中,使用keep-alive
“包裹” 页面组件,使用include
过滤需要缓存的页面 - 点击菜单时,push到
tags
与keepAliveList
,关闭标签时splicetags
与keepAliveList
(。。。真的觉得有点蠢) - 页面刷新时,
Vuex
数据被清,所以在Container.vue
中加了刷新的监听(。。。又是一个感觉蠢的点),用localStorage
存储
遗留的问题
菜单列表是登录时从后台获取,包括id,url,name等,但只有列表的菜单数据,没有新增页面的,新增时是直接跳转,所以缺少数据。现在只能缓存列表页的情况,当从列表页点击新增,跳转到新增页时,仍显示为列表的标签;当点击其他标签再回来时,显示列表页。
思路:
是否可以拦截或者监听到push,若是新增,则手动执行。
查到router.beforeEach((to, from, next) => {}
可以实现监听,若to
为新增页面,如果是新增,就手动push。
但又有一个问题,就是新增和修改都是push addCity
,通过携带的query
区分,query
数据怎么携带过去呢?
this.$router.push({
name: 'addCity',
query:'id=111' // 修改时有
})
效果
有点丑哈哈哈哈(。。。哈哈怪出没)
目录结构
上代码
此功能相关的地方store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import router from '@/router/index.js'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
stateSlide: true,
tabs: [], // 缓存哪些path的对象,包含path,菜单名称等参数
keepAliveList: [], // 缓存哪些path的数据,只保存path,例['bus','city']
currentTab: '' // 当前选中的标签
},
mutations: {
change (state, city) {
state.stateSlide = !state.stateSlide
},
chooseTab (state, tab) { // 点击选择标签
state.currentTab = tab
setAliveTab(state)
},
saveTab (state, item) { // 从左侧列表点击,新打开一个标签
let index = -1
state.tabs.map((val, idx) => {
if (val.lastPath == item.lastPath) {
index = idx
}
})
if (index == -1) { // 如果不存在则加入
state.tabs.push(item)
}
state.currentTab = item.lastPath
setAliveTab(state)
},
delTab (state, item) { // tabs点删除
state.tabs.forEach((val, index) => {
if (val.menuId == item.menuId && state.tabs.length > 1) { // 找到要删除的path,至少留一个
state.tabs.splice(index, 1)
// 如果删除的是当前的path,并且还有其他标签的话,则将最后一个path设置为当前页面
if (state.tabs.length > 0 && state.currentTab == val.lastPath) {
let endTab = state.tabs[state.tabs.length - 1]
state.currentTab = endTab.lastPath
router.push(endTab.lastPath)
}
setAliveTab(state)
}
})
}
}
})
function setAliveTab (state) { // 缓存列表限制刷新
state.keepAliveList = []
state.tabs.forEach(item => {
state.keepAliveList.push(item.lastPath)
})
}
export default store
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/pages/login/login'
import store from '@/store/index.js'
Vue.use(Router)
const router = new Router({
routes: [
{ path: '/', component: Login }
]
})
export default router
Container.vue
重点<keep-alive>
<template>
<div class="container">
<div class="header">
<Header></Header>
</div>
<section class="main">
<div class="slider" v-show="stateSlide" :style="{width:isCollapse? '60px': '200px', background: '#1E232B'}">
<img src="../assets/images/openMenu.png" class="openMenu" alt="" @click="changeCollapse" v-if="isCollapse">
<img src="../assets/images/retractMenu.png" class="retractMenu" alt="" @click="changeCollapse" v-else>
<Aside @clickItem="sliderItemClick" :isCollapse="isCollapse"></Aside>
</div>
<div class="main-body">
<div class="bread">
<span class="bread-title gray">{{bread.parentTitle}}</span>
<span class="bread-separator">/</span>
<span class="bread-title">{{bread.title}}</span>
</div>
<div class = "main-tags">
<el-tag class= "main-tag" :key="tag.lastPath" v-for="tag in tabs" closable
:effect="currentTab == tag.lastPath ? 'dark' : 'plain'"
:disable-transitions="false" @click="choose(tag)" @close="handleClose(tag)">
<router-link :to="tag.lastPath">{{tag.menuName}}</router-link>
</el-tag>
</div>
<transition >
<keep-alive :include="keepAliveList">
<router-view class="main-body-box" ></router-view>
</keep-alive>
</transition>
</div>
</section>
</div>
</template>
<script>
import { Header, Aside, Tabnav,Worktab } from './common'
import { mapState } from 'vuex'
export default ({
name: 'Container',
components: { Header, Aside, Tabnav , Worktab },
computed: {
...mapState(['stateSlide','tabs','currentTab','keepAliveList'])
},
data () {
return {
bread: '',
isCollapse: false
}
},
watch: {
$route() {
this.getBreadcrumb()
}
},
methods: {
sliderItemClick (item) {
this.$refs.tabNav.addList(item)
},
getBreadcrumb() {
if (this.$route.meta.title != undefined) {
this.bread = this.$route.meta;
}
},
choose(item){
this.$store.commit("chooseTab",item.lastPath)
// console.log(keepAliveList)
},
changeCollapse () {
this.isCollapse = !this.isCollapse
},
handleClose(item){ // 关闭标签
this.$store.commit("delTab",item)
}
},
created() {
this.getBreadcrumb();
//在页面刷新时将vuex里的信息保存到localStorage里
window.addEventListener("beforeunload",()=>{
localStorage.setItem("messageStore",JSON.stringify(this.$store.state))
})
//在页面加载时读取localStorage里的状态信息
localStorage.getItem("messageStore") && this.$store.replaceState(Object.assign(this.$store.state,JSON.parse(localStorage.getItem("messageStore"))));
}
})
</script>
<style scoped>
.container {
width: 100%;
height: 100%;
overflow: hidden;
/*display: flex;*/
}
.header {
width: 100%;
overflow: hidden;
height: 40px;
}
.nav-list-tag {
cursor: pointer;
}
.wrapper-nav-list {
height: 38px;
overflow: hidden;
padding: 0 20px;
}
.bread {
background-color: #fff;
border-top-right-radius: 6px;
border-top-left-radius: 6px;
padding: 4px 0 14px;
/* border-bottom: 1px solid rgba(216, 216, 216, 0.5); */
/* margin-bottom: 15px; */
}
.bread-separator {
margin: 0 10px;
}
.bread-title {
font-size: 14px;
font-weight: 600;
}
.gray {
color: #090b0e;
opacity: 0.4;
}
.main-body-box {
/*flex:1;*/
height: 85%;
background-color: #fff;
position: relative;
overflow-y: auto;
}
.main-body {
overflow: hidden;
background-color: #fff;
margin: 16px;
/* padding: 10px 20px; */
flex: 1;
}
.main {
flex: 1;
height: 100%;
overflow: hidden;
display: flex;
position: relative;
}
/*菜单栏展开收起*/
.openMenu,.retractMenu{
position: fixed;
top: 50%;
left: 51px;
margin-top: -25px;
z-index: 99;
width: 10px;
height: 50px;
}
.retractMenu{
left: 191px;
}
.slider {
height: 100%;
}
.changeRotate {
transform: rotate(180deg);
}
.main-tags{
border-bottom: 1px solid rgba(216, 216, 216, 0.5);
margin-bottom: 15px;
padding: 4px 0 14px;
}
.main-tag{
margin:0 4px;
font-size: 16px;
}
.tag-active{
color: blue
}
</style>
aside.vue
菜单点击时触发
choose(item){
this.$store.commit("saveTab",item)
},
参考了以下:
vue-router 实现组件的缓存之 keep-alive
Vue + Elementui实现多标签页共存的方法
使用 keep-alive 的 include 和 exclude 无效的一点注意
上一篇: Unity3D性能优化总结
下一篇: 还想蒙我