vue+element 实现tagsView性能

胜利后示例

  1. vuex store去存储浏览过的菜单;
    目录构造
//新建tagsView.jsconst tagsView = {    state: {      visitedViews: [],      cachedViews: []    },    mutations: {      ADD_VISITED_VIEWS: (state, view) => {        if (state.visitedViews.some(v => v.path === view.path)) return        state.visitedViews.push(Object.assign({}, view, {          title: view.meta.title || 'no-name'        }))        if (!view.meta.noCache) {          state.cachedViews.push(view.name)        }      },      DEL_VISITED_VIEWS: (state, view) => {        for (const [i, v] of state.visitedViews.entries()) {          if (v.path === view.path) {            state.visitedViews.splice(i, 1)            break          }        }        for (const i of state.cachedViews) {          if (i === view.name) {            const index = state.cachedViews.indexOf(i)            state.cachedViews.splice(index, 1)            break          }        }      },      DEL_OTHERS_VIEWS: (state, view) => {        for (const [i, v] of state.visitedViews.entries()) {          if (v.path === view.path) {            state.visitedViews = state.visitedViews.slice(i, i + 1)            break          }        }        for (const i of state.cachedViews) {          if (i === view.name) {            const index = state.cachedViews.indexOf(i)            state.cachedViews = state.cachedViews.slice(index, i + 1)            break          }        }      },      DEL_ALL_VIEWS: (state) => {        state.visitedViews = []        state.cachedViews = []      }    },    actions: {      addVisitedViews({ commit }, view) {        commit('ADD_VISITED_VIEWS', view)      },      delVisitedViews({ commit, state }, view) {        return new Promise((resolve) => {          commit('DEL_VISITED_VIEWS', view)          resolve([...state.visitedViews])        })      },      delOthersViews({ commit, state }, view) {        return new Promise((resolve) => {          commit('DEL_OTHERS_VIEWS', view)          resolve([...state.visitedViews])        })      },      delAllViews({ commit, state }) {        return new Promise((resolve) => {          commit('DEL_ALL_VIEWS')          resolve([...state.visitedViews])        })      }    }  }     export default tagsView
  1. 文件夹store中index.js引入方才新建的tagsView.js,并放入modules模块中去
import tagsView from './modules/tagsView'//后面state,mutations,getters,actions...省略 modules: {    tagsView  }
  1. 新建tagsView文件夹(index.vue,scrollPane.vue)目录构造本人定,我是放在了components里的common文件夹;
//index.vue<template>     <div class="tags-view-container">          <scroll-pane class="tags-view-wrapper" ref="scrollPane">              <router-link ref="tag"  :class="isActive(tag)?'active':''" class="tags-view-item"  :to="tag"  @contextmenu.prevent.native="openMenu(tag,$event)" v-for="tag in Array.from(visitedViews)" :key="tag.path" >                {{tag.title}}                  <span class="el-icon-close" @click.prevent.stop='closeSelectedTag(tag)'></span>              </router-link>          </scroll-pane>       <ul class='contextmenu' v-show="visible" :style="{left:left+'px',top:top+'px'}">         <li @click="closeSelectedTag(selectedTag)">敞开</li>         <li @click="closeOthersTags">敞开其余</li>         <li @click="closeAllTags">敞开所有</li>       </ul>     </div></template> <script>//留神文件门路  import ScrollPane from '@/components/common/TagsView/ScrollPane'      export default {      name: "tags-view",      components: { ScrollPane },     data(){          return{            visible: false,            top: 0,            left: 0,            selectedTag: {}          }     },      computed:{          visitedViews(){            console.log('tabView')            console.log(this.$store.state.tagsView)            // console.log(this.$store.state)            return this.$store.state.tagsView.visitedViews          }      },      watch:{         $route(){           this.addViewTags()           this.moveToCurrentTag()         },        visible(value) {          if (value) {            document.body.addEventListener('click', this.closeMenu)          } else {            document.body.removeEventListener('click', this.closeMenu)          }        }      },      mounted() {        this.addViewTags()      },      methods:{          generateRoute(){            // console.log(this.$route.name)            if (this.$route.path) {              return this.$route            }            return false          },        isActive(route) {          return route.path === this.$route.path        },        addViewTags() {          const route = this.generateRoute()          // console.log(route)          if (!route) {            return false          }          this.$store.dispatch('addVisitedViews', route)        },        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.$el)                break              }            }          })        },        closeSelectedTag(view) {          this.$store.dispatch('delVisitedViews', view).then((views) => {            if (this.isActive(view)) {              const latestView = views.slice(-1)[0]              if (latestView) {                this.$router.push(latestView)              } else {                this.$router.push('/')              }            }          })        },        closeOthersTags() {          this.$router.push(this.selectedTag)          this.$store.dispatch('delOthersViews', this.selectedTag).then(() => {            this.moveToCurrentTag()          })        },        closeAllTags() {          this.$store.dispatch('delAllViews')          this.$router.push('/')        },        openMenu(tag, e) {          this.visible = true          this.selectedTag = tag          const offsetLeft = this.$el.getBoundingClientRect().left // container margin left          this.left = e.clientX - offsetLeft + 15 // 15: margin right          this.top = e.clientY        },        closeMenu() {          this.visible = false        }      }    }</script> <style scoped>    .tags-view-wrapper {      background: #fff;      height: 54px;      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-item {        display: inline-block;        position: relative;        height: 30px;        line-height: 30px;        border: 1px solid #d8dce5;        color: #495060;        background: #fff;        padding: 0 8px;        font-size: 12px;        margin-left: 5px;        margin-top: 4px;    }    .tags-view-item:first-of-type {          margin-left: 15px;    }    .tags-view-item.active{      background-color: #42b983;      color: #fff;      border-color: #42b983;    }    .tags-view-item.active::before{       content: '';      background: #fff;      display: inline-block;      width: 8px;      height: 8px;      border-radius: 50%;      position: relative;      top: -1px;      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);    }  .contextmenu li{      margin: 0;      padding: 7px 16px;      cursor: pointer;  }  .contextmenu li:hover{    background: #eee;  }  .tags-view-item .el-icon-close{    width: 16px;    height: 16px;    border-radius: 50%;    text-align: center;    transition: all .3s cubic-bezier(.645, .045, .355, 1);    transform-origin: 100% 50%;  }  .el-icon-close:before{    transform: scale(.6);    display: inline-block;  }</style> 
//ScrollPane.vue<template>  <div class="scroll-container" ref="scrollContainer" @wheel.prevent="handleScroll">    <div class="scroll-wrapper" ref="scrollWrapper" :style="{left: left + 'px'}">      <slot></slot>    </div>  </div></template> <script>  const padding = 15 // tag's padding   export default {    name: 'scrollPane',    data() {      return {        left: 10      }    },    methods: {      handleScroll(e) {        const eventDelta = e.wheelDelta || -e.deltaY * 3//wheelDelta:-120;deltaY:-120        const $container = this.$refs.scrollContainer//里面的container        const $containerWidth = $container.offsetWidth//里面的container的宽度        const $wrapper = this.$refs.scrollWrapper//外面        const $wrapperWidth = $wrapper.offsetWidth//外面的宽度         if (eventDelta > 0) {          this.left = Math.min(0, this.left + eventDelta)//min() 办法可返回指定的数字中带有最低值的数字。        } else {          if ($containerWidth - padding < $wrapperWidth) {            if (this.left < -($wrapperWidth - $containerWidth + padding)) {              this.left = this.left            } else {              this.left = Math.max(this.left + eventDelta, $containerWidth - $wrapperWidth - padding)            }          } else {            this.left = 0          }        }      },      moveToTarget($target) {        const $container = this.$refs.scrollContainer        const $containerWidth = $container.offsetWidth        const $targetLeft = $target.offsetLeft        const $targetWidth = $target.offsetWidth         if ($targetLeft < -this.left) {          // tag in the left          this.left = -$targetLeft + padding        } else if ($targetLeft + padding > -this.left && $targetLeft + $targetWidth < -this.left + $containerWidth - padding) {          // tag in the current view          // eslint-disable-line        } else {          // tag in the right          this.left = -($targetLeft - ($containerWidth - $targetWidth) + padding)        }      }    }  }</script> <style scoped>  .scroll-container {    white-space: nowrap;    position: relative;    overflow: hidden;    width: 100%;  }  .scroll-wrapper {    position: absolute;    top: 8px;  }</style>

注意事项:

  1. 留神文件引入的门路;
  2. router.js 路由要加上meta,name。