diff --git a/src/components/cache/AKeepAlive.js b/src/components/cache/AKeepAlive.js new file mode 100644 index 00000000..f0f412e8 --- /dev/null +++ b/src/components/cache/AKeepAlive.js @@ -0,0 +1,141 @@ +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]) + } +} diff --git a/src/layouts/BlankView.vue b/src/layouts/BlankView.vue index 742f3b39..6e3f81a6 100644 --- a/src/layouts/BlankView.vue +++ b/src/layouts/BlankView.vue @@ -1,9 +1,6 @@ @@ -15,7 +12,7 @@ export default { name: 'BlankView', components: {PageToggleTransition}, computed: { - ...mapState('setting', ['multiPage', 'animate', 'dustbins']) + ...mapState('setting', ['multiPage', 'animate']) } } diff --git a/src/layouts/PageView.vue b/src/layouts/PageView.vue index 725f86ac..6b142707 100644 --- a/src/layouts/PageView.vue +++ b/src/layouts/PageView.vue @@ -4,10 +4,7 @@ - - - @@ -26,7 +23,7 @@ export default { } }, computed: { - ...mapState('setting', ['isMobile', 'multiPage', 'animate', 'dustbins']), + ...mapState('setting', ['isMobile', 'multiPage', 'animate']), desc() { return this.page.desc }, diff --git a/src/layouts/tabs/TabsView.vue b/src/layouts/tabs/TabsView.vue index 5c3aa3ae..2f35161a 100644 --- a/src/layouts/tabs/TabsView.vue +++ b/src/layouts/tabs/TabsView.vue @@ -17,9 +17,9 @@
- - - + + +
@@ -32,20 +32,23 @@ import Contextmenu from '@/components/menu/Contextmenu' 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') }, @@ -67,6 +70,7 @@ export default { }, mounted () { this.correctPageMinHeight(-this.tabsOffset) + this.cachedKeys.push(this.$refs.tabContent.$vnode.key) }, beforeDestroy() { window.removeEventListener('page:close', this.closePageListener) @@ -75,10 +79,12 @@ export default { 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) } }, @@ -107,9 +113,9 @@ export default { 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) { @@ -136,56 +142,40 @@ export default { }, 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) { return this.$t(getI18nKey(page.matched[page.matched.length - 1].path)) }, @@ -194,7 +184,7 @@ export default { const closePath = typeof closeRoute === 'string' ? closeRoute : closeRoute.path this.remove(closePath, nextRoute) }, - ...mapMutations('setting', ['setDustbins', 'correctPageMinHeight']) + ...mapMutations('setting', ['correctPageMinHeight']) } } /** diff --git a/src/store/modules/setting.js b/src/store/modules/setting.js index 9fd65b3e..7f0c22b3 100644 --- a/src/store/modules/setting.js +++ b/src/store/modules/setting.js @@ -6,7 +6,6 @@ export default { isMobile: false, animates: ADMIN.animates, palettes: ADMIN.palettes, - dustbins: [], pageMinHeight: 0, menuData: [], ...config, @@ -42,9 +41,6 @@ export default { setHideSetting(state, hideSetting) { state.hideSetting = hideSetting }, - setDustbins(state, dustbins) { - state.dustbins = dustbins - }, correctPageMinHeight(state, minHeight) { state.pageMinHeight += minHeight }, diff --git a/src/utils/util.js b/src/utils/util.js new file mode 100644 index 00000000..96b965c7 --- /dev/null +++ b/src/utils/util.js @@ -0,0 +1,21 @@ +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