当前位置:   article > 正文

【通俗易懂】vue实现tagsview标签导航栏切换菜单功能【详细注释,都能看的懂】

tagsview

前言:

后台系统现在基本都要有tagsview标签这个功能了,很多人是看网上的模板vue-element-admin内的tagsview。我也看了网上的很多资料和这个模板,但是很可惜,我是个小白,看了半天没看懂,也不知道如何复制,如何修改。所以我只能自己慢慢写一个这个功能,通过两天时间不断试错尝试,总算是写出来了功能。我相信不只有我一个人看不懂,那我就写个详细的攻略给你们看看吧,希望帮助更多的小白少掉点头发。如果看完还是不懂的可以评论问我。

效果图

在这里插入图片描述

基于

实现基于:vue element-ul vuex 右键菜单插件

思路:

利用vuex来实现动态响应
1,点击菜单获取菜单的名字用commit传给vuex内保存
2,tagsview组件拿到名字渲染出来。
3,添加点击事件,点击标签跳转对应路由。
4,通过动态class来设置高亮效果
5,右键菜单通过插件来实现
6,给叉号加点击事件,点击后关闭当前标签。通过传参拿到当前项和index来实现

功能:目前是主要的功能,更多细节功能可以扩展

1,点击菜单增加一个对应名字的tagsview标签
2,点击tagsview标签可以跳转对应的菜单路由
3,tagsview标签会根据跳转的页面不同高亮显示
4,点击关闭按钮可以关闭tagsview标签
5,点击tagsview标签会对应展开菜单,并选中对应菜单
6,右键tagsview标签可以出现菜单,里面有关闭和关闭全部选项
7,点击右键菜单内关闭会关闭一个,点击关闭全部会把所有tagsview标签栏清空
8,关闭时,如果关闭的是最右边的则向左边tagsview标签跳转路由,如果不是则向右跳转。如果删除后没有tagsview标签了,那么自动跳转首页
9,所有tagsview标签关闭后,标签导航栏隐藏不显示。有tagsview标签时显示。

上代码:

创建一个tagsview.vue组件:把这个代码复制进去。
<template>
  <!-- calss添加了一个样式 -->
  <div  class="tagsbox">
    <div
      @contextmenu.prevent="openMenu(item,$event)"
      :class="isActive(item.url)?'active':''"
      class="tagsview"
      v-for="(item, index) in tags"
      :key="index"
      @click="tagsmenu(item)"
    >
      {{ item.name }}
      <!-- 这个地方一定要click加个stop阻止,不然会因为事件冒泡一直触发父元素的点击事件,无法跳转另一个路由 -->
      <span class="el-icon-close tagsicon" @click.stop="handleClose(item,index)"></span>
      <ul v-show="visible" class="contextmenu" :style="{left:left+'px',top:top+'px'}">
      <li @click.stop="handleClose(item,index)">关闭</li>
      <li @click.stop="cleartags($route.path)">关闭所有</li>
    </ul>
    </div>
  </div>
</template>

<script>
//这个就是导入vuex的数据,配合下面...map用
import { mapState, mapMutations } from "vuex";
export default {
  data() {
    return {
      //右键菜单隐藏对应布尔值
      visible: false,
      //右键菜单对应位置
      top: 0,
      left: 0
    }
  },
  computed: {
    //引入vuex中state中的tags数据,一样this调用就行
    ...mapState(["tags"]),
  },
  watch:{
    //监听右键菜单的值是否为true,如果是就创建全局监听点击事件,触发closeMenu事件隐藏菜单,如果是false就删除监听
    visible(value) {
          if (value) {
            document.body.addEventListener('click', this.closeMenu)
          } else {
            document.body.removeEventListener('click', this.closeMenu)
          }
        }
  },
  methods: {
    //引入vuex中mutation方法,可以直接this.xxx调用他
    ...mapMutations(["closeTab", "cleartagsview"]),
    //点击叉叉删除的事件
    handleClose(item, index) {
      //先把长度保存下来后面用来比较做判断条件
      let length = this.tags.length - 1;
      //vuex调方法,上面...map引入的vuex方法,不会这种方法的看vue官网文档
      this.closeTab(item);
      // 如果关闭的标签不是当前路由的话,就不跳转
      if (item.url !== this.$route.path) {
        return;
      }
      // 判断:如果index和length是一样的,那就代表都是一样的长度,就是最后一位,那就往左跳转一个
      if (index === length) {
        //再判断:如果length=0,也就是说你删完了所有标签
        if (length === 0) {
          //那么再判断:如果当前路由不等于index,也就是我首页的路由
          if (this.$route.path !== "/index") {
            //那么就跳转首页。这一步的意思是:如果删除的最后一个标签不是首页就统一跳转首页,如果你删除的最后一个标签是首页标签,已经在这个首页路由上了,你还跳个什么呢。这不重复操作了吗。
            this.$router.push({ path: "/index" });
          }
        } else {
          //那么,如果上面的条件都不成立,没有length=0.也就是说你还有好几个标签,并且你删除的是最后一位标签,那么就往左边挪一位跳转路由
          this.$router.push({ path: this.tags[index - 1].url });
        }
      } else {
        // 如果你点击不是最后一位标签,点的前面的,那就往右边跳转
        this.$router.push({ path: this.tags[index].url });
      }
    },
    //点击跳转路由
    tagsmenu(item) {
      //判断:当前路由不等于当前选中项的url,也就代表你点击的不是现在选中的标签,是另一个标签就跳转过去,如果你点击的是现在已经选中的标签就不用跳转了,因为你已经在这个路由了还跳什么呢。
      if (this.$route.path !== item.url) {
        //用path的跳转方法把当前项的url当作地址跳转。
        this.$router.push({ path: item.url });
      }
    },
    //通过判断路由一致返回布尔值添加class,添加高亮效果
    isActive(route) {
          return route === this.$route.path
        },
        //右键事件,显示右键菜单,并固定好位置。
        openMenu(tag, e) {
          this.visible = true
          this.selectedTag = tag
          const offsetLeft = this.$el.getBoundingClientRect().left 
          this.left = e.clientX - offsetLeft + 210  //右键菜单距离左边的距离
          this.top = e.clientY +10  //右键菜单距离上面的距离           这两个可以更改,看看自己的右键菜单在什么位置,自己调
        },
        //隐藏右键菜单
        closeMenu() {
          this.visible = false
        },
        //右键菜单关闭所有选项,触发vuex中的方法,把当前路由当参数传过去用于判断
        cleartags(val){
          this.cleartagsview(val)
        }
  },
};
</script>

<style lang="scss" scoped>
//标签导航样式
.tagsview {
  cursor: pointer;
  margin-left: 4px;
  height: 26px;
  line-height: 26px;
  padding: 0 8px;
  border: 1px solid #d8dce5;
  border-radius: 5px;
  color: #000;
  font-size: 12px;
  display: inline-block;
}
//叉号鼠标经过样式
.tagsicon:hover{
  color: #f56c6c;

}
//标签高亮
.active{
  background-color: #40ba84;
  color: #fff;
}
//右键菜单样式
.contextmenu {
      margin: 0;
      background: #fff;
      z-index: 100;
      position: absolute;
      list-style-type: none;
      padding: 5px 0;
      border-radius: 4px;
      font-size: 12px;
      font-weight: 400;
      color: #333;
      box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
      li {
        margin: 0;
        padding: 7px 16px;
        cursor: pointer;
        &:hover {
          background: #eee;
        }
      }}
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158

菜单栏部分

给个点击事件,把你们循环的菜单每一项拿出来传给vuex保存,用来后面渲染标签
在这里插入图片描述
通过commit把每一项菜单的数据传给vuex,这个地方也可以用console.log看一下你的每一项内path和name之类的是什么,找一个跟你路由中一致的用于后续判断依据。这个每一项就是传进来的val,写法就是val.path这样,查看点击后他会返回什么。看看跟你的当前跳转的路由path是否一样,如果是一样的就可以用path来做判断。如果是name一样就用name判断。

methods:{
            //点击把菜单的名字传出去
            clickMenu(val){
                this.$store.commit("pushtags",val)
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
主组件的部分:注意,这个不要直接复制了,你看下我tagsview组件放的位置就行了,可以自己调整位置
<template>
  <div>
    <!-- elementul中布局组件 -->
    <el-container style="height: 100vh;">
      <!-- 左侧菜单部分 -->
      <el-aside :width="subwidth">
        <!-- 菜单组件 -->
          <nav-left></nav-left>
      </el-aside>
      <el-container>
        <!-- 头部部分 -->
        <el-header>
          <!-- 头部组件 -->
            <headers></headers>
        </el-header>
        <!-- 标签导航栏组件 -->
        <tagsview></tagsview>
        <!-- 内容部分 -->
        <el-main>
          <!--if判断路由元信息内keepAlive是否为true如果是就缓存,如果缓存显示缓存的,如果不缓存就用不缓存的路由视图 -->
          <keep-alive v-if="$route.meta.keepAlive">
              <router-view></router-view>
          </keep-alive>
            <router-view v-else></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script>
//引入vuex中的state,不了解的参考vuex文档
import {mapState} from 'vuex'
//菜单组件
import navLeft from "@/components/navLeft"
//头部组件
import Headers from '../components/headers.vue';
//引入tagsview组件
import tagsview from "@/views/tagsview/tagsview.vue"
export default {
  data(){
    return{
      //菜单的宽度,默认给个200
        subwidth:'200px'
    }
  },
    components:{
      //菜单
        navLeft,
        //头部
        Headers,
        //引入tagsview组件
        tagsview
      },
      computed:{
        //引入vuex中state的变量,可以直接this.xxx调用到
        ...mapState(["isCollapse"]),
      },
      watch:{
        //监听vuex中的变量如果变动了就赋值,从而改变菜单栏缩小展开
        isCollapse(){
          if(this.isCollapse){
            this.subwidth='64px'
          }else{
            this.subwidth='200px'
          }
        }
      },
};
</script>

<style lang="scss" scoped>
//这些是布局组件内自带的,去elementul复制然后改改
.el-header, .el-footer {
    background-color: #fff;
    color: #333;
    line-height: 60px;
    box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  }
  //左侧菜单栏样式
  .el-aside {
    background-color: #001529;
    color: #333;
    text-align: left;
    line-height: 56px;
    //下面四个是菜单折叠动画效果
    transition: width 0.15s;
  -webkit-transition: width 0.15s;
  -moz-transition: width 0.15s;
  -webkit-transition: width 0.15s;
  -o-transition: width 0.15s;
  }
  //内容区域
  .el-main {
    background-color: #E9EEF3;
    color: #333;
  }
  
  body > .el-container {
    margin-bottom: 40px;
  }
  
  .el-container:nth-child(5) .el-aside,
  .el-container:nth-child(6) .el-aside {
    line-height: 260px;
  }
  
  .el-container:nth-child(7) .el-aside {
    line-height: 320px;
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
vuex部分:
import Vue from 'vue'
import Vuex from 'vuex'
import router from '../router/index.js'
Vue.use(Vuex)
export default new Vuex.Store({
  state: { 
    //tags数组
    tags:[],
    //tagsview标签显示隐藏
    isCollapse:false
  },
  mutations: {
    pushtags(state,val){
      //如果等于-1说明tabs不存在那么插入,否则什么都不做
      //findindex找角标,循环判断一下,如果等于那么就代表有相同的,就不必添加,如果找不到那就是-1.就添加
      let result = state.tags.findIndex(item => item.name === val.name)
      result === -1 ? state.tags.push(val) : ''
    },
    //关闭标签
    closeTab(state, val) {
      //同上,找角标,然后用角标的位置对应删除一位。splice:这是数组的删除方法
      let result = state.tags.findIndex(item => item.name === val.name)
      state.tags.splice(result, 1)
  },
  //关闭所有tagsview标签
    cleartagsview(state,val){
      //清空数组
      state.tags=[]
      //跳转到首页,val接受传过来的当前路由
      if(val !== "/index"){
        router.push({path:"/index"})
      }
    },
    //改变tagsview显示隐藏
    changeisshow(state){
      state.isCollapse=!state.isCollapse
    }
  },
  actions: {
  },
  modules: {
  }
})

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

右键菜单实现

第一步,安装组件

npm install vue-contextmenu --save
  • 1

第二步:引入到main.js文件

import VueContextMenu from 'vue-contextmenu'
Vue.use(VueContextMenu)
  • 1
  • 2

第三步:使用,放在你想要弹出的地方,具体位置看我代码,放在了div上
contextmenu:这是弹出组件
prevent:这是修饰符,意思是右键的时候不要弹出默认的菜单,默认的菜单就是你们平常在电脑桌面上右键刷新的那个右键菜单。让他别出来。

@contextmenu.prevent="openMenu(item,$event)"
  • 1

点击标签左侧菜单也选中对应的菜单,子菜单自动展开选中功能

在这里插入图片描述
这个功能我当时乍一想也觉得有点复杂,后来发现elementul自带一个属性一句话解决
没错就这一句话,加在el-menu上,他就会根据当前路由自动激活对应菜单。注意是menu不是menu-item。最外层的容器

:default-active="$route.path"
  • 1

标签切换时自动高亮

是这一句话的作用,其实就一个逻辑,根据当前路由判断一下是不是一样,如果是一样的路由,那就给他加个class。这个active我已经css写好样式了。添加就亮不添加就不亮

:class="isActive(item.url)?'active':''"
  • 1

右键关闭所有

首先右键关闭我就直接再调用了一次叉号的方法完事。
这里要说的是关闭所有的小注意项:
关闭所有很明显很简单就是把数组清空,这里注意清空要用【】不能用 " ",如果用 " "表示清空就会报错。
像这样:

 //关闭所有tagsview标签
    cleartagsview(state,val){
      //像这里一样后面加的是[]
      state.tags=[]
      //跳转到首页,val接受传过来的当前路由
      if(val !== "/index"){
        router.push({path:"/index"})
      }
    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意项:

1,样式可以改的,比如我用的div,你们可以用tag组件,或者tags组件或者自己写div,span之类的循环也行。这个自行更改

2,我是用的el-menu组件写的侧边栏菜单,所以我用它自带的属性开启路由模式了,所以url就是我的路由,我这个组件内有很多用path和url来对比判断的方法,因为我url和path是一致的,所以我可以这么用,你们用的时候一定要看下自己的路由信息哪个是一样的,有些人是name一样,那就换成name。这点很重要,不然不生效!!!

3,如果中途有问题,建议多用log看看,然后可以用alert弹框来测试一下是否执行某些地方。这样就很好更改了。

4,你的标签导航栏组件引入主组件结构内,放在全局,因为是所有页面都要用的,自己看看放什么位置好,自己选中,我是放在了页头的下面,有些人会放在内容部分的上面。随意

5,vuex使用会实时更新,但是刷新也会没有,如果你想要一直存在,就保存到本地去。

如果看了还是有不了解的可以评论提问
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/264036
推荐阅读
相关标签
  

闽ICP备14008679号