后台系统现在基本都要有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标签时显示。
<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>
给个点击事件,把你们循环的菜单每一项拿出来传给vuex保存,用来后面渲染标签
通过commit把每一项菜单的数据传给vuex,这个地方也可以用console.log看一下你的每一项内path和name之类的是什么,找一个跟你路由中一致的用于后续判断依据。这个每一项就是传进来的val,写法就是val.path这样,查看点击后他会返回什么。看看跟你的当前跳转的路由path是否一样,如果是一样的就可以用path来做判断。如果是name一样就用name判断。
methods:{
//点击把菜单的名字传出去
clickMenu(val){
this.$store.commit("pushtags",val)
}
}
<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>
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: {
}
})
第一步,安装组件
npm install vue-contextmenu --save
第二步:引入到main.js文件
import VueContextMenu from 'vue-contextmenu'
Vue.use(VueContextMenu)
第三步:使用,放在你想要弹出的地方,具体位置看我代码,放在了div上
contextmenu:这是弹出组件
prevent:这是修饰符,意思是右键的时候不要弹出默认的菜单,默认的菜单就是你们平常在电脑桌面上右键刷新的那个右键菜单。让他别出来。
@contextmenu.prevent="openMenu(item,$event)"
这个功能我当时乍一想也觉得有点复杂,后来发现elementul自带一个属性一句话解决
没错就这一句话,加在el-menu上,他就会根据当前路由自动激活对应菜单。注意是menu不是menu-item。最外层的容器
:default-active="$route.path"
是这一句话的作用,其实就一个逻辑,根据当前路由判断一下是不是一样,如果是一样的路由,那就给他加个class。这个active我已经css写好样式了。添加就亮不添加就不亮
:class="isActive(item.url)?'active':''"
首先右键关闭我就直接再调用了一次叉号的方法完事。
这里要说的是关闭所有的小注意项:
关闭所有很明显很简单就是把数组清空,这里注意清空要用【】不能用 " ",如果用 " "表示清空就会报错。
像这样:
//关闭所有tagsview标签
cleartagsview(state,val){
//像这里一样后面加的是[]
state.tags=[]
//跳转到首页,val接受传过来的当前路由
if(val !== "/index"){
router.push({
path:"/index"})
}
},
1,样式可以改的,比如我用的div,你们可以用tag组件,或者tags组件或者自己写div,span之类的循环也行。这个自行更改
2,我是用的el-menu组件写的侧边栏菜单,所以我用它自带的属性开启路由模式了,所以url就是我的路由,我这个组件内有很多用path和url来对比判断的方法,因为我url和path是一致的,所以我可以这么用,你们用的时候一定要看下自己的路由信息哪个是一样的,有些人是name一样,那就换成name。这点很重要,不然不生效!!!
3,如果中途有问题,建议多用log看看,然后可以用alert弹框来测试一下是否执行某些地方。这样就很好更改了。
4,你的标签导航栏组件引入主组件结构内,放在全局,因为是所有页面都要用的,自己看看放什么位置好,自己选中,我是放在了页头的下面,有些人会放在内容部分的上面。随意
5,vuex使用会实时更新,但是刷新也会没有,如果你想要一直存在,就保存到本地去。