1. 程式人生 > 其它 >關於tag標籤元件

關於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
}