点击多个tab标签打开关闭多个页面

需求

现将页面分为Header LeftSideBar Main三大模块 左侧LeftSideBar为menu菜单,点击菜单每一项,在Main中出现上部为tag标签,下部为内容 可打开多个tag标签 ,可内容切换 ,可关闭

效果图

1.router.js中(在LeftSideBar组件中现在有两个菜单项icons和tabs)

{
      path:'/addtab',
      redirect:'/addtab/index',//重定向 输入/addtab 地址会变为/addtab/index
      component: Main,
      children:[
        {
          path:'index',//当addtab/index匹配成功时 TabContent会被渲染在Main中的router-view中
          name:'TabContent',
          component:()=>import("@/components/TabContent")
        }
      ]
},
{
      path:'/addicon',
      redirect:'/addicon/index',
      component: Main,
      children:[
        {
          path:'index',
          name:'IconContent',
          component:()=>import("@/components/IconContent")
        }
      ]
}

请戳--动态组件嵌套路由

2.this.$router.push({name:"TabContent",params:{}})  实现点击左侧tab  打开组件main  

3.在main中

<template>
  <div>
    <TagsView/>
    <router-view></router-view>
  </div>
</template>

4.在TagsView中

<template>
  <div class="tags-view-wrapper">
    <router-link class="tags-view-item" :to="item" :key="item.path" :class="isActive(item)?'active':''" v-for="(item) in Array.from(visitedViews)">
      {{item.params.name}}
      <span class='el-icon-close' @click.prevent.stop='closeSelectedTag(item)'></span>
    </router-link>
  </div>
</template>

a.添加标签的方法

visitedViews是存放路由信息的数组

 
addTags(){
     const route=this.$route;//获取地址栏路由
     this.$store.commit({
     type:'addTags',
     route
 })
}
在store.js中
addTags(state, payload) {
      let flag = state.visitedTags.some(
        item => item.path === payload.route.path
      );//打开标签后,判断数组中是否已经存在该路由
      if (!flag) {
        state.visitedTags.push(
          Object.assign(
            {},
            {
              path: payload.route.path,
              name: payload.route.name,
              params: payload.route.params
            }
          )
        );
      } //数组中路由存在不push ,单击左侧路由变化,点击标签路由变化均触发
    } //添加标签

第一次点击是在mountd中触发addTags方法,后来的每次点击路由均会变化 ,使用watch监听触发

 watch:{
      $route(){
        this.addTags();
      }//地址栏变化了就触发这个添加方法
    }

b.关闭标签

 在store.js中   

 closeTags(state, payload) {
      for (const [key, item] of state.visitedTags.entries()) {
        if (item.path === payload.view.path) {
          state.visitedTags.splice(key, 1);
          break;
        }
      }
    } //如果要关闭的标签在路由中存在则删除     

 在tagviews中

     isActive(route) {
        return route.path === this.$route.path
      },//当前地址栏路径是否与渲染的路径相同 样式匹配
      closeSelectedTag(view){
         this.$store.dispatch({
           type:"closeTags",
           view
         }).then((views)=>{
            此时的views是指的被删除后的visitedViews数组中存在的元素
           if (this.isActive(view)) {
            当前关闭的标签是否是被选中的标签
             const latestView = views.slice(-1)[0];
             if (latestView) {
               this.$router.push(latestView);//如果数组不为空则让选中的标签为紧邻关闭标签的那一个
             } else {
               this.$router.push('/') ;//如果为空则页面跳转到/
             }
           }
         })
      }

说一下思路

点击左侧的每一项都会打开一个组件(对应一个路由)  第一次点击时将路由信息push到visitedViews中 后来的每次点击都是通过watch $route执行添加标签方法 

删除时要考虑是否是对激活项进行关闭 若是则先删除数组中要关闭的标签的那个路由,然后获取剩余visitedViews中的路由,让最后一个路由作为激活项

vue tab页多页面切换

实现路由发生变化时,新增一个tab标签页,点击其他标签时切换到对应的页面,刷新网页同时保留状态

在这里插入图片描述

这里就直接说它实现的代码就OK!!!

VueX记录下每次新增后的tab标签页路由

store.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    // 路由导航start
    // 缓存组件页面
    catch_components: [],
    // 当前选中的菜单 - 默认选择首页
    activePath: '/index',
    // 菜单项 - 默认需要展示的页面()
    tabList: [{
      path: '/index',
      label: '首页',
      name: 'index',
      fullPath: "/index"
    }],
    // 路由导航end
  },
  // 更改vuex的store中状态的唯一方法 - 同步操作
  mutations: {
    // 路由导航start
    //清空vuex数据
    clearVUEX(state) {
      state.catch_components = []
      state.activePath = 'index'
      state.tabList = [{
        path: '/idnex',
        label: '首页',
        name: 'index',
        fullPath: "/index"
      }]
    },
    // 跳转页面执行
    selectMenu(state, submenu) {
      // 首页就是 wellcome   也就是 home
      if (submenu.name === 'index') {
        submenu.name = 'index'
        label.path = '首页'
        submenu.path = '/index'
        submenu.fullPath= '/index'
      }
      // 当前选中菜单
      var activePath = submenu.name
      // 历史已选中菜单列表
      var oldTabList = state.tabList
      // 将菜单信息添加到tablist - 添加时判断是否已有该路由标签
      var result = oldTabList.some(item => {
        if (item.name === activePath) {
          // console.log('--------', item.fullPath != submenu.fullPath)
          // 有该路由标签是否为多次点击(相当于查看同路由下的详情,该过程只改变了参数)
          if (!item.fullPath != submenu.fullPath) {
            item.fullPath = submenu.fullPath
          }
          return true
        }
      })
      // 如果不包含该对象,则添加
      if (!result) {
        oldTabList.push({
          path: submenu.name,
          name: submenu.name,
          label: submenu.label,
          fullPath: submenu.fullPath
        })
      }
      // 重新赋值标签路由和当前选中菜单
      state.activePath = activePath
      state.tabList = oldTabList
    },
    // 添加keepalive缓存
    addKeepAliveCache(state, val) {
      // 如果是首页不缓存
      if (val === 'index') {
        return
      }
      // console.log(state.catch_components)
      // 添加时判断,如果该组件已存在,便不添加
      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
    },
    //关闭菜单
    closeTab(state, val) {
      // 重新赋值
      state.activePath = val.activePath
      state.tabList = val.tabList
    },
    // 点击标签选择菜单
    changeMenu(state, val) {
      state.activePath = val
    },
    // 路由导航end
  },
  actions: {
  }
})

根据自己的需求定义一个展示路由标签组件vue文件

BScroll :当路由标签过多时,用于横向滚动标签页

 <!-- crumbs.vue -->
<template>
  <div class="tags">
    <div class="horizontal-container">
      <div class="scroll-wrapper" ref="scroll">
        <div class="scroll-content">
          <el-tag size="medium" v-for="(tab, index) in tabList" :key="tab.path" @close="handleClose(tab, index)"
            @click="changeMenu(tab)" :closable="tab.name !== 'index'"
            :effect="activePath === tab.name ? 'dark' : 'plain'">
            {{tab.label}}
          </el-tag>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
  import {
    mapState
  } from 'vuex';
  import BScroll from '@better-scroll/core'
  export default {
    data() {
      return {
        //菜单列表
        menuList: [],
      }
    },
    computed: {
      ...mapState({ // 从 state 中的到的计算属性
        activePath: state => state.activePath, // 已选中菜单
        tabList: state => state.tabList, // tags菜单列表
        catch_components: state => state.catch_components, // keepalive缓存
      })
    },
    mounted() {
      // this.handleCommand()
      this.init()
    },
    methods: {
      init() {
        this.bs = new BScroll(this.$refs.scroll, {
          scrollX: true,
          probeType: 3 // listening scroll event
        })
      },
      // 清空当前vuex数据
      handleCommand() {
        this.$store.commit('clearVUEX')
      },
      // 点击菜单 - 传入name,添加到keepalive缓存页面
      selectMenu(item) {
        // console.log(item.name)
        // 加入keepalive缓存
        this.$store.commit('addKeepAliveCache', item.name)
        //添加tags标签
        //访问wellcome 就代表home
        var name = item.name === 'index' ? 'index' : item.name
        var submenu = {
          path: item.path,
          name: name,
          label: item.meta.title,
          fullPath: item.fullPath
        }
        // console.log(submenu)
        //更新选中菜单
        this.$store.commit('selectMenu', submenu)
        console.log(this.$store.state.tabList)
      },
      // 点击标签跳转路由
      changeMenu(item) {
        // 历史选中菜单
        var oldActivePath = this.$store.state.activePath
        // 首先判断点击的是否是自己,如果是自己则return
        if (oldActivePath === item.name) {
          return
        }
        // 存储菜单
        this.$store.commit('changeMenu', item.name)
        // 页面跳转
        this.$router.push({
          path: item.fullPath
        })
      },
      // 关闭tab标签
      handleClose(tab, index) {
        // 历史选中菜单
        var oldActivePath = this.$store.state.activePath
        // 历史已选中菜单列表
        var oldTabList = this.$store.state.tabList
        // 计算标签个数
        let length = oldTabList.length - 1
        // 删除tabList中的该对象
        for (let i = 0; i < oldTabList.length; i++) {
          let item = oldTabList[i]
          if (item.name === tab.name) {
            oldTabList.splice(i, 1);
          }
        }
        // 删除keepAlive缓存
        this.$store.commit('removeKeepAliveCache', tab.name)
        // 如果关闭的标签不是当前路由的话,就不跳转
        if (tab.name !== oldActivePath) {
          return
        }
        // 如果length为1,必然只剩下首页标签,此时关闭后,更新到首页
        if (length === 1) {
          // 同时存储菜单
          this.$store.commit('closeTab', {
            activePath: 'home',
            tabList: oldTabList
          })
          // tab页向左跳转
          this.$router.push({
            name: oldTabList[index - 1].name
          })
          // 不再向下执行
          return
        }
        // 关闭的标签是最右边的话,往左边跳转一个
        if (index === length) {
          // 同时更新路径
          oldActivePath = oldTabList[index - 1].name
          // 同时存储菜单
          this.$store.commit('closeTab', {
            activePath: oldActivePath,
            tabList: oldTabList
          })
          // tab页向左跳转
          this.$router.push({
            name: oldTabList[index - 1].name
          })
        } else {
          // 同时更新路径
          oldActivePath = oldTabList[index].name
          // 同时存储菜单
          this.$store.commit('closeTab', {
            activePath: oldActivePath,
            tabList: oldTabList
          })
          // tab页向右跳转
          this.$router.push({
            name: oldTabList[index].name
          })
        }
      },
    },
    watch: {
      // 路由发生变化时调用更新tab标签数据
      '$route': {
        handler(newValue) {
          // console.log(newValue, oldValue)
          this.selectMenu(newValue);
        },
        immediate: true
      }
    },
  }
</script>
<style lang="less" scoped="scoped">
  /deep/ .el-tag--medium {
    margin-right: 10px;
  }
  .horizontal-container {
    .scroll-wrapper {
      position: relative;
      width: 100%;
      // margin: 80px auto;
      margin: 0 auto;
      white-space: nowrap;
      // border: 3px solid #42b983;
      border-radius: 5px;
      overflow: hidden;
      .scroll-content {
        display: inline-block;
      }
      .scroll-item {
        height: 40px;
        line-height: 40px;
        // font-size: 24px;
        display: inline-block;
        text-align: center;
        padding: 0 10px;
      }
    }
  }
  /deep/.el-tabs__nav-scroll {
    background: #fff;
  }
  .el-tag {
    cursor: pointer;
    margin-left: 10px;
    border-radius: 2px;
    font-size: 12px;
    color: #1890FF;
    border-color: #1890FF;
  }
  .el-tag--dark {
    color: #fff;
    background-color: #1890FF;
  }
  .el-dropdown-link {
    cursor: pointer;
  }
  .el-icon-arrow-down {
    font-size: 12px;
  }
  .submit-row {
    display: flex;
    flex-direction: row;
    justify-content: flex-end;
    align-items: center;
  }
</style>

若F5或者强刷新页面时需要保留当前tab路由数据,在App.vue中插入代码

    created() {
      //在页面刷新时将vuex里的信息保存到sessionStorage里
      window.addEventListener("beforeunload", () => {
        sessionStorage.setItem("store", JSON.stringify(this.$store.state))
      })
    
    },

以上为个人经验,希望能给大家一个参考,也希望大家多多支持阿兔在线工具。

点赞(0)

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部