在项目中遇到这么一个需求,点击侧边栏时会弹出标签,点击页面的表格时也会弹出标签,在网上找的资料只有在侧边栏点击的时候弹标签,没有表格内的内容点击时弹标签,所以自己将tag组件改了一下,还好,完美适合需求
在网上百度的tagView多页签的代码
// tagview组件下的index.vue
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
<router-link
v-for="tag in visitedViews"
ref="tag"
:key="tag.path"
:class="isActive(tag)?'active':''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
tag="span"
class="tags-view-item"
@click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
@contextmenu.prevent.native="openMenu(tag,$event)"
>
<!-- 多语言设置 -->
{{ $t('route.'+tag.name) }}
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
</scroll-pane>
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)">{{ $t('tagsView.refresh') }}</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">{{ $t('tagsView.close') }}</li>
<li @click="closeOthersTags">{{ $t('tagsView.closeOthers') }}</li>
<li @click="closeAllTags(selectedTag)">{{ $t('tagsView.closeAll') }}</li>
</ul>
</div>
</template>
<script>
import ScrollPane from './ScrollPane'
import path from 'path'
export default {
components: { ScrollPane },
data() {
return {
visible: false,
top: 0,
left: 0,
selectedTag: {},
affixTags: []
}
},
computed: {
visitedViews() {
return this.$store.state.tagsView.visitedViews
},
routes() {
return this.$store.state.permission.routes
}
},
watch: {
$route() {
this.addTags()
this.moveToCurrentTag()
},
visible(value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
} else {
document.body.removeEventListener('click', this.closeMenu)
}
}
},
mounted() {
this.initTags()
this.addTags()
},
methods: {
isActive(route) {
return route.path === this.$route.path
},
isAffix(tag) {
return tag.meta && tag.meta.affix
},
filterAffixTags(routes, basePath = '/') {
let tags = []
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
}
if (route.children) {
const tempTags = this.filterAffixTags(route.children, route.path)
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags]
}
}
})
return tags
},
initTags() {
const affixTags = this.affixTags = this.filterAffixTags(this.routes)
for (const tag of affixTags) {
// Must have tag name
if (tag.name) {
this.$store.dispatch('tagsView/addVisitedView', tag)
}
}
},
addTags() {
const { name } = this.$route
if (name) {
this.$store.dispatch('tagsView/addView', this.$route)
}
return false
},
moveToCurrentTag() {
const tags = this.$refs.tag
this.$nextTick(() => {
for (const tag of tags) {
if (tag.to.path === this.$route.path) {
this.$refs.scrollPane.moveToTarget(tag)
// when query is different then update
if (tag.to.fullPath !== this.$route.fullPath) {
this.$store.dispatch('tagsView/updateVisitedView', this.$route)
}
break
}
}
})
},
refreshSelectedTag(view) {
this.$store.dispatch('tagsView/delCachedView', view).then(() => {
const { fullPath } = view
this.$nextTick(() => {
this.$router.replace({
path: '/redirect' + fullPath
})
})
})
},
closeSelectedTag(view) {
this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
if (this.isActive(view)) {
this.toLastView(visitedViews, view)
}
})
},
closeOthersTags() {
this.$router.push(this.selectedTag)
this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
this.moveToCurrentTag()
})
},
closeAllTags(view) {
this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
if (this.affixTags.some(tag => tag.path === view.path)) {
return
}
this.toLastView(visitedViews, view)
})
},
toLastView(visitedViews, view) {
const latestView = visitedViews.slice(-1)[0]
if (latestView) {
this.$router.push(latestView.fullPath)
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === 'Dashboard') {
// to reload home page
this.$router.replace({ path: '/redirect' + view.fullPath })
} else {
this.$router.push('/')
}
}
},
openMenu(tag, e) {
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.top = e.clientY
this.visible = true
this.selectedTag = tag
},
closeMenu() {
this.visible = false
},
handleScroll() {
this.closeMenu()
}
}
}
</script>
<style lang="scss" scoped>
.tags-view-container {
height: 34px;
width: 100%;
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 {
.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: #409EFF;
color: #fff;
border-color: #409EFF;
&::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: 3000;
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>
// tagview组件下的ScrollPane.vue
<template>
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
<slot />
</el-scrollbar>
</template>
<script>
const tagAndTagSpacing = 4 // tagAndTagSpacing
export default {
name: 'ScrollPane',
data() {
return {
left: 0
}
},
computed: {
scrollWrapper() {
return this.$refs.scrollContainer.$refs.wrap
}
},
mounted() {
this.scrollWrapper.addEventListener('scroll', this.emitScroll, true)
},
beforeDestroy() {
this.scrollWrapper.removeEventListener('scroll', this.emitScroll)
},
methods: {
handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 40
const $scrollWrapper = this.scrollWrapper
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
},
emitScroll() {
this.$emit('scroll')
},
moveToTarget(currentTag) {
const $container = this.$refs.scrollContainer.$el
const $containerWidth = $container.offsetWidth
const $scrollWrapper = this.scrollWrapper
const tagList = this.$parent.$refs.tag
let firstTag = null
let lastTag = null
// find first tag and last tag
if (tagList.length > 0) {
firstTag = tagList[0]
lastTag = tagList[tagList.length - 1]
}
if (firstTag === currentTag) {
$scrollWrapper.scrollLeft = 0
} else if (lastTag === currentTag) {
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
} else {
// find preTag and nextTag
const currentIndex = tagList.findIndex(item => item === currentTag)
const prevTag = tagList[currentIndex - 1]
const nextTag = tagList[currentIndex + 1]
// the tag's offsetLeft after of nextTag
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
// the tag's offsetLeft before of prevTag
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
}
}
}
}
}
</script>
<style lang="scss" scoped>
.scroll-container {
white-space: nowrap;
position: relative;
overflow: hidden;
width: 100%;
&>>> {
.el-scrollbar__bar {
bottom: 0px;
}
.el-scrollbar__wrap {
height: 49px;
}
}
}
</style>
// vuex下的tagsView.js
const state = {
visitedViews: [],
cachedViews: []
}
const mutations = {
ADD_VISITED_VIEW: (state, view) => {
if (state.visitedViews.some(v => v.path === view.path)) return
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name'
})
)
},
ADD_CACHED_VIEW: (state, view) => {
if (state.cachedViews.includes(view.name)) return
if (!view.meta.noCache) {
state.cachedViews.push(view.name)
}
},
DEL_VISITED_VIEW: (state, view) => {
for (const [i, v] of state.visitedViews.entries()) {
if (v.path === view.path) {
state.visitedViews.splice(i, 1)
break
}
}
},
DEL_CACHED_VIEW: (state, view) => {
const index = state.cachedViews.indexOf(view.name)
index > -1 && state.cachedViews.splice(index, 1)
},
DEL_OTHERS_VISITED_VIEWS: (state, view) => {
state.visitedViews = state.visitedViews.filter(v => {
return v.meta.affix || v.path === view.path
})
},
DEL_OTHERS_CACHED_VIEWS: (state, view) => {
const index = state.cachedViews.indexOf(view.name)
if (index > -1) {
state.cachedViews = state.cachedViews.slice(index, index + 1)
} else {
// if index = -1, there is no cached tags
state.cachedViews = []
}
},
DEL_ALL_VISITED_VIEWS: state => {
// keep affix tags
const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
state.visitedViews = affixTags
},
DEL_ALL_CACHED_VIEWS: state => {
state.cachedViews = []
},
UPDATE_VISITED_VIEW: (state, view) => {
for (let v of state.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
}
}
const actions = {
addView({ dispatch }, view) {
dispatch('addVisitedView', view)
dispatch('addCachedView', view)
},
addVisitedView({ commit }, view) {
commit('ADD_VISITED_VIEW', view)
},
addCachedView({ commit }, view) {
commit('ADD_CACHED_VIEW', view)
},
delView({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delVisitedView', view)
dispatch('delCachedView', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delVisitedView({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_VISITED_VIEW', view)
resolve([...state.visitedViews])
})
},
delCachedView({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_CACHED_VIEW', view)
resolve([...state.cachedViews])
})
},
delOthersViews({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delOthersVisitedViews', view)
dispatch('delOthersCachedViews', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delOthersVisitedViews({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_OTHERS_VISITED_VIEWS', view)
resolve([...state.visitedViews])
})
},
delOthersCachedViews({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_OTHERS_CACHED_VIEWS', view)
resolve([...state.cachedViews])
})
},
delAllViews({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delAllVisitedViews', view)
dispatch('delAllCachedViews', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delAllVisitedViews({ commit, state }) {
return new Promise(resolve => {
commit('DEL_ALL_VISITED_VIEWS')
resolve([...state.visitedViews])
})
},
delAllCachedViews({ commit, state }) {
return new Promise(resolve => {
commit('DEL_ALL_CACHED_VIEWS')
resolve([...state.cachedViews])
})
},
updateVisitedView({ commit }, view) {
commit('UPDATE_VISITED_VIEW', view)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
1
import TagsView from './TagsView'
Vue.component('TagsView', TagsView)
并在store中引入该模块
import tagsView from './modules/tagsView'
const store = new Vuex.Store({
modules: {
app,
settings,
user,
permission,
tagsView
},
getters
})
<template>
<div :class="classObj" class="app-wrapper">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar />
<!-- 放置tabsview -->
<tags-view />
</div>
<app-main />
</div>
</div>
</template>
将以上代码复制到自己的项目中然后根据自己的需要相对应的更改(以上代码只能实现侧边栏点击增加tag标签)
我的项目需求中因为牵扯到侧边栏点击添加tag标签和侧边栏点击出来的表格中点击表格项也要添加相应的标签,所以对上面的代码进行修改
主要的思路是根据fullPath进行判断,因为每个页面的path可能一样,但是fullPath一定不同
// 更改后的TagsView组件下的index.vue
<template>
<div id="tags-view-container" class="tags-view-container">
<el-button icon="el-icon-arrow-left" ref="buttonLeft" class="buttonLeft" @click="swipeToHead" />
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
<el-tooltip v-for="tag in visitedViews" :key="tag.fullPath" :content="tag.title" popper-class="popper123456" placement="top-start" >
<!-- <div>{{tag.title}}</div> -->
<router-link
ref="tag"
:class="isActive(tag)?'active':''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
tag="span"
class="tags-view-item"
@click.middle.native="closeSelectedTag(tag)"
@contextmenu.prevent.native="openMenu(tag,$event)"
>
{{ generateTitle(tag.title) }}
<span v-if="!tag.meta.affix" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
</el-tooltip>
</scroll-pane>
<el-button icon="el-icon-arrow-right" class="buttonRight" @click="swipeToTail" />
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
<!-- <li @click="refreshSelectedTag(selectedTag)">
{{ $t('tagsView.refresh') }}
</li> -->
<li v-if="!(selectedTag.meta&&selectedTag.meta.affix)" @click="closeSelectedTag(selectedTag)">
{{
$t('tagsView.close') }}
</li>
<li @click="closeOthersTags(selectedTag)">
{{ $t('tagsView.closeOthers') }}
</li>
<li @click="closeAllTags(selectedTag)">
{{ $t('tagsView.closeAll') }}
</li>
</ul>
</div>
</template>
<script>
import ScrollPane from './ScrollPane'
import { generateTitle } from '@bsp/utils/i18n'
import path from 'path'
import Utils from '../../assets/utils'
export default {
components: { ScrollPane },
data() {
return {
offset: 0,
visible: false,
top: 0,
left: 0,
selectedTag: {},
affixTags: []
}
},
computed: {
visitedViews() {
return this.$store.state.tagsView.visitedViews
},
routes() {
return this.$store.state.permission.routes
}
},
watch: {
visitedViews(){
this.$nextTick(function(){
this.buttonShow() // 这个方法会在tag标签过长时两端显示前进后退按钮
})
},
$route() {
this.addTags()
this.moveToCurrentTag()
},
visible(value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
} else {
document.body.removeEventListener('click', this.closeMenu)
}
}
},
mounted() {
// debugger
this.initTags()
this.addTags()
},
methods: {
// clickTag(tag){
// console.log(tag.fullPath)
// if(tag.fullPath ==='/approval/tasks-done'){
// taskDone = true
// }else if(tag.fullPath ==='/approval/tasks-end'){
// taskEnd = true
// }else{
// return
// }
// },
buttonShow(){
const totalOffsetLeft = document.querySelector('.el-scrollbar__view').offsetWidth - document.querySelector('.el-scrollbar__wrap').offsetWidth
if(totalOffsetLeft < 0){
document.querySelector('.buttonLeft').style.display="none"
document.querySelector('.buttonRight').style.display="none"
document.querySelector('.tags-view-container').style.padding='0 20px'
}else{
document.querySelector('.buttonLeft').style.display="block"
document.querySelector('.buttonRight').style.display="block"
document.querySelector('.tags-view-container').style.padding=0
}
},
swipeToHead() {
this.offset = this.offset < 100 ? 0 : this.offset - 100
document.querySelector('.el-scrollbar__wrap').scrollTo({ top: 0, left: this.offset, behavior: 'smooth' })
},
swipeToTail() {
const totalOffsetLeft = document.querySelector('.el-scrollbar__view').offsetWidth - document.querySelector('.el-scrollbar__wrap').offsetWidth
this.offset = this.offset + 100 < totalOffsetLeft ? this.offset + 100 : totalOffsetLeft
document.querySelector('.el-scrollbar__wrap').scrollTo({ top: 0, left: this.offset, behavior: 'smooth' })
},
generateTitle, // generateTitle by vue-i18n
isActive(route) {
return route.fullPath === this.$route.fullPath
},
filterAffixTags(routes, basePath = '/') {
let tags = []
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
}
if (route.children) {
const tempTags = this.filterAffixTags(route.children, route.path)
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags]
}
}
})
return tags
},
initTags() {
const affixTags = this.affixTags = this.filterAffixTags(this.routes)
for (const tag of affixTags) {
// Must have tag name
if (tag.name) {
this.$store.dispatch('tagsView/addVisitedView', tag)
}
}
},
addTags() {
const { name } = this.$route
if (this.$route.path === '/temp') {
return
}
if(this.$route.query.enableRestart!==undefined){
this.$route.meta.tagtitle = this.$store.getters.btnName
this.$store.dispatch('tagsView/addView', this.$route)
}else{
if (name) {
this.$store.dispatch('tagsView/addView', this.$route)
}else{
this.$route.meta.tagtitle = this.$store.getters.btnName
this.$store.dispatch('tagsView/addView', this.$route)
}
}
return false
},
moveToCurrentTag() {
const tags = this.$refs.tag
this.$nextTick(() => {
for (const tag of tags) {
if (tag.to.fullPath === this.$route.fullPath) {
this.$refs.scrollPane.moveToTarget(tag)
if (tag.to.fullPath !== this.$route.fullPath) {
this.$store.dispatch('tagsView/updateVisitedView', this.$route)
}
break
}
}
})
},
refreshSelectedTag(view) {
this.$store.dispatch('tagsView/delCachedView', view).then(() => {
const { fullPath } = view
this.$nextTick(() => {
this.$router.replace({
path: '/redirect' + fullPath
})
})
})
},
closeSelectedTag(view) {
if(view.fullPath !== this.$route.fullPath){
this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
if (this.isActive(view)) {
this.toLastView(visitedViews, view)
}
})
}else{
if(this.$route.path.split('/').length>3&&this.$route.path.split('/')[2]!=='tasks-end'&&this.$route.path.split('/')[2]!=='tasks-done'&&this.$route.path.split('/')[2]!=='tasks-suspend'&&this.$route.path.split('/')[3]!=='channel-fee-after'&&this.$route.path.split('/')[3]!=='advertisement-fee-after-list'&&this.$route.query.actDefId === 'ShenBaoRen'){
const btn = this.$route.query.actType=='todo'? 'save':'create'
if(this.$route.query.isReadOnly==='1'){ // 如果是可读写的话会弹框 this.$route.query.isReadOnly==='0'可读写 this.$route.query.isReadOnly==='1' 只读
this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
if (this.isActive(view)) {
this.toLastView(visitedViews, view)
}
})
}else{
Utils.$emit('close',btn);
}
}else{
this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
if (this.isActive(view)) {
this.toLastView(visitedViews, view)
}
})
}
}
},
closeOthersTags() {
this.$router.push(this.selectedTag.fullPath)
this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
this.moveToCurrentTag()
})
},
closeAllTags(view) {
this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
if (this.affixTags.some(tag => tag.path === view.path)) {
return
}
this.toLastView(visitedViews, view)
})
},
toLastView(visitedViews, view) {
const latestView = visitedViews.slice(-1)[0]
if (latestView) {
this.$router.push(latestView.fullPath)
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === 'Dashboard') {
// to reload home page
this.$router.replace({ path: '/redirect' + view.fullPath })
} else {
this.$router.push('/')
}
}
},
openMenu(tag, e) {
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.top = e.clientY /3
this.visible = true
this.selectedTag = tag
},
closeMenu() {
this.visible = false
}
}
}
</script>
<style lang="scss" scoped>
button {
padding:4px;
// padding-left: 8px;
// padding-right: 8px;
}
.tags-view-container {
display: flex;
min-height: 34px;
width: 100%;
padding: 0 20px;
background: #fff;
// border-bottom: 1px solid #d8dce5;
background-color: #f5f5f5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
.tags-view-wrapper {
background-color: #fff;
border-bottom: 1px solid #d8dce5;
.tags-view-item {
display: inline-block;
position: relative;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: 200px;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 16px 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: #409eff;
color: #fff;
border-color: #409eff;
&::before {
content: '';
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 2px;
}
}
.el-icon-close{
position: absolute;
top: 4px;
right: 0;
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 3000;
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">
// tooptip的自定义属性要在全局设置,不能设置到scoped中
// 在vue框架中使用element,遇到动态添加dom元素的组件时(tooltip, dialog等),在属性popper-class,自定义class时样式不生效,后面经过分析不生效的原因是vue使用scoped后会在当前组件下每个dom元素上添加一个组件唯一标识(例如data-v-fae5bece),class也会编译成data-v-fae5bece,而我们使用popper-class自定义class想写在scoped中,element动态添加的dom上不具备唯一标识data-v-fae5bece,所以样式不会生效只能用全局样式覆盖
.popper123456{
// &:hover{
max-width: 500px !important;
// }
}
//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>
const state = {
visitedViews: [],
cachedViews: [],
delRoute: []
}
const mutations = {
ADD_VISITED_VIEW: (state, view) => {
// debugger
if (view.query.id) {
// debugger
if (state.visitedViews.some(v => v.fullPath === view.fullPath)) {
// debugger
return
}
// debugger
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || view.meta.tagtitle || 'no-name'
})
)
} else if (view.path === '/') {
state.visitedViews = []
} else {
if (view.query.enableRestart !== undefined) {
if (state.visitedViews.some(v => v.fullPath === view.fullPath)) {
return false
} else {
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.tagtitle || view.meta.title || 'no-name'
})
)
}
} else {
if (state.visitedViews.some(v => v.fullPath === view.fullPath)) {
return false
} else {
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || view.meta.tagtitle || 'no-name'
})
)
}
}
}
},
ADD_CACHED_VIEW: (state, view) => {
if (state.cachedViews.includes(view.name)) return
if (state.cachedViews.includes(view.meta.tagtitle)) return
if (!view.meta.noCache) {
state.cachedViews.push(view.name || view.meta.tagtitle)
}
},
// ADD_CACHED_VIEW: (state, view) => {
// if (!state.cachedViews.includes(view.fullPath)) {
// state.cachedViews.push(view.fullPath)
// }
// },
DEL_VISITED_VIEW: (state, view) => {
for (const [i, v] of state.visitedViews.entries()) {
if (v.fullPath === view.fullPath) {
state.visitedViews.splice(i, 1)
state.delRoute.push(view.fullPath)
break
}
}
},
DEL_CACHED_VIEW: (state, view) => {
for (const i of state.cachedViews) {
if (i === view.name || i === view.meta.tagtitle) {
const index = state.cachedViews.indexOf(i)
state.cachedViews.splice(index, 1)
break
}
}
},
// DEL_CACHED_VIEW: (state, view) => {
// for (const i of state.cachedViews) {
// if (i === view.fullPath) {
// state.delRoute[0] = view.fullPath
// const index = state.cachedViews.indexOf(i)
// state.cachedViews.splice(index, 1)
// break
// }
// }
// },
EMPTYDELROUTE: (state) => {
state.delRoute = []
},
DEL_OTHERS_VISITED_VIEWS: (state, view) => {
state.visitedViews = state.visitedViews.filter(v => {
if (v.fullPath !== view.fullPath) {
state.delRoute.push(v.fullPath)
console.log('222222222')
}
console.log(state.delRoute)
return v.meta.affix || v.fullPath === view.fullPath
})
// state.visitedViews.forEach(i => {
// if (i.fullPath !== view.fullPath) {
// state.delRoute.push(i.fullPath)
// }
// })
},
DEL_OTHERS_CACHED_VIEWS: (state, view) => {
for (const i of state.cachedViews) {
if (i === view.name || i === view.meta.tagtitle) {
const index = state.cachedViews.indexOf(i)
state.cachedViews = state.cachedViews.slice(index, index + 1)
break
}
}
},
DEL_ALL_VISITED_VIEWS: (state) => {
// keep affix tags
state.visitedViews.forEach(el => {
state.delRoute.push(el.fullPath)
})
// console.log(state.delRoute)
// console.log('打印了state.delRoute')
const affixTags = state.visitedViews.filter(tag => {
return tag.meta.affix
})
state.visitedViews = affixTags
},
DEL_ALL_CACHED_VIEWS: state => {
state.cachedViews = []
},
UPDATE_VISITED_VIEW: (state, view) => {
for (let v of state.visitedViews) {
if (v.fullPath === view.fullPath) {
v = Object.assign(v, view)
break
}
}
}
}
const actions = {
addView({ dispatch }, view) {
dispatch('addVisitedView', view)
dispatch('addCachedView', view)
},
addVisitedView({ commit }, view) {
commit('ADD_VISITED_VIEW', view)
},
addCachedView({ commit }, view) {
commit('ADD_CACHED_VIEW', view)
},
delView({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delVisitedView', view)
dispatch('delCachedView', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delVisitedView({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_VISITED_VIEW', view)
resolve([...state.visitedViews])
})
},
delCachedView({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_CACHED_VIEW', view)
resolve([...state.cachedViews])
})
},
delOthersViews({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delOthersVisitedViews', view)
dispatch('delOthersCachedViews', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delOthersVisitedViews({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_OTHERS_VISITED_VIEWS', view)
resolve([...state.visitedViews])
})
},
delOthersCachedViews({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_OTHERS_CACHED_VIEWS', view)
resolve([...state.cachedViews])
})
},
delAllViews({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delAllVisitedViews', view)
dispatch('delAllCachedViews', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delAllVisitedViews({ commit, state }) {
return new Promise(resolve => {
commit('DEL_ALL_VISITED_VIEWS')
resolve([...state.visitedViews])
})
},
delAllCachedViews({ commit, state }) {
return new Promise(resolve => {
commit('DEL_ALL_CACHED_VIEWS')
resolve([...state.cachedViews])
})
},
updateVisitedView({ commit }, view) {
commit('UPDATE_VISITED_VIEW', view)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}