关于tag标签组件

  在项目中遇到这么一个需求,点击侧边栏时会弹出标签,点击页面的表格时也会弹出标签,在网上找的资料只有在侧边栏点击的时候弹标签,没有表格内的内容点击时弹标签,所以自己将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第一步,将组件TagsView目录放置到src/components , 并全局注册

import TagsView from './TagsView'
Vue.component('TagsView', TagsView)

第二步,将Vuex模块tagsView.js放置到 src/store/modules

并在store中引入该模块

import tagsView from './modules/tagsView'
const store = new Vuex.Store({
  modules: {
    app,
    settings,
    user,
    permission,
    tagsView
  },
  getters
})

第三步,在src/layout/Index.vue中引入该组件

<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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值