后台管理系统右侧tags的实现

1,891 阅读4分钟

已实现的功能

在这里插入图片描述
接下来就是如何实现的了(参考了vue-element-admin,根据自身项目做了一些改变) tagsViews组件
在这里插入图片描述

<template>
    <div class="tags-view-container">
        <el-scrollbar class="tags-view-wrapper" ref="scrollContainer">
            <router-link class="tags-view-item" :to="item" :key="item.path" :class="isActive(item)?'active':''" v-for="(item) in visitedViews" @contextmenu.prevent.native="openMenu(item,$event)">
                {{item.tagsName?item.tagsName:item.name}}
                <span class='el-icon-close' @click.prevent.stop='closeSelectedTag(item)' v-if="item.path != '/index'"></span>
            </router-link>
        </el-scrollbar>
        <ul class="contextmenu" v-show="visible" :style="{left:left+'px',top:'20px' }">
	      	<li @click="refresh(selectTag)">刷新</li>
	      	<li v-if="selectTag.path !== '/index'" @click="closeSelectedTag(selectTag)">关闭</li>
	      	<li @click="closeAllTags">全部关闭</li>
	    </ul>
    </div>
</template>

<script>
export default {
    watch:{
        $route(){
            this.addTags()
        },//地址栏变化了就触发这个添加方法
        visible(value) {
	      	if (value) {
	        	document.body.addEventListener('click', this.closeMenu)
	      	} else {
	        	document.body.removeEventListener('click', this.closeMenu)
	      	}
	    }
    },```
    data () {
        return {
			viewWidth: 100,
			left: 0,
			visible: false,
			selectTag: {}
        }
    },
    computed: {
        visitedViews() {
            return this.$store.state.tagsView.visitedViews
        }
    },
    methods: {
    	openMenu(tag, e){ // 右键打开tags菜单
    		this.selectTag = tag 
 
    		const menuMinWidth = 105
			const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
			const offsetWidth = this.$el.offsetWidth // container width
			const maxLeft = offsetWidth - menuMinWidth // left boundary
			const left = e.clientX - offsetLeft + 15 // 15: margin right
			
			if (left > maxLeft) {
			    this.left = maxLeft
			} else {
			    this.left = left
			}
    		this.visible = true
    	},
    	closeMenu(){ // 点击右键菜单以后关闭右键菜单
    		this.visible = false
    	},
        addTags(){
            const route = this.$route;//获取地址栏路由
            if (route.path !== "/") {
                this.$store.commit({
                    type:'addTags',
                    route
                })
            }
            return false
        },
        closeAllTags(){
          	// 关闭全部tags的操作
            this.$store.dispatch('closeALLTags').then(()=>{
                this.$router.push('/index')
            })
        },
        refresh(selectTag){ // 刷新
        	const route = this.$route.path
        	if(route === selectTag.path){
          		this.$router.replace({
				  	path: '/empty'
				})
				this.$router.replace({
					path: selectTag.path,
					query: selectTag.query
				})
        	}
        	else {
        		this.$router.replace({
				  	path: selectTag.path,
				  	query: selectTag.query
				})
        	}
        },
        changeViewWidth(viewWidth){
        	if(this.$refs.scrollContainer){
        		this.$refs.scrollContainer.$refs.wrap.firstElementChild.style.width = viewWidth + 'px'	
        	}
        },
        isActive(route) {   //  当前地址栏路径是否与渲染的路径相同 样式匹配
            return route.path === this.$route.path
        },
        closeSelectedTag(view){
            this.$store.dispatch("closeTags", view).then((views)=>{
                //  此时的views是指的被删除后的visitedViews数组中存在的元素
                if (this.isActive(view)) {  //  关闭的如果是自己就打开别的,否则不做操作
                    let latestView
                    if (this.visitedViews[views.index]) {   //  如果被关闭标签的后一个存在 则展示
                        latestView = this.visitedViews[views.index]
                    }
                    else if (this.visitedViews[views.index - 1]){   // 否则展示被关闭的前一个(前一个永远存在)
                        latestView = this.visitedViews[views.index - 1]
                    }
                    if (latestView) {   
                        this.$router.push(latestView)   //如果数组不为空则让选中的标签为紧邻关闭标签的那一个
                    }
                    else {
                        this.$router.push({path: '/index'}); //如果为空则页面跳转到首页
                    }
                }
            })
        }
    },
    mounted() {
        this.addTags()	
		window.setInterval(() => {  // 这个是为了解决tags数量在超出以后 滚动条只有在页面页面刷新才出现的问题  目的是触发回流让el-scrollbar组件重新计算    目前只是暂时解决的,本不应该出现滚动条问题     
			if(this.viewWidth === 100){
				this.viewWidth  = 200
			}
			else {
				this.viewWidth = 100
			}
		    setTimeout(this.changeViewWidth(this.viewWidth), 0)
		}, 1000)
    }
}
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.tags-view-container {
    height: 34px;
    width: 100%;
    overflow: auto;
    background: #fff;
    border-bottom: 1px solid #d8dce5;
    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
    .tags-view-wrapper {
    	white-space: nowrap;
	    position: relative;
	    left: 0;
	    top: 0;
	    overflow: hidden;
	    width: 100%;
	    height: 100%;
	  	/deep/ {
	    	.el-scrollbar__bar {
	      		bottom: 0px;
	    	}
	    	.el-scrollbar__wrap {
	    		height: 49px;
	    		.el-scrollbar__view {
	    			height: 100%;
	    		}
	    	}
	  	}
        .tags-view-item {
	        display: inline-block;
	        position: relative;
	        cursor: pointer;
	        height: 26px;
	        line-height: 26px;
	        border: 1px solid #d8dce5;
	        color: #495060;
	        background: #fff;
	        padding: 0 8px;
	        font-size: 12px;
	        margin-left: 5px;
	        margin-top: 4px;
	        &:first-of-type {
	            margin-left: 15px;
	        }
	        &:last-of-type {
	            margin-right: 15px;
	        }
	        &.active {
	            background-color: #42b983;
	            color: #fff;
	            border-color: #42b983;
	            &::before {
	            content: '';
	            background: #fff;
	            display: inline-block;
	            width: 8px;
	            height: 8px;
	            border-radius: 50%;
	            position: relative;
	            margin-right: 2px;
	            }
	        }
        }
    }
    .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;
            }
        }
    }
    .is-vertical {
    	display: none;
    }
    .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>

<style lang="scss">
/*reset element css of el-icon-close*/
.tags-view-wrapper {
    .tags-view-item {
        .el-icon-close {
            width: 16px;
            height: 16px;
            vertical-align: 2px;
            border-radius: 50%;
            text-align: center;
            transition: all .3s cubic-bezier(.645, .045, .355, 1);
            transform-origin: 100% 50%;
            &:before {
                transform: scale(.6);
                display: inline-block;
                vertical-align: -3px;
            }
            &:hover {
                background-color: #b4bccc;
                color: #fff;
            }
        }
    }
}
</style>

在组件中的mounted里面的轮查机制的写法实属无奈,el-scrollbar的滚动条在应该出现的时候,他得刷新一下才可以。所以我写了轮查来手动触发回流。

vuex中的配置 tagsViews.js

const tagsView = {
    state: {
        visitedViews: [
            {
                path: "/index",
                name: "首页",
                query: {},
                menuId: 1
            }
        ]
    },
    mutations: {
        addTags(state, payload) {
            let flag
            let tags = JSON.parse(localStorage.getItem("tags"))   //  获取刷新页面以后保存的tags
            localStorage.removeItem("tags")   //  获取之后删除session,避免关闭tags后的错误
            if (tags) {
                state.visitedViews = tags
            }
            flag = state.visitedViews.some(
                item => item.path === payload.route.path
            )//打开标签后,判断数组中是否已经存在该路由
            if (!flag) {
                state.visitedViews.push(
                    Object.assign(
                        {},
                        {
                            path: payload.route.path,
                            name: payload.route.name,
                            query: payload.route.query,
                            tagsName: payload.route.query.name?payload.route.query.name:""// 动态渲染的路由就给他添加一个tagsName    比如在列表页打开详情
                        }
                    )
                )
            } //数组中路由存在不push ,单击左侧路由变化,点击标签路由变化均触发
        }
    },
    actions: {
        closeTags(state, payload) {
            for (const [key ,item] of state.state.visitedViews.entries()) {
                if (item.path === payload.path) {
                    return {
                        delEl: state.state.visitedViews.splice(key, 1), 
                        index: key
                    }
                }
            }
        },
        closeALLTags(state) {
            state.state.visitedViews = [
            	{
	                path: "/index",
	                name: "首页",
	                query: {},
	                menuId: 1
	            }
            ]
        }
    }
}
  
export default tagsView
  
在app.vue中还要添加如下代码
mounted () {	//	用于监听浏览器的刷新事件
	window.addEventListener('beforeunload', e => {
		if(this.$store.state.tagsView.visitedViews.length > 1) {
			localStorage.setItem('tags', JSON.stringify(this.$store.state.tagsView.visitedViews))		
		}
	})
}
//	也可以在删除和添加tags的时候 每次都更新一下localStorage   这样就能不在app.vue里面写这些了
本身的需求是   刷新页面时tags不会被关闭掉

getters.js

const getters = {
    visitedViews: state => state.tagsView.visitedViews,
}

export default getters

store.js

import Vue from 'vue'
import Vuex from 'vuex'
import tagsView from './modules/tagsView'
import getters from './getters'

Vue.use(Vuex)

const store = new Vuex.Store({
    modules: {
        tagsView
    },
    getters
})

export default store

以上右侧打开tags的需求就基本实现了