例如,字典管理,当我们打开多个子页签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
})
}
}
....省略其他代码
powered by kaifamiao