vue tab标签实现并与菜单联动

发布于 2019-09-06  655 次阅读


主要思路,每点击一下侧边栏菜单(也就是访问URL),根据这个URL匹配的路由中meta属性中的自定义的isTabView属性,判断是否需要显示为TAB,是的话就增加这个TAB标签或者切换到这个TAB标签。

本TAB组件主要特点:

1.菜单联动,切换标签时菜单也会切换(其实是因为router.path)

2.标签数据都保存到sessionStorage,页面打开时也会根据sessionStorage恢复之前的标签

3.添加功能左移动,右移动(当标签书过多时),关闭当前,关闭所有

还有个是标签:首页(/control)是一直保留的,可以自行修改

第一步肯定是拦截到这个访问URL的事件,每次访问都要调用addOpendPage方法

router.afterEach((to, from, next) => {
  let param = {app: router.app, name: to.name, params: to.params, query: to.query, meta: to.meta, path: to.path}
  store.dispatch('addOpendPage', param)
  NProgress.done() // 结束Progress
})

然后直接放核心代码,也是使用vuex的,在store目录下的modules目录,保存为tabview.js


import * as utils from '@/utils'
import router from "../../router"

const tabview = {
  state: {
    pageOpendList: []
  },
  mutations: {
    /**
     * 初始化设置tab 一般默认首页,页面加载时调用
     * @method tabOpendListInit
     */
    setOpenedList(state) {
      const local = sessionStorage.pageOpendList && JSON.parse(sessionStorage.pageOpendList).length > 0
      if (local) {
        state.pageOpendList = JSON.parse(sessionStorage.pageOpendList)
      }
      // var exist = false;
      // for (var k in state.pageOpendList) {
      //   if (state.pageOpendList[k].path == '/control')
      //     exist = true;
      // }
      //默认添加首页
      if (!utils.objArrayContains(state.pageOpendList,'path','/control')) {
        state.pageOpendList.splice(0, 0, {
          meta: {title: "首页", isTabView: true},
          name: "control",
          params: {},
          path: "/control",
          query: {}
        })
        sessionStorage.pageOpendList = JSON.stringify(state.pageOpendList)
      }
    },
    /*
     * @描述: 关闭所有标签
     * @参数注释:
     * @param state
     * @返回值:
     * @创建人:  LoneKing
     * @创建时间:  16:04 2019/7/26
     */
    closeAllOpenedList(state) {
      sessionStorage.pageOpendList = ''
      state.pageOpendList = []
      router.push({
        name: "control"
      })
    },
    /**
     * 每次打开页面都会经过此方法,用于合并参数
     * @method setPageOpendList
     */
    setPageOpendList(state, res) {
      const {index, query, params, meta, path} = res
      let opendPage = state.pageOpendList[index]
      if (params) {
        opendPage.params = params
      }
      if (query) {
        opendPage.query = query
      }
      if (meta) {
        opendPage.meta = meta
      }
      if (path) {
        opendPage.path = path
      }
      state.pageOpendList.splice(index, 1, opendPage)
      sessionStorage.pageOpendList = JSON.stringify(state.pageOpendList)
    },
    increateTag(state, tag) {
      const local = sessionStorage.pageOpendList && JSON.parse(sessionStorage.pageOpendList).length > 0
      if (local) {
        state.pageOpendList = JSON.parse(sessionStorage.pageOpendList)
      }

      if (!utils.objArrayContains(state.pageOpendList,'path',tag.path)) {
        state.pageOpendList.push(tag)
        sessionStorage.pageOpendList = JSON.stringify(state.pageOpendList)
      }
    },
    /**
     * @param {*} state
     * @param {当前页签信息} obj
     * @param { 当前实例 } obj.vm
     * @param { 路由name} obj.name
     */
    closeOpendList(state, name) {
      const lists = state.pageOpendList
      for (let i = 0; i < lists.length; i++) {
        if (lists[i].name === name) {
          const lastName = state.pageOpendList[i - 1].name
          state.pageOpendList.splice(i, 1)
          sessionStorage.setItem('pageOpendList', JSON.stringify(state.pageOpendList))
          router.push({
            name: lastName
          })
        }
      }
    },
    /**
     * @param {*} state
     * @param {当前页签信息} obj
     * @param { 当前实例 } obj.vm
     * @param { 路由name} obj.name
     */
    closeOtherOpendList(state, name) {
      const lists = state.pageOpendList
      for (let i = 0; i < lists.length; i++) {
        if (lists[i].name === name) {
          state.pageOpendList = []
          state.pageOpendList.push(lists[i])
          //默认添加首页
          if (!utils.objArrayContains(state.pageOpendList,'path','/control')) {
            state.pageOpendList.splice(0, 0, {
              meta: {title: "首页", isTabView: true},
              name: "control",
              params: {},
              path: "/control",
              query: {}
            })
            sessionStorage.pageOpendList = JSON.stringify(state.pageOpendList)
          }
          break
        }
      }
    }
  },
  actions: {
    /**
     * @method addOpendPage
     * @param vm 当前实例
     * @param name 当前路由name
     * @param query 查询参数
     * @param param 查询参数
     * 一般放在router BeforeAfter(BeforeEach) 执行
     */
    addOpendPage: ({commit, state}, param) => {
      let {vm, name, params = '', query = '', meta = '', path = ''} = param
      let pageOpendList = state.pageOpendList
      let opendLen = pageOpendList.length
      let i = 0
      let tagHasOpened = false
      if (opendLen > 0) {
        for (; i < opendLen; i++) {
          //本来用的是name 但是viewList会出现重复情况 于是改成path
          if (pageOpendList[i].path === path) {
            commit('setPageOpendList', {
              index: i,
              params,
              query,
              meta,
              path,
            })
            tagHasOpened = true
            break
          }
        }
      }
      /**
       * 注入参数 如果tab未打开
       */
      if (!tagHasOpened && name) {
        let tag = {
          name: name
        }
        if (params) {
          tag.params = params
        }
        if (query) {
          tag.query = query
        }
        if (meta && meta.isTabView) {
          tag.meta = meta
        } else if (meta && !meta.isTabView) {
          return
        }
        if (path) {
          tag.path = path
        }
        commit('increateTag', tag)
      }
    }
  }
}

export default tabview

getter.js只需要这一个属性就行

const getters = {
  pageOpendList: state => state.tabview.pageOpendList
}
export default getters

TabView组件代码,保存为TabView.js 然后直接import,调用就行

<template>
    <div class="tag-view-wrap">
        <el-button @click="moveLeft"  class="left left-button" icon="el-icon-d-arrow-left" circle
                   style="padding:0;" type="default"></el-button>
        <div class="tabs-wrap" id="tabs-wrap">
            <div :style="{marginLeft:tabMarginLeft+'px'}" class="tabs breadcrumb-move">
                <el-tag
                        :class="{ active: item.path === $route.path }"
                        :closable="item.name !== 'control'"
                        :key="item.id"
                        @click.native="jump(item)"
                        @close="close(item)"
                        class="tag-view"
                        v-for="item in pageOpendList">
                    {{menuTitle[item.path]}}
                </el-tag>
            </div>
        </div>
        <el-dropdown class="right close-button" trigger="click">
            <el-button size="mini" type="default">
                操作<i class="el-icon-arrow-down el-icon--right"></i>
            </el-button>
            <el-dropdown-menu slot="dropdown">
                <el-dropdown-item @click.native="closeOther">关闭其他</el-dropdown-item>
                <el-dropdown-item @click.native="closeAll">关闭所有</el-dropdown-item>
                <el-dropdown-item @click.native="clearMenuCache">清除菜单缓存</el-dropdown-item>
                <el-dropdown-item @click.native="clearTabCache">清除Tab缓存</el-dropdown-item>
            </el-dropdown-menu>
        </el-dropdown>
        <el-button @click="moveRight" circle class="right right-button" icon="el-icon-d-arrow-right"
                   type="default"></el-button>
    </div>
</template>

<script>
    import {mapGetters} from 'vuex'

    export default {
        data() {
            return {
                tabMarginLeft: 0
            }
        },
        computed: {
            ...mapGetters([
                'pageOpendList',
                'menuTitle',
            ]),
        },
        watch: {
            //自动调整tab位置滚动
            pageOpendList: {
                handler: function (newVal, oldVal) {
                    let tabs = document.getElementsByClassName("tag-view")
                    let wrapWidth = document.getElementById("tabs-wrap").offsetWidth
                    let currentActiveTabWidth = 0
                    for (let index = 0; index < newVal.length; index++) {
                        //+4是因为每个tab之间margin-left 4
                        currentActiveTabWidth += tabs[index].offsetWidth + 4
                        if (this.$route.path === newVal[index].path) {
                            log(this.$route.path)
                            break
                        }
                    }
                    if (currentActiveTabWidth > wrapWidth) {
                        this.moveRight()
                    } else {
                        this.moveLeft()
                    }
                },
                deep: true
            }
        },
        beforeCreate() {
            this.$store.commit('setOpenedList')
        },
        methods: {
            clearMenuCache() {
                sessionStorage.menus = ''
                window.location.reload()
            },
            clearTabCache() {
                sessionStorage.pageOpendList = ''
                window.location.reload()
            },
            moveLeft() {
                this.tabMarginLeft = 0
            },
            moveRight() {
                let totalWidth = this.getTabsWidth()
                let wrapWidth = document.getElementById("tabs-wrap").offsetWidth
                if (totalWidth > wrapWidth) {
                    this.tabMarginLeft = wrapWidth - totalWidth - 180
                }
            },
            getTabsWidth() {
                let totalWidth = 0
                let tabs = document.getElementsByClassName("tag-view")
                for (let i = 0; i < tabs.length; i++) {
                    //+4是因为每个tab之间margin-left 4
                    totalWidth += tabs[i].offsetWidth + 4
                }
                return totalWidth
            },
            closeOther() {
                this.$store.commit("closeOtherOpendList", this.$route.name)
            },
            closeAll() {
                this.$store.commit("closeAllOpenedList")
            },
            jump(item) {
                const {params, query} = item
                /**
                 * @description
                 * 下面四种情况考虑到参数传递的问题,所以单独处理
                 */
                if (params) {
                    this.$router.push({
                        name: item.name,
                        params: params
                    })
                    return
                }
                if (query) {
                    this.$router.push({
                        name: item.name,
                        query: query
                    })
                    return
                }
                if (query && params) {
                    this.$router.push({
                        name: item.name,
                        params: params,
                        query: query
                    })
                    return
                }
                this.$router.push({
                    name: item.name
                })
            },
            close(item) {
                this.$store.commit("closeOpendList", item)
            }
        }
    }
</script>

<style lang="scss">

    .tag-view-wrap {
        border-bottom: 1px solid rgb(230, 230, 230);
        width: 90%;
        display: inline-block;
        white-space: nowrap;
        overflow: hidden;

        .tabs-wrap {
            width: 88%;
            float: left;
            overflow: hidden;
        }

        .left-button {
            position: relative;
            top: 12px;
            width: 28px;
            height: 28px;
            padding: 0px !important;
        }

        .right-button {
            position: relative;
            top: 12px;
            width: 28px;
            height: 28px;
            padding: 0px !important;
        }

        .close-button {
            padding-left: 3px;
        }

        .tabs {
            width: 100%;
            white-space: nowrap;
            .tag-view {
                cursor: pointer;
                margin: 0 4px;

                &.active {
                    background-color: #e63979;
                    color: #fff;

                    .el-tag__close {
                        color: #fff;
                    }
                }
            }
        }
    }
</style>

相关代码打包

[reply2down]http://down.lkcloud.top/BlogFile/tabview.zip[/reply2down]


LoneKing