Bläddra i källkod

解决切换标签页会刷新的问题

黄梓星 2 år sedan
förälder
incheckning
5e8473923e

+ 3 - 2
src/layout/components/AppMain.vue

@@ -11,16 +11,17 @@
 
 <script>
 import iframeToggle from "./IframeToggle/index"
+import keepAlive from './KeepAlive'
 
 export default {
   name: 'AppMain',
-  components: { iframeToggle },
+  components: { iframeToggle, keepAlive },
   computed: {
     cachedViews() {
       return this.$store.state.tagsView.cachedViews
     },
     key() {
-      return this.$route.path
+      return this.$route.fullPath
     }
   }
 }

+ 186 - 0
src/layout/components/KeepAlive/index.js

@@ -0,0 +1,186 @@
+/**
+ * 验证数据类型是否是正则
+ * @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])
+  }
+}

+ 9 - 9
src/layout/components/TagsView/index.vue

@@ -4,9 +4,9 @@
       <router-link
         v-for="tag in visitedViews"
         ref="tag"
-        :key="tag.path"
+        :key="tag.fullPath"
         :class="isActive(tag)?'active':''"
-        :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
+        :to="{ path: tag.fullPath, query: tag.query, fullPath: tag.fullPath }"
         tag="span"
         class="tags-view-item"
         :style="activeStyle(tag)"
@@ -73,7 +73,7 @@ export default {
   },
   methods: {
     isActive(route) {
-      return route.path === this.$route.path
+      return route.fullPath === this.$route.fullPath
     },
     activeStyle(tag) {
       if (!this.isActive(tag)) return {};
@@ -105,14 +105,14 @@ export default {
         if (route.meta && route.meta.affix) {
           const tagPath = path.resolve(basePath, route.path)
           tags.push({
-            fullPath: tagPath,
+            fullPath: route.fullPath,
             path: tagPath,
             name: route.name,
             meta: { ...route.meta }
           })
         }
         if (route.children) {
-          const tempTags = this.filterAffixTags(route.children, route.path)
+          const tempTags = this.filterAffixTags(route.children, route.fullPath)
           if (tempTags.length >= 1) {
             tags = [...tags, ...tempTags]
           }
@@ -124,9 +124,9 @@ export default {
       const affixTags = this.affixTags = this.filterAffixTags(this.routes)
       for (const tag of affixTags) {
         // Must have tag name
-        if (tag.name) {
+        // if (tag.name) {
           this.$store.dispatch('tagsView/addVisitedView', tag)
-        }
+        // }
       }
     },
     addTags() {
@@ -143,7 +143,7 @@ export default {
       const tags = this.$refs.tag
       this.$nextTick(() => {
         for (const tag of tags) {
-          if (tag.to.path === this.$route.path) {
+          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) {
@@ -189,7 +189,7 @@ export default {
     },
     closeAllTags(view) {
       this.$tab.closeAllPage().then(({ visitedViews }) => {
-        if (this.affixTags.some(tag => tag.path === this.$route.path)) {
+        if (this.affixTags.some(tag => tag.fullPath === this.$route.fullPath)) {
           return
         }
         this.toLastView(visitedViews, view)

+ 18 - 18
src/store/modules/tagsView.js

@@ -6,7 +6,7 @@ const state = {
 
 const mutations = {
   ADD_IFRAME_VIEW: (state, view) => {
-    if (state.iframeViews.some(v => v.path === view.path)) return
+    if (state.iframeViews.some(v => v.fullPath === view.fullPath)) return
     state.iframeViews.push(
       Object.assign({}, view, {
         title: view.meta.title || 'no-name'
@@ -14,7 +14,7 @@ const mutations = {
     )
   },
   ADD_VISITED_VIEW: (state, view) => {
-    if (state.visitedViews.some(v => v.path === view.path)) return
+    if (!view.fullPath || state.visitedViews.some(v => v.fullPath === view.fullPath)) return
     state.visitedViews.push(
       Object.assign({}, view, {
         title: view.meta.title || 'no-name'
@@ -22,36 +22,36 @@ const mutations = {
     )
   },
   ADD_CACHED_VIEW: (state, view) => {
-    if (state.cachedViews.includes(view.name)) return
+    if (state.cachedViews.includes(view.fullPath)) return
     if (view.meta && !view.meta.noCache) {
-      state.cachedViews.push(view.name)
+      state.cachedViews.push(view.fullPath)
     }
   },
   DEL_VISITED_VIEW: (state, view) => {
     for (const [i, v] of state.visitedViews.entries()) {
-      if (v.path === view.path) {
+      if (v.fullPath === view.fullPath) {
         state.visitedViews.splice(i, 1)
         break
       }
     }
-    state.iframeViews = state.iframeViews.filter(item => item.path !== view.path)
+    state.iframeViews = state.iframeViews.filter(item => item.fullPath !== view.fullPath)
   },
   DEL_IFRAME_VIEW: (state, view) => {
-    state.iframeViews = state.iframeViews.filter(item => item.path !== view.path)
+    state.iframeViews = state.iframeViews.filter(item => item.fullPath !== view.fullPath)
   },
   DEL_CACHED_VIEW: (state, view) => {
-    const index = state.cachedViews.indexOf(view.name)
+    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.path === view.path
+      return v.meta.affix || v.fullPath === view.fullPath
     })
-    state.iframeViews = state.iframeViews.filter(item => item.path === view.path)
+    state.iframeViews = state.iframeViews.filter(item => item.fullPath === view.fullPath)
   },
   DEL_OTHERS_CACHED_VIEWS: (state, view) => {
-    const index = state.cachedViews.indexOf(view.name)
+    const index = state.cachedViews.indexOf(view.fullPath)
     if (index > -1) {
       state.cachedViews = state.cachedViews.slice(index, index + 1)
     } else {
@@ -69,14 +69,14 @@ const mutations = {
   },
   UPDATE_VISITED_VIEW: (state, view) => {
     for (let v of state.visitedViews) {
-      if (v.path === view.path) {
+      if (v.fullPath === view.fullPath) {
         v = Object.assign(v, view)
         break
       }
     }
   },
   DEL_RIGHT_VIEWS: (state, view) => {
-    const index = state.visitedViews.findIndex(v => v.path === view.path)
+    const index = state.visitedViews.findIndex(v => v.fullPath === view.fullPath)
     if (index === -1) {
       return
     }
@@ -84,19 +84,19 @@ const mutations = {
       if (idx <= index || (item.meta && item.meta.affix)) {
         return true
       }
-      const i = state.cachedViews.indexOf(item.name)
+      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.path === item.path)
+        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.path === view.path)
+    const index = state.visitedViews.findIndex(v => v.fullPath === view.fullPath)
     if (index === -1) {
       return
     }
@@ -104,12 +104,12 @@ const mutations = {
       if (idx >= index || (item.meta && item.meta.affix)) {
         return true
       }
-      const i = state.cachedViews.indexOf(item.name)
+      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.path === item.path)
+        const fi = state.iframeViews.findIndex(v => v.fullPath === item.fullPath)
         state.iframeViews.splice(fi, 1)
       }
       return false