开发喵星球

若依如何缓存子页面页签(143)

例如,字典管理,当我们打开多个子页签tab1和tab2,关闭tab2后,但是tab1缓存也被清空重置了,我们可以通过如下方法解决。

1、在src\layout\components下新建目录KeepAlive创建文件index.js

/**
 * 验证数据类型是否是正则
 * @param v
 * @returns {boolean}
 */
function isRegExp (v) {
  return Object.prototype.toString.call(v) === '[object RegExp]'
}

/**
 * 移除数组中指定的项
 * @param arr
 * @param item
 * @returns {*|{}|number|Array|*[]|[]|T[]}
 */
export function remove (arr, item) {
  if (arr.length) {
    const index = arr.indexOf(item)
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

/**
 * 判断数据是否定义了
 * @param v
 * @returns {boolean}
 */
function isDef (v) {
  return v !== undefined && v !== null
}

function isAsyncPlaceholder (node) {
  return node.isComment && node.asyncFactory
}

/**
 * 获取KeepAlive下的第一个子组件
 * @param children
 * @returns {*}
 */
function getFirstComponentChild (children) {
  if (Array.isArray(children)) {
    for (let i = 0; i < children.length; i++) {
      const c = children[i]
      if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
        return c
      }
    }
  }
}

/**
 * 匹配缓存的页面组件
 * @param pattern
 * @param name
 * @returns {boolean|*}
 */
function matches (pattern, name) {
  if (Array.isArray(pattern)) {
    return pattern.indexOf(name) > -1
  } else if (typeof pattern === 'string') {
    return pattern.split(',').indexOf(name) > -1
  } else if (isRegExp(pattern)) {
    return pattern.test(name)
  }
  /* istanbul ignore next */
  return false
}

/**
 * 原先对于没有设置组件name值的,设置为路由的name
 * 现在我们直接取fullPath为name
 * @param {*} opts
 */
function getComponentName (opts) {
  // return (opts && opts.Ctor.options.name) || this.route.name
  return this.route.fullPath
}

/**
 * 删除缓存
 * @param keepAliveInstance
 * @param filter
 */
function pruneCache (keepAliveInstance, filter) {
  const { cache, keys, _vnode } = keepAliveInstance
  Object.keys(cache).forEach(key => {
    const cachedNode = cache[key]
    if (cachedNode) {
      if (key && !filter(key)) {
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  })
}

/**
 * 删除缓存条目
 * @param cache
 * @param key
 * @param keys
 * @param current
 */
function pruneCacheEntry (cache, key, keys, current) {
  const cached = cache[key]
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.destroy()
  }
  cache[key] = null
  remove(keys, key)
}

const patternTypes = [String, RegExp, Array]

export default {
  name: 'KeepAlive',
  // abstract: true,
  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },

  created () {
    // Object.create(null)创建一个非常干净且高度可定制的对象
    // 新创建的对象除了自身属性外,原型链上没有任何属性,也就是说没有继承Object的任何东西
    this.cache = Object.create(null)
    this.keys = []
  },

  mounted () {
    this.watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  destroyed () {
    Object.keys(this.cache).forEach(key => {
      pruneCacheEntry(this.cache, key, this.keys)
    })
  },

  render () {
    const slot = this.slots.default
    const vnode = getFirstComponentChild(slot)
    const componentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // 获取组件的名称,此处修改后取fullPath作为name
      const key = getComponentName.call(this, componentOptions)

      const { include, exclude } = this
      // 没有缓存的直接返回vnode
      if (
        // not included
        (include && (!key || !matches(include, key))) ||
        // excluded
        (exclude && key && matches(exclude, key))
      ) {
        return vnode
      }

      const { cache, keys } = this
      if (cache[key]) {
        // 取缓存中的实例作为vnode的实例
        vnode.componentInstance = cache[key].componentInstance
        // 将当前缓存的key设置为最新的,便于后面缓存的数量超了以后删除最老的
        remove(keys, key)
        keys.push(key)
      } else {
        cache[key] = vnode
        keys.push(key)
        // 移除最老的缓存
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }
      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}

2、修改src\layout\components\AppMain.vue

<template>
  <section class="app-main">
    <transition name="fade-transform" mode="out-in">
      <keep-alive :include="cachedViews">
        <router-view v-if="!route.meta.link" :key="key" />
      </keep-alive>
    </transition>
    <iframe-toggle />
  </section>
</template>

<script>
import iframeToggle from "./IframeToggle/index"
import keepAlive from './KeepAlive'

export default {
  name: 'AppMain',
  components: { iframeToggle, keepAlive },
  computed: {
    cachedViews() {
      return this.store.state.tagsView.cachedViews
    },
    key() {
      return this.$route.fullPath
    }
  }
}
</script>
......省略style代码

3、修改src\layout\components\TagsView\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.fullPath"
        :class="isActive(tag)?'active':''"
        :to="{ path: tag.fullPath, query: tag.query, fullPath: tag.fullPath }"
        tag="span"
        class="tags-view-item"
        :style="activeStyle(tag)"
        @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
        @contextmenu.prevent.native="openMenu(tag,event)"
      >
        {{ tag.title }}
        <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)"><i class="el-icon-refresh-right"></i> 刷新页面</li>
      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><i class="el-icon-close"></i> 关闭当前</li>
      <li @click="closeOthersTags"><i class="el-icon-circle-close"></i> 关闭其他</li>
      <li v-if="!isFirstView()" @click="closeLeftTags"><i class="el-icon-back"></i> 关闭左侧</li>
      <li v-if="!isLastView()" @click="closeRightTags"><i class="el-icon-right"></i> 关闭右侧</li>
      <li @click="closeAllTags(selectedTag)"><i class="el-icon-circle-close"></i> 全部关闭</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
    },
    theme() {
      return this.store.state.settings.theme;
    }
  },
  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.fullPath === this.route.fullPath
    },
    activeStyle(tag) {
      if (!this.isActive(tag)) return {};
      return {
        "background-color": this.theme,
        "border-color": this.theme
      };
    },
    isAffix(tag) {
      return tag.meta && tag.meta.affix
    },
    isFirstView() {
      try {
        return this.selectedTag.fullPath === this.visitedViews[1].fullPath || this.selectedTag.fullPath === '/index'
      } catch (err) {
        return false
      }
    },
    isLastView() {
      try {
        return this.selectedTag.fullPath === this.visitedViews[this.visitedViews.length - 1].fullPath
      } catch (err) {
        return false
      }
    },
    filterAffixTags(routes, basePath = '/') {
      let tags = []
      routes.forEach(route => {
        if (route.meta && route.meta.affix) {
          const tagPath = path.resolve(basePath, route.path)
          tags.push({
            fullPath: route.fullPath,
            path: tagPath,
            name: route.name,
            meta: { ...route.meta }
          })
        }
        if (route.children) {
          const tempTags = this.filterAffixTags(route.children, route.fullPath)
          if (tempTags.length >= 1) {
            tags = [...tags, ...tempTags]
          }
        }
      })
      return tags
    },
    initTags() {
      const affixTags = this.affixTags = this.filterAffixTags(this.routes)
      for (const tag of affixTags) {
        this.store.dispatch('tagsView/addVisitedView', tag)
      }
    },
    addTags() {
      const { name } = this.route
      if (name) {
        this.store.dispatch('tagsView/addView', this.route)
        if (this.route.meta.link) {
          this.store.dispatch('tagsView/addIframeView', 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)
            // when query is different then update
            if (tag.to.fullPath !== this.route.fullPath) {
              this.store.dispatch('tagsView/updateVisitedView', this.route)
            }
            break
          }
        }
      })
    },
    refreshSelectedTag(view) {
      this.tab.refreshPage(view);
      if (this.route.meta.link) {
        this.store.dispatch('tagsView/delIframeView', this.route)
      }
    },
    closeSelectedTag(view) {
      this.tab.closePage(view).then(({ visitedViews }) => {
        if (this.isActive(view)) {
          this.toLastView(visitedViews, view)
        }
      })
    },
    closeRightTags() {
      this.tab.closeRightPage(this.selectedTag).then(visitedViews => {
        if (!visitedViews.find(i => i.fullPath === this.route.fullPath)) {
          this.toLastView(visitedViews)
        }
      })
    },
    closeLeftTags() {
      this.tab.closeLeftPage(this.selectedTag).then(visitedViews => {
        if (!visitedViews.find(i => i.fullPath === this.route.fullPath)) {
          this.toLastView(visitedViews)
        }
      })
    },
    closeOthersTags() {
      this.router.push(this.selectedTag).catch(()=>{});
      this.tab.closeOtherPage(this.selectedTag).then(() => {
        this.moveToCurrentTag()
      })
    },
    closeAllTags(view) {
      this.tab.closeAllPage().then(({ visitedViews }) => {
        if (this.affixTags.some(tag => tag.fullPath === this.route.fullPath)) {
          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代码

4、修改src\store\modules\tagsView.js

const state = {
  visitedViews: [],
  cachedViews: [],
  iframeViews: []
}

const mutations = {
  ADD_IFRAME_VIEW: (state, view) => {
    if (state.iframeViews.some(v => v.fullPath === view.fullPath)) return
    state.iframeViews.push(
      Object.assign({}, view, {
        title: view.meta.title || 'no-name'
      })
    )
  },
  ADD_VISITED_VIEW: (state, view) => {
    if (!view.fullPath || state.visitedViews.some(v => v.fullPath === view.fullPath)) return
    state.visitedViews.push(
      Object.assign({}, view, {
        title: view.meta.title || 'no-name'
      })
    )
  },
  ADD_CACHED_VIEW: (state, view) => {
    if (state.cachedViews.includes(view.fullPath)) return
    if (view.meta && !view.meta.noCache) {
      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)
        break
      }
    }
    state.iframeViews = state.iframeViews.filter(item => item.fullPath !== view.fullPath)
  },
  DEL_IFRAME_VIEW: (state, view) => {
    state.iframeViews = state.iframeViews.filter(item => item.fullPath !== view.fullPath)
  },
  DEL_CACHED_VIEW: (state, view) => {
    const index = state.cachedViews.indexOf(view.fullPath)
    index > -1 && state.cachedViews.splice(index, 1)
  },

  DEL_OTHERS_VISITED_VIEWS: (state, view) => {
    state.visitedViews = state.visitedViews.filter(v => {
      return v.meta.affix || v.fullPath === view.fullPath
    })
    state.iframeViews = state.iframeViews.filter(item => item.fullPath === view.fullPath)
  },
  DEL_OTHERS_CACHED_VIEWS: (state, view) => {
    const index = state.cachedViews.indexOf(view.fullPath)
    if (index > -1) {
      state.cachedViews = state.cachedViews.slice(index, index + 1)
    } else {
      state.cachedViews = []
    }
  },
  DEL_ALL_VISITED_VIEWS: state => {
    // keep affix tags
    const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
    state.visitedViews = affixTags
    state.iframeViews = []
  },
  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
      }
    }
  },
  DEL_RIGHT_VIEWS: (state, view) => {
    const index = state.visitedViews.findIndex(v => v.fullPath === view.fullPath)
    if (index === -1) {
      return
    }
    state.visitedViews = state.visitedViews.filter((item, idx) => {
      if (idx <= index || (item.meta && item.meta.affix)) {
        return true
      }
      const i = state.cachedViews.indexOf(item.fullPath)
      if (i > -1) {
        state.cachedViews.splice(i, 1)
      }
      if(item.meta.link) {
        const fi = state.iframeViews.findIndex(v => v.fullPath === item.fullPath)
        state.iframeViews.splice(fi, 1)
      }
      return false
    })
  },
  DEL_LEFT_VIEWS: (state, view) => {
    const index = state.visitedViews.findIndex(v => v.fullPath === view.fullPath)
    if (index === -1) {
      return
    }
    state.visitedViews = state.visitedViews.filter((item, idx) => {
      if (idx >= index || (item.meta && item.meta.affix)) {
        return true
      }
      const i = state.cachedViews.indexOf(item.fullPath)
      if (i > -1) {
        state.cachedViews.splice(i, 1)
      }
      if(item.meta.link) {
        const fi = state.iframeViews.findIndex(v => v.fullPath === item.fullPath)
        state.iframeViews.splice(fi, 1)
      }
      return false
    })
  }
}

....省略其他代码
   
分类:Java/OOP 作者:无限繁荣, 吴蓉 发表于:2024-03-08 21:49:26 阅读量:162
<<   >>


powered by kaifamiao