vue-element-admin icon indicating copy to clipboard operation
vue-element-admin copied to clipboard

多级路由嵌套 keep-alive 失效

Open a13514097 opened this issue 7 years ago • 34 comments

多标签间切换有时能保存之前的数据有时不行

a13514097 avatar Jan 18 '18 06:01 a13514097

image

a13514097 avatar Jan 18 '18 06:01 a13514097

这出现的原因是多级路由导致的,就是router-view嵌套 在层级不同的router-view中切换tag会出现缓存数据失效的问题。比如你在综合table和table内联两者之间切换不会有问题,但你和table之外的页面切换时就会有问题。我之后想想有没有什么解决方案吧。

PanJiaChen avatar Jan 18 '18 06:01 PanJiaChen

你的缓存数据是用什么存的 我之前react 做标签数据是直接存在state里 没有这种问题

a13514097 avatar Jan 18 '18 07:01 a13514097

目前使用 vue 的 keep-alive. 当然你也可以和你之前的做法一样,存在vue的vuex里。

PanJiaChen avatar Jan 18 '18 07:01 PanJiaChen

这是keep-alive的bug 么 网上查了很多资料 都没有这方面的问题

a13514097 avatar Jan 19 '18 10:01 a13514097

解决了 原来是被你设置了cachedViews 删掉cachedViews 所有子页面全都keep-alive 就行了 或者是你cachedViews里面的代码有bug 修复一下 应该是可以的。

a13514097 avatar Jan 19 '18 10:01 a13514097

不算是bug,就如我前面所说的多久层级切换的时候会让缓存失效。你是全页面keep-alive 所以没有这个问题。

PanJiaChen avatar Jan 19 '18 10:01 PanJiaChen

那请问有没有办法使得多级路由的keep-alive依然有效呢? 楼主说的去掉cachedViews,试了下,会导致就算关闭了tag再次打开依然会缓存之前的内容,这不是我想要的。

hoganfan avatar Feb 27 '18 11:02 hoganfan

每个多级目录的component指向同一个.vue,就可以做到不同的多级目录之间共享keep-alive,但是它们与根目录之间还是不能共享keep-alive。即只有同一个router-view之间才能共享keep-alive

hoganfan avatar Feb 27 '18 11:02 hoganfan

@hoganfan https://github.com/PanJiaChen/vue-element-admin/commit/d431de058943ad653029f90892954ad96274fe20 你可以借鉴一下,这个分支一直没有合。

PanJiaChen avatar Feb 27 '18 12:02 PanJiaChen

您好。我这边的解决办法,就是在【tagsView.js】文件的state.cacheViews里,手动加上一级菜单和三级菜单之间,缺失的二级菜单的名字。

如下图:

image

hanshou101 avatar Sep 25 '18 13:09 hanshou101

您好,我这边的解决办法,就是在【tagsView.js】文件的state.cacheViews里,手动加上一级菜单和三级菜单之间,缺失的二级菜单的名字。

如下图:

图片

你好 能看下你的代码吗?

souths avatar Oct 25 '18 08:10 souths

那请问有没有办法使得多级路由的keep-alive依然有效呢? 楼主说的去掉cachedViews,试了下,会导致就算关闭了tag再次打开依然会缓存之前的内容,这不是我想要的。

请问关闭tag,打开后依旧会缓存这个问题你解决了吗,我还发现右键刷新第二次就重定向不到标签了

waittingbar avatar Dec 11 '18 08:12 waittingbar

即使手动追加到cacheViews里面二级菜单router-view的name,在切换到任何一个路由时候,cacheViews里面二级菜单的子页面mounted都将会被执行一次。

TY-LIU avatar Jan 07 '19 07:01 TY-LIU

我也有这个问题,不知道好了吗

xlb avatar May 28 '19 05:05 xlb

您好。我这边的解决办法,就是在【tagsView.js】文件的state.cacheViews里,手动加上一级菜单和三级菜单之间,缺失的二级菜单的名字。

如下图:

image

您好。我这边的解决办法,就是在【tagsView.js】文件的state.cacheViews里,手动加上一级菜单和三级菜单之间,缺失的二级菜单的名字。

如下图:

image

只在state.cachedViews 加上,会在关掉之后的 DEL_ALL_VIEWS中再次将 state.cachedViews 清空,所以在后续清空中继续设置一遍就好了。

DEL_ALL_VIEWS: (state) => { state.visitedViews = [] state.cachedViews = ['empty'] } cachedViews: ['empty']

建议作者可以默认包含一个什么都不做的route-view,然后state.cachedViews 从空数组改为一个包含空route-view的数组

xlb avatar May 28 '19 05:05 xlb

即使手动追加到cacheViews里面二级菜单router-view的name,在切换到任何一个路由时候,cacheViews里面二级菜单的子页面mounted都将会被执行一次。

这个确实会 请问你解决了吗?

cottengc avatar Jul 31 '19 03:07 cottengc

即使手动追加到cacheViews里面二级菜单router-view的name,在切换到任何一个路由时候,cacheViews里面二级菜单的子页面mounted都将会被执行一次。

这个确实会 请问你解决了吗?

这个问题我自己解决了,之前也想了好多办法,比如用vuex来做缓存等等,发现都太麻烦。 最后偶然想到,实际上是不是多级菜单对于项目业务来讲并不重要,只是对于后台菜单显示上才重要,也就是说,多级菜单实际上就是为了界面显示而分成的多级。
既然这样的话我用把显示的菜单和实际的路由分离开就可以了, 菜单的显示继续用多级的菜单数据, 然后router里面实际添加的数据进行格式化一下,全部转换成一级菜单, 这样就不涉及到父菜单需要用router-view来显示子级菜单内容, 所有的页面都共用的一个router-view, 就没这个缓存的问题了。

myshumin avatar Aug 05 '19 05:08 myshumin

即使手动追加到cacheViews里面二级菜单router-view的name,在切换到任何一个路由时候,cacheViews里面二级菜单的子页面mounted都将会被执行一次。

这个确实会 请问你解决了吗?

这个问题我自己解决了,之前也想了好多办法,比如用vuex来做缓存等等,发现都太麻烦。 最后偶然想到,实际上是不是多级菜单对于项目业务来讲并不重要,只是对于后台菜单显示上才重要,也就是说,多级菜单实际上就是为了界面显示而分成的多级。 既然这样的话我用把显示的菜单和实际的路由分离开就可以了, 菜单的显示继续用多级的菜单数据, 然后router里面实际添加的数据进行格式化一下,全部转换成一级菜单, 这样就不涉及到父菜单需要用router-view来显示子级菜单内容, 所有的页面都共用的一个router-view, 就没这个缓存的问题了。

你好,可以看看你的关键代码吗?

cottengc avatar Aug 05 '19 05:08 cottengc

即使手动追加到cacheViews里面二级菜单router-view的name,在切换到任何一个路由时候,cacheViews里面二级菜单的子页面mounted都将会被执行一次。

这个确实会 请问你解决了吗?

这个问题我自己解决了,之前也想了好多办法,比如用vuex来做缓存等等,发现都太麻烦。 最后偶然想到,实际上是不是多级菜单对于项目业务来讲并不重要,只是对于后台菜单显示上才重要,也就是说,多级菜单实际上就是为了界面显示而分成的多级。 既然这样的话我用把显示的菜单和实际的路由分离开就可以了, 菜单的显示继续用多级的菜单数据, 然后router里面实际添加的数据进行格式化一下,全部转换成一级菜单, 这样就不涉及到父菜单需要用router-view来显示子级菜单内容, 所有的页面都共用的一个router-view, 就没这个缓存的问题了。

你好,可以看看你的关键代码吗?

我的不是用vue-element-admin做的,当时比较早,还没有vue-element-admin,自己搭的框架。 解决缓存的问题原理挺简单的, 缓存失效是因为父级需要为子级菜单提供一个容器(router-view), 就造成了多层嵌套keep-alive,如果一个设置了缓存,一个没有设置缓存, 或者从一个子级菜单切换到另外一个不同父容器的菜单,都会造成已缓存的组件被销毁掉。 但是如果都共用一个keep-alive,就能解决这个问题。

我这边的解决办法是这样的,首先将配置好的router,用vuex缓存起来,用作菜单的显示。 然后把router转换一下,全部转换成一级,再router.addRoutes, 这样菜单的显示和实际操作的路由就分离了,一个是多级,一个是一级。 至于面包屑导航,是在router.afterEach, 通过route.name 查找了一遍 菜单的数据重新生成的。

myshumin avatar Aug 05 '19 06:08 myshumin

这个问题解决了吗?

Codezero123 avatar Dec 04 '19 03:12 Codezero123

您好,我这边的解决办法,就是在【tagsView.js】文件的state.cacheViews里,手动加上一级菜单和三级菜单之间,缺失的二级菜单的名字。 如下图: 图片

你好 能看下你的代码吗?

您好。我这边的解决办法,就是在【tagsView.js】文件的state.cacheViews里,手动加上一级菜单和三级菜单之间,缺失的二级菜单的名字。

如下图:

image

使用这种方式的话会有一个问题,假如有两个三级路由依赖这个二级路由的组件的话,需要同时关闭这两个三级路由才会清空缓存,不然你关闭tab重新打开的话缓存依然存在

shanzhaozhen avatar Dec 14 '19 07:12 shanzhaozhen

我做的时候,换了个思路,menu和router是分离的,索性就把数据搞成两套。然后重新生成一套供vue-router使用的始终保持两层的数据,就规避了三层的问题。menu数据还是作者的数据没动。(我项目的菜单登陆时候动态获取的,我在登陆成功拿到菜单数据之后format了两次,有个问题是:第二层级是不能跳转的😂,感觉跟楼上的思路都差不多)

lucacicii avatar Mar 09 '20 10:03 lucacicii

@bluegek 能看下你的具体解决方法吗

tyxcss3 avatar Mar 30 '20 07:03 tyxcss3

原本keep-alive是按照组件名缓存。 如果仅作用于页面组件时,结合vue-router,可改为按照当前路由路径(route.path)做缓存;Fork一份keep-alive.js,将相关部分做修改。但是可能对动态路由无法支持。

rexerwang avatar Jul 30 '20 09:07 rexerwang

我这边将菜单显示跟路由做了分离,目前使用下来没什么问题。

`export const initMenu = (router, menu) => { if (menu.length === 0) { return } const menus = formatRoutes(menu)

const routeMenu = formatRoutes(filterAsyncRouter(menu))

const unfound = { path: '*', redirect: '/404', hidden: true } menus.push(unfound) routeMenu.push(unfound) store.commit('ADD_ROUTERS', menus) // 菜单显示正常

router.addRoutes(routeMenu) }

// 遍历后台传来的路由字符串,转换为组件对象 function filterAsyncRouter (asyncRouterMap, lastRouter = false, type = true) { return JSON.parse(JSON.stringify(asyncRouterMap)).filter(route => { // 处理 vue-router所需要路由 Empty(继承Empty模板的层)的children全部提到上一层 if (type && route.children) { route.children = filterChildren(route.children) } // 拼装路由 if (lastRouter && route.path.indexOf('http') === -1) { route.path = lastRouter.path + '/' + route.path } if (route.children != null && route.children && route.children.length) { route.children = filterAsyncRouter(route.children, route, type) } return true }) } // function filterChildren (childrenMap, lastRouter = false) { var children = [] JSON.parse(JSON.stringify(childrenMap)).forEach((el, index) => { if (el.children && el.children.length) { if (el.component === 'Empty') { el.children.forEach(c => { c.path = el.path + '/' + c.path if (c.children && c.children.length) { children = children.concat(filterChildren(c.children, c)) return } children.push(c) }) childrenMap.splice(index, 1) return } } if (lastRouter) { el.path = lastRouter.path + '/' + el.path } children = children.concat(el) }) return children }`

xlb avatar Aug 10 '20 10:08 xlb

image image image

二级菜单那一层的router-view中再加个keep-alive,同时设置二级菜单的组件name

geekLeopoldFitz avatar Oct 26 '20 05:10 geekLeopoldFitz

这个问题已经解决,思路跟楼上的一样,核心就是给菜单展示和真正的路由区分开来,真正的路由最多到二级公用一个router-view,就能实现缓存了

zhoumingquan avatar Jan 09 '21 08:01 zhoumingquan

// 扁平化路由,在添加路由方法`router.addRoutes`前处理.
// 因keep-alive无法对超过二级的页面保存状态,
// 参见BUG:https://github.com/PanJiaChen/vue-element-admin/issues/406
// 解决方案:显示菜单使用三级,系统路由将三级路由移动到二级
export function flatRoutes(routes:RouteConfig[]){
    let ret = [];
    routes.forEach(it=>{
        const r = {...it,children:[]};
        it.children.forEach(it2=> {
            // 把三级移动到二级,以避免keep-alive无法缓存多级路由的状态
            if (it2.children && it2.children.length > 0) {
                it2.children.forEach(it3 => r.children.push({...it3, children: null}));
                return;
            }
            // 直接添加二级路由
            r.children.push({...it2, children: null});
        });
        ret.push(r);
    });
    return ret;
}

在添加路由前转换:

const finalRoutes = flatRoutes(PermissionModule.dynamicRoutes);
router.addRoutes(finalRoutes);

ixre avatar Jan 12 '21 08:01 ixre

// 扁平化路由,在添加路由方法`router.addRoutes`前处理.
// 因keep-alive无法对超过二级的页面保存状态,
// 参见BUG:https://github.com/PanJiaChen/vue-element-admin/issues/406
// 解决方案:显示菜单使用三级,系统路由将三级路由移动到二级
export function flatRoutes(routes:RouteConfig[]){
    let ret = [];
    routes.forEach(it=>{
        const r = {...it,children:[]};
        it.children.forEach(it2=> {
            // 把三级移动到二级,以避免keep-alive无法缓存多级路由的状态
            if (it2.children && it2.children.length > 0) {
                it2.children.forEach(it3 => r.children.push({...it3, children: null}));
                return;
            }
            // 直接添加二级路由
            r.children.push({...it2, children: null});
        });
        ret.push(r);
    });
    return ret;
}

在添加路由前转换:

const finalRoutes = flatRoutes(PermissionModule.dynamicRoutes);
router.addRoutes(finalRoutes);

完美解决问题

sand1018 avatar May 26 '21 06:05 sand1018