fix: the cache problem in tabs mode;:bug:
修复:多页签模式下的缓存问题;
New file |
| | |
| | | import {isDef, isRegExp, remove} from '@/utils/util' |
| | | |
| | | const patternTypes = [String, RegExp, Array] |
| | | |
| | | 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 |
| | | } |
| | | |
| | | function getComponentName (opts) { |
| | | return opts && (opts.Ctor.options.name || opts.tag) |
| | | } |
| | | |
| | | 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) || c.isAsyncPlaceholder)) { |
| | | return c |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | function pruneCache (keepAliveInstance, filter) { |
| | | const { cache, keys, _vnode } = keepAliveInstance |
| | | for (const key in cache) { |
| | | const cachedNode = cache[key] |
| | | if (cachedNode) { |
| | | const name = getComponentName(cachedNode.componentOptions) |
| | | if (name && !filter(name)) { |
| | | pruneCacheEntry(cache, key, keys, _vnode) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | 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) |
| | | } |
| | | |
| | | export default { |
| | | name: 'AKeepAlive', |
| | | abstract: true, |
| | | model: { |
| | | prop: 'clearCaches', |
| | | event: 'clear', |
| | | }, |
| | | props: { |
| | | include: patternTypes, |
| | | exclude: patternTypes, |
| | | max: [String, Number], |
| | | clearCaches: Array |
| | | }, |
| | | |
| | | watch: { |
| | | clearCaches: function(val) { |
| | | if (val && val.length > 0) { |
| | | const {cache, keys} = this |
| | | val.forEach(key => { |
| | | pruneCacheEntry(cache, key, keys, this._vnode) |
| | | }) |
| | | this.$emit('clear', []) |
| | | } |
| | | } |
| | | }, |
| | | |
| | | created() { |
| | | this.cache = Object.create(null) |
| | | this.keys = [] |
| | | }, |
| | | |
| | | destroyed () { |
| | | for (const key in this.cache) { |
| | | pruneCacheEntry(this.cache, key, this.keys) |
| | | } |
| | | }, |
| | | |
| | | mounted () { |
| | | this.$watch('include', val => { |
| | | pruneCache(this, name => matches(val, name)) |
| | | }) |
| | | this.$watch('exclude', val => { |
| | | pruneCache(this, name => !matches(val, name)) |
| | | }) |
| | | }, |
| | | |
| | | render () { |
| | | const slot = this.$slots.default |
| | | const vnode = getFirstComponentChild(slot) |
| | | const componentOptions = vnode && vnode.componentOptions |
| | | if (componentOptions) { |
| | | // check pattern |
| | | const name = getComponentName(componentOptions) |
| | | const { include, exclude } = this |
| | | if ( |
| | | // not included |
| | | (include && (!name || !matches(include, name))) || |
| | | // excluded |
| | | (exclude && name && matches(exclude, name)) |
| | | ) { |
| | | return vnode |
| | | } |
| | | |
| | | const { cache, keys } = this |
| | | const key = vnode.key == null |
| | | // same constructor may get registered as different local components |
| | | // so cid alone is not enough (#3269) |
| | | ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') |
| | | : vnode.key |
| | | if (cache[key]) { |
| | | vnode.componentInstance = cache[key].componentInstance |
| | | // make current key freshest |
| | | remove(keys, key) |
| | | keys.push(key) |
| | | } else { |
| | | cache[key] = vnode |
| | | keys.push(key) |
| | | // prune oldest entry |
| | | if (this.max && keys.length > parseInt(this.max)) { |
| | | pruneCacheEntry(cache, keys[0], keys, this._vnode) |
| | | } |
| | | } |
| | | |
| | | vnode.data.keepAlive = true |
| | | } |
| | | return vnode || (slot && slot[0]) |
| | | } |
| | | } |
| | |
| | | <template> |
| | | <page-toggle-transition :disabled="animate.disabled" :animate="animate.name" :direction="animate.direction"> |
| | | <keep-alive :exclude="dustbins" v-if="multiPage"> |
| | | <router-view /> |
| | | </keep-alive> |
| | | <router-view v-else /> |
| | | <router-view /> |
| | | </page-toggle-transition> |
| | | </template> |
| | | |
| | |
| | | name: 'BlankView', |
| | | components: {PageToggleTransition}, |
| | | computed: { |
| | | ...mapState('setting', ['multiPage', 'animate', 'dustbins']) |
| | | ...mapState('setting', ['multiPage', 'animate']) |
| | | } |
| | | } |
| | | </script> |
| | |
| | | <img :src="extraImage"/> |
| | | </div> |
| | | <page-toggle-transition :disabled="animate.disabled" :animate="animate.name" :direction="animate.direction"> |
| | | <keep-alive :exclude="dustbins" v-if="multiPage"> |
| | | <router-view ref="page" /> |
| | | </keep-alive> |
| | | <router-view ref="page" v-else /> |
| | | </page-toggle-transition> |
| | | </page-layout> |
| | | </template> |
| | |
| | | } |
| | | }, |
| | | computed: { |
| | | ...mapState('setting', ['isMobile', 'multiPage', 'animate', 'dustbins']), |
| | | ...mapState('setting', ['isMobile', 'multiPage', 'animate']), |
| | | desc() { |
| | | return this.page.desc |
| | | }, |
| | |
| | | </a-tabs> |
| | | <div class="tabs-view-content" :style="`margin-top: ${multiPage ? -24 : 0}px`"> |
| | | <page-toggle-transition :disabled="animate.disabled" :animate="animate.name" :direction="animate.direction"> |
| | | <keep-alive :exclude="dustbins" v-if="multiPage"> |
| | | <router-view :key="$route.fullPath" /> |
| | | </keep-alive> |
| | | <a-keep-alive v-if="multiPage" v-model="clearCaches"> |
| | | <router-view ref="tabContent" :key="$route.fullPath" /> |
| | | </a-keep-alive> |
| | | <router-view v-else /> |
| | | </page-toggle-transition> |
| | | </div> |
| | |
| | | import PageToggleTransition from '@/components/transition/PageToggleTransition' |
| | | import {mapState, mapMutations} from 'vuex' |
| | | import {getI18nKey} from '@/utils/routerUtil' |
| | | import AKeepAlive from '@/components/cache/AKeepAlive' |
| | | |
| | | export default { |
| | | name: 'TabsView', |
| | | i18n: require('./i18n'), |
| | | components: { PageToggleTransition, Contextmenu, AdminLayout }, |
| | | components: { PageToggleTransition, Contextmenu, AdminLayout , AKeepAlive }, |
| | | data () { |
| | | return { |
| | | clearCaches: [], |
| | | pageList: [], |
| | | cachedKeys: [], |
| | | activePage: '', |
| | | menuVisible: false |
| | | } |
| | | }, |
| | | computed: { |
| | | ...mapState('setting', ['multiPage', 'animate', 'layout', 'dustbins']), |
| | | ...mapState('setting', ['multiPage', 'animate', 'layout']), |
| | | menuItemList() { |
| | | return [ |
| | | { key: '1', icon: 'vertical-right', text: this.$t('closeLeft') }, |
| | |
| | | }, |
| | | mounted () { |
| | | this.correctPageMinHeight(-this.tabsOffset) |
| | | this.cachedKeys.push(this.$refs.tabContent.$vnode.key) |
| | | }, |
| | | beforeDestroy() { |
| | | window.removeEventListener('page:close', this.closePageListener) |
| | |
| | | watch: { |
| | | '$route': function (newRoute) { |
| | | this.activePage = newRoute.fullPath |
| | | this.putCache(newRoute) |
| | | if (!this.multiPage) { |
| | | this.pageList = [newRoute] |
| | | } else if (this.pageList.findIndex(item => item.fullPath == newRoute.fullPath) == -1) { |
| | | this.$nextTick(() => { |
| | | this.cachedKeys.push(this.$refs.tabContent.$vnode.key) |
| | | }) |
| | | this.pageList.push(newRoute) |
| | | } |
| | | }, |
| | |
| | | return this.$message.warning(this.$t('warn')) |
| | | } |
| | | let index = this.pageList.findIndex(item => item.fullPath === key) |
| | | let pageRoute = this.pageList[index] |
| | | this.clearCache(pageRoute) |
| | | this.pageList = this.pageList.filter(item => item.fullPath !== key) |
| | | //清除缓存 |
| | | this.clearCaches = this.cachedKeys.splice(index, 1) |
| | | this.pageList.splice(index, 1) |
| | | if (next) { |
| | | this.$router.push(next) |
| | | } else if (key === this.activePage) { |
| | |
| | | }, |
| | | closeOthers (pageKey) { |
| | | const index = this.pageList.findIndex(item => item.fullPath === pageKey) |
| | | // 要关闭的页面清除缓存 |
| | | this.pageList.forEach(item => { |
| | | if (item.fullPath !== pageKey){ |
| | | this.clearCache(item) |
| | | } |
| | | }) |
| | | // 清除缓存 |
| | | this.clearCaches = this.cachedKeys.filter((item, i) => i != index) |
| | | this.cachedKeys = this.cachedKeys.slice(index, index + 1) |
| | | |
| | | this.pageList = this.pageList.slice(index, index + 1) |
| | | this.activePage = this.pageList[0].fullPath |
| | | this.$router.push(this.activePage) |
| | | if (this.activePage != pageKey) { |
| | | this.activePage = pageKey |
| | | this.$router.push(this.activePage) |
| | | } |
| | | }, |
| | | closeLeft (pageKey) { |
| | | const index = this.pageList.findIndex(item => item.fullPath === pageKey) |
| | | // 清除缓存 |
| | | this.pageList.forEach((item, i) => { |
| | | if (i < index) { |
| | | this.clearCache(item) |
| | | } |
| | | }) |
| | | this.clearCaches = this.cachedKeys.filter((item, i) => i < index) |
| | | this.cachedKeys = this.cachedKeys.slice(index) |
| | | |
| | | this.pageList = this.pageList.slice(index) |
| | | if (this.pageList.findIndex(item => item.fullPath === this.activePage) === -1) { |
| | | this.activePage = this.pageList[0].fullPath |
| | | if (!this.pageList.find(item => item.fullPath === this.activePage)) { |
| | | this.activePage = pageKey |
| | | this.$router.push(this.activePage) |
| | | } |
| | | }, |
| | | closeRight (pageKey) { |
| | | const index = this.pageList.findIndex(item => item.fullPath === pageKey) |
| | | // 清除缓存 |
| | | this.pageList.forEach((item, i) => { |
| | | if (i > index) { |
| | | this.clearCache(item) |
| | | } |
| | | }) |
| | | this.clearCaches = this.cachedKeys.filter((item, i) => i > index) |
| | | this.cachedKeys = this.cachedKeys.slice(0, index+1) |
| | | |
| | | this.pageList = this.pageList.slice(0, index + 1) |
| | | if (this.pageList.findIndex(item => item.fullPath === this.activePage) === -1) { |
| | | this.activePage = this.pageList[this.pageList.length - 1].fullPath |
| | | if (!this.pageList.find(item => item.fullPath === this.activePage)) { |
| | | this.activePage = pageKey |
| | | this.$router.push(this.activePage) |
| | | } |
| | | }, |
| | | clearCache(route) { |
| | | const componentName = route.matched.slice(-1)[0].components.default.name |
| | | if (this.dustbins.findIndex(item => item === componentName) === -1) { |
| | | this.setDustbins(this.dustbins.concat(componentName)) |
| | | } |
| | | }, |
| | | putCache(route) { |
| | | const componentName = route.matched.slice(-1)[0].components.default.name |
| | | if (this.dustbins.includes(componentName)) { |
| | | this.setDustbins(this.dustbins.filter(item => item !== componentName)) |
| | | } |
| | | }, |
| | | pageName(page) { |
| | |
| | | const closePath = typeof closeRoute === 'string' ? closeRoute : closeRoute.path |
| | | this.remove(closePath, nextRoute) |
| | | }, |
| | | ...mapMutations('setting', ['setDustbins', 'correctPageMinHeight']) |
| | | ...mapMutations('setting', ['correctPageMinHeight']) |
| | | } |
| | | } |
| | | /** |
| | |
| | | isMobile: false, |
| | | animates: ADMIN.animates, |
| | | palettes: ADMIN.palettes, |
| | | dustbins: [], |
| | | pageMinHeight: 0, |
| | | menuData: [], |
| | | ...config, |
| | |
| | | }, |
| | | setHideSetting(state, hideSetting) { |
| | | state.hideSetting = hideSetting |
| | | }, |
| | | setDustbins(state, dustbins) { |
| | | state.dustbins = dustbins |
| | | }, |
| | | correctPageMinHeight(state, minHeight) { |
| | | state.pageMinHeight += minHeight |
New file |
| | |
| | | export function isDef (v){ |
| | | return v !== undefined && v !== null |
| | | } |
| | | |
| | | /** |
| | | * Remove an item from an array. |
| | | */ |
| | | export function remove (arr, item) { |
| | | if (arr.length) { |
| | | const index = arr.indexOf(item) |
| | | if (index > -1) { |
| | | return arr.splice(index, 1) |
| | | } |
| | | } |
| | | } |
| | | |
| | | export function isRegExp (v) { |
| | | return _toString.call(v) === '[object RegExp]' |
| | | } |
| | | |
| | | const _toString = Object.prototype.toString |