Vue3后台管理系统标签页终极解决方案

您所在的位置:网站首页 a标签原理 Vue3后台管理系统标签页终极解决方案

Vue3后台管理系统标签页终极解决方案

2024-01-20 02:45| 来源: 网络整理| 查看: 265

前言

后台管理系统中都会存在一个需求:标签栏导航,顶部标签栏为一个个的,再结合和实现:

然后我们再来监听$route,来判断当前页面是否需要重新加载或者已被缓存。

方案缺陷

该方案主要来自 vue-element-admin,但其项目中这个方案存在如下2个问题:

无法缓存三级以及三级以上路由的问题 动态路由页面,同时打开多个详情页(例:路由为/page/:id的两个详情页/page/1, /page/2),当你使用标签页的刷新功能,刷新/page/1页面时,/page/2的页面缓存也会被刷新清除 解决方案 问题1

该问题目前市面上大部分的后台都处理解决了,主要方案基本都是将三级以及三级以上的路由拍平成二级路由,但菜单展示仍然使用原本的路由层级。

// 原路由 const asyncRoutes = [...] // 降级后的路由,排除component组件字段的深拷贝,不然会导致keep-alive失效 const flatRoutes = getFlatRoutes(deepClone(asyncRoutes, ['component'])) // 三级以及三级以上的路由降级成二级路由 const formatRouter = (routes, basePath = '/', list = [], parent) => { routes.map(item => { item.path = path.resolve(basePath, item.path) const meta = item.meta || {} if (!meta.parent && parent) { meta.parent = parent.path item.meta = meta } if (item.redirect) item.redirect = path.resolve(basePath, item.redirect) if (item.children && item.children.length > 0) { const arr = formatRouter(item.children, item.path, list, item) delete item.children list.concat(arr) } list.push(item) }) return list } // 路由降级 export const getFlatRoutes = (routes) => { return routes.map((child) => { if (child.children && child.children.length > 0) { child.children = formatRouter(child.children, child.path, [], child) } return child }) } 问题2 问题复现

这里以 Vben Admin 后台举例,在其 功能 > Tab带参页面下,进行如下操作:

打开 Tab带参1 菜单,在输入框中随便输入值, 打开 Tab带参2 菜单,也随便输入一个值, 在顶部标签页中来回切换这两个标签页,可以看到你输入的值都被缓存了下来 这时,我们右键其中一个标签页,选择重新加载 你会看到另一个标签页的值也被清空了 原因说明

这是因为 vue 的 组件的 include 默认是优先匹配组件的 name,使得路由 router 的 name 和路由组件的 name 一一对应,来达到缓存效果,但是因为动态路由,他的 router name 都是一样的,所以你刷新其中一个详情页,另一个详情页缓存的内容也会被清空。

解决方案

在 vue2 中,vue 官方提供了一个vm.$destroy() api 可以手动销毁组件实例,让我们能在刷新某个标签页的时候单独处理该详情页的缓存,但这个api在vue3中被移除了,我找到其的替代api $unmounted,但该api是卸载了整个vue3实例,不再适用了。

要解决该问题,还是要回到 组件的 include 原理,要是我们能让组件的 name动态,变成 $route的fullPath,那么就可以很优雅的处理这个问题了。

那么如何让组件的 name动态 呢?要知道,我们的组件 name 都是在组件中定义写死的。

很简单:利用vue的渲染函数,给每个组件包一层wrap就可以啦~

const wrap = (fullPath, component) => { const wrapper = { name: fullPath, render() { return h('div', null, component) }, } return h(wrapper) }

这样就可以做到动态name了,但是,这里我们又遇到一个问题,在切换标签页时,vue会抛出错误:parentComponent.ctx.deactivate is not a function

为了解决这个问题,我们需要根据cachedViews数组,缓存多个wrap,而不是共用一个wrap,具体代码如下

// 自定义name的壳的集合 const cachedWrapperComponents = new Map() // 为keep-alive里的component接收的组件包上一层自定义name的壳 const wrap = (fullPath:, component) => { let wrapper if (cachedWrapperComponents.has(fullPath)) { wrapper = cachedWrapperComponents.get(fullPath) } else { wrapper = { name: fullPath, render() { return h('div', null, component) }, } cachedWrapperComponents.set(fullPath, wrapper) } return h(wrapper) } // 监听cachedViews的变化,当清除标签页缓存时移除相应的 wapper components watch(cachedViews, (fullPaths) => { cachedWrapperComponents.forEach((value, key) => { if (!fullPaths.includes(key)) { cachedWrapperComponents.delete(key) } }) })

关于 组件的问题就到这里结束了,但有心人可能看到了,渲染函数h('div', null, component),我多渲染了一个空的div,这是因为vue3虽然支持了多根节点元素,但 组件要求必须只有1个根节点,不然动画将不会生效。现在你可以在组件中无所顾忌的使用vue3带来的新特性多根节点啦。

致谢

感谢你抽出宝贵的时间阅读这篇文章,以上说的代码已在我的项目vue-mushroom-admin实现了:

wrap 相关代码位于src/layout/components/AppMain.vue 下 将三级以及三级以上的路由拍平成二级路由位于 src/store/permission.ts 下

最后,如果觉得这篇文章对你有帮助的话,请给个 star 再走~~~



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3