web管理系统中使用Vuex与keep-alive将单页面改为多标签页面

web项目中使用Vuex和keep-alive 实现多标签

写在前面

web管理系统,页面越来越多,每次只能打开一个,导致比较频繁的来回切换,所以试着实现多标签页。Vue CLI默认创建的是单页面应用,一开始也没有考虑多标签的情况(以前写Java用jsp的时候倒是多标签页)。这是个0.1版本,先这样用着,后续会再查资料看看其他更好的方案,陆续优化(flag立的倒是挺快哈哈哈哈),个人存档,不喜勿喷,欢迎指教。

思路

  1. Vuex存储已打开的页面集合tags,保持缓存的白名单集合keepAliveList,当前选中页面的url currentTab
  2. Container.vue中,使用keep-alive “包裹” 页面组件,使用include 过滤需要缓存的页面
  3. 点击菜单时,push到tagskeepAliveList,关闭标签时splice tagskeepAliveList (。。。真的觉得有点蠢)
  4. 页面刷新时,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 无效的一点注意

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值