菜单权限
15.1 路由的拆分
15.1.1 路由分析
菜单的权限:
超级管理员账号:admin atguigu123 拥有全部的菜单、按钮的权限
飞行员账号 硅谷333 111111 不包含权限管理模块、按钮的权限并非全部按钮
同一个项目:不同人(职位是不一样的,他能访问到的菜单、按钮的权限是不一样的)一、目前整个项目一共多少个路由!!!
login(登录页面)、
404(404一级路由)、
任意路由、
首页(/home)、
数据大屏、
权限管理(三个子路由)
商品管理模块(四个子路由)1.1开发菜单权限
---第一步:拆分路由
静态(常量)路由:大家都可以拥有的路由
login、首页、数据大屏、404异步路由:不同的身份有的有这个路由、有的没有
权限管理(三个子路由)
商品管理模块(四个子路由)任意路由:任意路由1.2菜单权限开发思路
目前咱们的项目:任意用户访问大家能看见的、能操作的菜单与按钮都是一样的(大家注册的路由都是一样的)
15.1.2 路由的拆分
//对外暴露配置路由(常量路由)
export const constantRoute = [{//登录路由path: '/login',component: () => import('@/views/login/index.vue'),name: 'login', //命名路由meta: {title: '登录', //菜单标题hidden: true, //路由的标题在菜单中是否隐藏},},{//登录成功以后展示数据的路由path: '/',component: () => import('@/layout/index.vue'),name: 'layout',meta: {hidden: false,},redirect: '/home',children: [{path: '/home',component: () => import('@/views/home/index.vue'),meta: {title: '首页',hidden: false,icon: 'HomeFilled',},},],},{path: '/404',component: () => import('@/views/404/index.vue'),name: '404',meta: {title: '404',hidden: true,},},{path: '/screen',component: () => import('@/views/screen/index.vue'),name: 'Screen',meta: {hidden: false,title: '数据大屏',icon: 'Platform',},},
]//异步路由
export const asnycRoute = [{path: '/acl',component: () => import('@/layout/index.vue'),name: 'Acl',meta: {hidden: false,title: '权限管理',icon: 'Lock',},redirect: '/acl/user',children: [{path: '/acl/user',component: () => import('@/views/acl/user/index.vue'),name: 'User',meta: {hidden: false,title: '用户管理',icon: 'User',},},{path: '/acl/role',component: () => import('@/views/acl/role/index.vue'),name: 'Role',meta: {hidden: false,title: '角色管理',icon: 'UserFilled',},},{path: '/acl/permission',component: () => import('@/views/acl/permission/index.vue'),name: 'Permission',meta: {hidden: false,title: '菜单管理',icon: 'Monitor',},},],},{path: '/product',component: () => import('@/layout/index.vue'),name: 'Product',meta: {hidden: false,title: '商品管理',icon: 'Goods',},redirect: '/product/trademark',children: [{path: '/product/trademark',component: () => import('@/views/product/trademark/index.vue'),name: 'Trademark',meta: {hidden: false,title: '品牌管理',icon: 'ShoppingCartFull',},},{path: '/product/attr',component: () => import('@/views/product/attr/index.vue'),name: 'Attr',meta: {hidden: false,title: '属性管理',icon: 'CollectionTag',},},{path: '/product/spu',component: () => import('@/views/product/spu/index.vue'),name: 'Spu',meta: {hidden: false,title: 'SPU管理',icon: 'Calendar',},},{path: '/product/sku',component: () => import('@/views/product/sku/index.vue'),name: 'Sku',meta: {hidden: false,title: 'SKU管理',icon: 'Orange',},},],},
]//任意路由
//任意路由
export const anyRoute = {//任意路由path: '/:pathMatch(.*)*',redirect: '/404',name: 'Any',meta: {title: '任意路由',hidden: true,icon: 'DataLine',},
}
15.2 菜单权限的实现
15.2.1 获取正确路由的方法
注意:这里使用了递归。其次,这里是浅拷贝,会改变原有的路由。因此还需要改进。
//硅谷333: routes['Product','Trademark','Sku']
let guigu333 = ['Product', 'Trademark', 'Sku'];
function filterAsyncRoute(asnycRoute, routes) {return asnycRoute.filter(item => {if (routes.includes(item.name)) {if (item.children && item.children.length > 0) {item.children = filterAsyncRoute(item.children, routes)}return true}})
}
//硅谷333需要展示的异步路由
let guigu333Result = filterAsyncRoute(asnycRoute, guigu333);
console.log([...constRoute, ...guigu333Result, anyRoute], '硅谷333');
15.2.2 获取路由
。。。。。。import router from '@/router'
//引入路由(常量路由)
import { constantRoute, asnycRoute, anyRoute } from '@/router/routes'
//用于过滤当前用户需要展示的异步路由
function filterAsyncRoute(asnycRoute: any, routes: any) {return asnycRoute.filter((item: any) => {if (routes.includes(item.name)) {if (item.children && item.children.length > 0) {//硅谷333账号:product\trademark\attr\skuitem.children = filterAsyncRoute(item.children, routes)}return true}})
}
//创建用户小仓库
const useUserStore = defineStore('User', {//小仓库存储数据地方state: (): UserState => {return {。。。。。。。menuRoutes: constantRoute, //仓库存储生成菜单需要数组(路由)us。。。。。。}},//处理异步|逻辑地方actions: {。。。。。。。//获取用户信息方法async userInfo() {//获取用户信息进行存储const result: userInfoResponseData = await reqUserInfo()if (result.code == 200) {this.username = result.data.namethis.avatar = result.data.avatar//计算当前用户需要展示的异步路由const userAsyncRoute = filterAsyncRoute(asnycRoute, result.data.routes)//菜单需要的数据整理完毕this.menuRoutes = [...constantRoute, ...userAsyncRoute, anyRoute]//目前路由器管理的只有常量路由:用户计算完毕异步路由、任意路由动态追加;[...userAsyncRoute, anyRoute].forEach((route: any) => {router.addRoute(route)})return 'ok'} else {return Promise.reject(new Error(result.message))}},。。。。。。
})
//对外暴露小仓库
export default useUserStore
15.3 菜单权限的2个小问题
15.3.1 深拷贝
之前获取需要的路由方法中使用的是浅拷贝,会改变原有的路由。因此我们这里引入深拷贝的方法
//引入深拷贝方法
//@ts-expect-error
import cloneDeep from 'lodash/cloneDeep'
。。。。。。//获取用户信息方法async userInfo() {//获取用户信息进行存储const result: userInfoResponseData = await reqUserInfo()if (result.code == 200) {this.username = result.data.namethis.avatar = result.data.avatar//计算当前用户需要展示的异步路由const userAsyncRoute = filterAsyncRoute(cloneDeep(asnycRoute),result.data.routes,)//菜单需要的数据整理完毕this.menuRoutes = [...constantRoute, ...userAsyncRoute, anyRoute]//目前路由器管理的只有常量路由:用户计算完毕异步路由、任意路由动态追加;[...userAsyncRoute, anyRoute].forEach((route: any) => {router.addRoute(route)})return 'ok'} else {return Promise.reject(new Error(result.message))}},
15.3.2 路由加载问题
这样配置路由后,如果你访问的是异步路由,会在刷新的时候出现空白页面。原因是异步路由是异步获取的,加载的时候还没有。因此我们可以在路由守卫文件中改写。这个的意思就是一直加载。
//用户登录判断if (token) {//登陆成功,访问login。指向首页if (to.path == '/login') {next('/')} else {//登陆成功访问其余的,放行//有用户信息if (username) {//放行next()} else {//如果没有用户信息,在收尾这里发请求获取到了用户信息再放行try {//获取用户信息await userStore.userInfo()//万一刷新的时候是异步路由,有可能获取到用户信息但是异步路由没有加载完毕,出现空白效果next({ ...to })} catch (error) {//token过期|用户手动处理token//退出登陆->用户相关的数据清空await userStore.userLogout()next({ path: '/login', query: { redirect: to.path } })}}}} else {//用户未登录if (to.path == '/login') {next()} else {next({ path: '/login', query: { redirect: to.path } })}}