本文已参与「新人创作礼」活动,一起开启掘金创作之路
前言
今天讲一下动态显示路由的实现。如果想看如何实现建议向下滑动至最终实现处,前边是踩坑总结
踩坑记
状态管理器的坑
我在做分用户动态显示查看了很多的文章都是用Vuex记录用户所拥有的路由,然后在首页上通过v-for循环出来。读过我上一篇文章的xdm都知道我继承了pinia,所以我开始使用状态管理器记录当前用户路由。从而引发了三个坑、刷新页面和路由处理、动态路由问题。
pinia刷新页面
我们都知道,状态管理器没有存储数据到缓存中,所以我们在使用状态管理器的时候。往往会有这么一种处理:在路由跳转时获取不到state,在目录遍历时获取不到state。那好我们补充上在登录时设置路由缓存,在pinia获取不到的话就通过缓存获取,这样暂时解决了刷新页面丢失状态的情况。
路由处理
我们如果想要把路由存储在pinia中的话,需要设置路由分为动态路由和静态路由。因为需要一个入口然后在跳转到动态路由的时候addRouter,所以我们在设置路由缓存只设置动态路由到缓存中然后在router.beforeEach()中添加router就行了
动态路由
这个坑放到下边介绍
弃坑原因
补状态的代码太多了,尤其是在router.beforeEach()中,现在是需要补用户信息,加上路由的话同时又要补充路由信息。以后如果需要别的状态信息真的令人崩溃
记录路由到缓存的坑
动态路由
既然状态管理器需要补充状态,那我们就直接取缓存路由吧。想法很美好,现实很残忍。我们现在采用的是按需加载,在定义组件时,其实是没有加载的。现在就出现了一个问题,我设置完用户的路由缓存之后,跳转到指定路由就会发现在控制台中提示没有这个组件。芭比Q了。想要记录路由就不能动态加载,想要压缩加载速度就需要动态加载,这陷入了套娃纠结。
最终实现
就在我套娃纠结emo的时候,一个灵感突然出现在我脑袋中。既然缓存不了路由,那我缓存目录吧!,我了个去,我被我的机智深深的打败了。下边介绍一下我是如何实现的
修改原有的路由代码,定义一个所有的路由状态信息放在route中
这里我们创建一个config.ts文件在router路径下,然后定义所有需要的路由,这里放心,我们在前边的文章已经做好了路由鉴权,完全不用担心会访问到不属于当前人的页面。
import LayOut from '@/components/layout/index.vue'
export const allRouters = [
{
path: '/',
redirect: '/index',
component: LayOut,
meta: { title: '首页' },
children: [
{
hidden: true,
path: '/index',
component: () => import(/* webpackChunkName: "index" */ '@/views/index/index.vue')
}
]
},
{
hidden: true,
path: '/login',
component: () => import(/* webpackChunkName: "login" */ '@/views/login/index.vue')
},
{
path: '/system',
component: LayOut,
meta: { roleCode: 'system', title: '系统管理' },
hidden: false,
children: [
{
path: '/system/user',
meta: { roleCode: 'system-user', title: '用户管理' },
component: () => import(/* webpackChunkName: "system" */ '@/views/system/user/index.vue')
},
{
path: '/system/setting',
meta: { roleCode: 'system-setting', title: '系统设置' },
component: () => import(/* webpackChunkName: "system" */ '@/views/system/setting/index.vue')
}
]
}
]
登录时记录当前人的目录
继续添加config.ts的内容,定义一个根据当前人的权限取路由目录信息的方法,代码如下
interface Menu {
path: ''
children: []
title: ''
}
export const whiteNameRouters = ['/login']
const fillMenuData = (routers, roleCode) => {
let menuArr: Menu[] = []
let length = routers.length
for (var index = 0; index < length; index++) {
let router = routers[index]
let needRoleCode = router.meta?.roleCode
let hidden = router['hidden'] || false
if (hidden) {
continue
}
if (needRoleCode) {
if (roleCode.indexOf(needRoleCode) > -1 && !hidden) {
let childrenRouter: any = []
if (router.children && router.children.length > 0) {
childrenRouter = fillMenuData(router.children, roleCode)
}
menuArr.push({ path: router.path, children: childrenRouter, title: router.meta?.title })
}
} else {
let childrenRouter: any = []
if (router.children && router.children.length > 0) {
childrenRouter = fillMenuData(router.children, roleCode)
}
menuArr.push({ path: router.path, children: childrenRouter, title: router.meta?.title })
}
}
return menuArr
}
export const setMenuStoreByUserCode = (roleCode: string[]) => {
if (!roleCode) {
roleCode = []
}
//code:[add、del]等为按钮权限,此处略去
roleCode = roleCode.filter((item) => item.indexOf(':') === -1)
let menuData = fillMenuData(allRouters, roleCode)
// Session记录异步缓存信息
Session.set('menuInfo', menuData)
}
setMenuStoreByUserCode:根据传入的权限编码获取对应的路由封装成目录信息
fillMenuData:递归方法,如果当前router有子集,取当前的子集目录信息
whiteNameRouters:白名单
登录成功后,调用setMenuStoreByUserCode
const handleLogin = () => {
loginRef.value.validate((vaild) => {
if (vaild) {
//设置加载中防止重复调用接口
loginLoad.value = true
Session.set('token', new Date().getTime())
let username = loginModel.username ? loginModel.username : ''
let userInfo = { userName: '', roleCode: [] as any[] }
switch (username) {
case 'admin':
userInfo = getAdminInfo(username)
break
case 'dept':
userInfo = getDeptAdminInfo(username)
break
default:
userInfo = getCommonUserInfo(username)
break
}
Session.set('userInfo', userInfo)
userInfoStore.setUserInfo(userInfo)
setMenuStoreByUserCode(userInfo.roleCode)
loginLoad.value = false
router.push('/')
}
})
}
目录循环
在进进行完以上几步之后,我们的会话缓存中已经有了目录信息。现在我们在layout左侧加载出来
...
<el-menu class="el-menu-vertical-demo" @open="true" @close="false" :collapse="false" router>
<template v-for="(item, index) in allMenu" :key="index">
<template v-if="!item['hidden']">
<el-sub-menu
v-if="item.children && item.children.filter((item) => !item['hidden']).length > 0"
:index="item.path"
>
<template #title>
<span>{{ item.title }}</span>
</template>
<template v-for="(sub, childIndex) in item.children" :key="index + '-' + childIndex">
<el-menu-item :index="sub.path">
<template #title>{{ sub.title ? sub.title : '' }}</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else :index="item.path">
<template #title>{{ item.title }}</template>
</el-menu-item>
</template>
</template>
</el-menu>
...
import { onMounted, ref } from 'vue'
const allMenu = ref([] as any[])
...
onMounted(() => {
allMenu.value = Session.get('menuInfo')
})
...
注意这里时在onMounted中设置目录信息
到这里我们就实现了在登录admin的时候会显示全部页面,在登录123时只显示首页
结语
真诚的说一句,可能是我的Vuex技术不太到家,状态管理器用的不是很6.建议各位前端同学有时间可以研究一下如何使用pinia实现动态显示
欢迎关注我的掘金账号:juejin.cn/user/261290…
欢迎star我的git项目:gitee.com/liangminghu…
下篇预告:终于搞完了路由动态显示,下期优化一下indexOf的路由判断存在的漏洞,按钮鉴权准备改为基于自定义指令。欢迎各位xdm提交基于pinia的动态目录显示issue。