欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-06 21:00:50
...

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' // 修改时有
        })

效果

有点丑哈哈哈哈(。。。哈哈怪出没)
web管理系统中使用Vuex与keep-alive将单页面改为多标签页面

目录结构

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

上代码

此功能相关的地方
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 无效的一点注意

相关标签: 优化