修改vue的keep-alive实现仿easyui-页面tab切换

时间:2022-10-23 10:24:10

后台管理页面通常会有tabs切换作为导航

常见实现方式

  1. 通过显示和隐藏div(缺点:无法看到路由)
  2. 通过iframe,其实和显示隐藏区别不大

vue实现方式

因为要在vue中实现,用vue-router和vue中一个keep-alive,但是keep-alive有个缺点,他是用对象来缓存组件,并且是一个抽象组件,所以就稍微修改下。

效果图

修改vue的keep-alive实现仿easyui-页面tab切换

功能

  1. 点击左侧显示toolbar nav
  2. 通过toolbar 切换路由,并保持之前缓存
  3. 关闭toolbar清除缓存,打开后仍可用缓存

项目源代码

https://github.com/slipkinem/vue-admin

实现方式

监听路由的变动,当路由改变时将当前路由添加到一个列表里面。循环此列表生成toolbartabs,给keep-alive添加两个方法。第一个是当keep-alive工作的时候,设置存储keyhook、第二个方法添加通过key删除缓存removeCacheByKey
当点击关闭按钮的时候,调用removeCacheByKey就可以完美解决vue keep-alive无法主动清除缓存的问题了。主要的keep-alive代码:

import _ from 'lodash'

function isDef (val) {
return val !== undefined && val !== null
}

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)) {
return c
}
}
}
}

function getComponentName (opts) {
return opts && (opts.Ctor.options.name || opts.tag)
}

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 pruneCache (cache, current, filter) {
for (const key in cache) {
const cachedNode = cache[key]
if (cachedNode) {
const name = getComponentName(cachedNode.componentOptions)
if (name && !filter(name)) {
if (cachedNode !== current) {
pruneCacheEntry(cachedNode)
}
cache[key] = null
}
}
}
}

function pruneCacheEntry (vnode) {
if (vnode) {
vnode.componentInstance.$destroy()
}
}

export default {
name: 'pk-keep-alive',

props: {
include: [],
exclude: [],
updateComponentsKey: Function
},

created () {
// vue的keep-alive存储对象
this.cache = Object.create(null)
},
// 调用keep-alive组件销毁钩子,组件销毁的时候同时清除缓存
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache[key])
}
},

watch: {
include (val) {
pruneCache(this.cache, this._vnode, name => matches(val, name))
},
exclude (val) {
pruneCache(this.cache, this._vnode, name => !matches(val, name))
}
},

render () {
const vnode = getFirstComponentChild(this.$slots.default)
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
const name = getComponentName(componentOptions)
if (name && (
(this.include && !matches(this.include, name)) ||
(this.exclude && matches(this.exclude, name))
)) {
return vnode
}
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
// 添加获取key的外部hook
this.updateComponentsKey && this.updateComponentsKey(key)
if (this.cache[key]) {
vnode.componentInstance = this.cache[key].componentInstance
} else {
this.cache[key] = vnode
}
vnode.data.keepAlive = true
}
return vnode
},
methods: {
// 通过cache的key删除对应的缓存
removeCacheByKey (key) {
pruneCacheEntry(this.cache[key])
this.cache[key] = null
}
}
}