专栏名称: 前端大全
分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯
目录
相关文章推荐
广告文案  ·  春晚之外,还有22张非遗海报 ·  2 天前  
51好读  ›  专栏  ›  前端大全

前端权限开发——设计到实践(保姆级)

前端大全  · 公众号  ·  · 2024-05-21 09:01

正文

作者:守夜人x

https://juejin.cn/post/7259210874446692411

1.权限控制的方案选择。

做后台项目区别于做其它的项目,权限验证与安全性是非常重要的,可以说是一个后台项目一开始就必须考虑和搭建的基础核心功能。在后台管理系统中,实现权限控制可以采用多种方案:

权限方案类型 描述
基于角色的访问控制(Role-Based Access Control,RBAC) 是一种广泛采用的权限控制方案。系统中定义了不同的角色,每个角色具有一组权限,而用户被分配到一个或多个角色。通过控制用户角色的分配,可以实现对用户访问系统中不同功能和资源的权限控制。
基于权限的访问控制(Permission-Based Access Control) 这种方案将权限直接分配给用户,而不是通过角色来管理。每个用户都有自己的权限列表,控制用户对系统中各项功能和资源的访问。
基于资源的访问控制(Resource-Based Access Control,RBAC) 这种方案将权限控制与资源本身关联起来。系统中的每个资源都有自己的访问权限,用户通过被授予资源的访问权限来控制其对资源的操作。
层次结构权限控制(Hierarchical Access Control) 这种方案基于资源和操作的层次结构来进行权限控制。系统中的资源和操作被组织成层次结构,用户被授予访问某个层次及其子层次的权限。
基于规则的访问控制(Rule-Based Access Control) 这种方案使用预定义的规则来确定用户对系统中功能和资源的访问权限。规则可以基于用户属性、环境条件或其他因素进行定义。

这里我选择了 基于角色的访问控制(Role-Based Access Control,RBAC) 这是因为RBAC提供了一种灵活且易于管理的方式来控制用户对系统功能和资源的访问,也是目前最主流的前端权限方案选择。

在RBAC中,系统中的功能和资源被组织成角色,而用户则被分配到不同的角色。每个角色都有一组权限,定义了该角色可以执行的操作和访问的资源。通过给用户分配适当的角色,可以实现对用户的权限控制。

RBAC的好处之一是它简化了权限管理的复杂性。管理员只需管理角色和分配角色给用户,而不需要为每个用户单独定义权限。当需要对用户的权限进行修改时,只需调整其角色的权限即可。

此外,RBAC还支持灵活的权限组合,允许创建具有不同权限组合的角色,以适应不同用户的需求。它也便于扩展,可以随着系统的发展和需求的变化而调整和添加角色。

2.RBAC下的权限字段设计与管理模型

1.用户权限授权

是对用户身份认证的细化。可简单理解为访问控制,在用户身份认证通过后,系统对用户访问菜单或按钮进行控制。也就是说,该用户有身份进入系统了,但他不一定能访问系统里的所有菜单或按钮,而他只能访问管理员给他分配的权限菜单或按钮。
主要包括:

  • Permission(权限标识、权限字符串):针对系统访问资源的权限标识,如:用户添加、用户修改、用户删除。

  • Role (角色):可以理解为权限组,也就是说角色下可以访问和点击哪些菜单、访问哪些权限标识。

权限标识或权限字符串校验规则:

  • 权限字符串:指定权限串必须和菜单中的权限标识匹配才可访问

  • 权限字符串命名规范为: 模块:功能:操作 ,例如: sys:user:edit

  • 使用冒号分隔,对授权资源进行分类,如 sys:user:edit 代表 系统模块:用户功能:编辑操作

  • 设定的功能指定的 权限字符串 与当前用户的 权限字符串 进行匹配,若匹配成功说明当前用户有该功能权限

  • 还可以使用简单的通配符,如 sys:user:* ,建议省略为 sys:user (分离前端不能使用星号写法)

  • 举例1 sys:user 将于 sys:user sys:user: 开头的所有权限字符串匹配成功

  • 举例2 sys 将于 sys sys: 开头的所有权限字符串匹配成功 这种命名格式的好处有:

  1. 可读性和可理解性 :使用模块、功能和操作的格式可以直观地表达权限的含义。每个部分都有明确的作用,模块表示特定的模块或子系统,功能表示模块内的某个功能或页面,操作表示对功能进行的具体操作。通过这种格式,权限名称可以更容易地被开发人员、管理员和其他人员理解和解释。

  2. 可扩展性和灵活性: 通过使用模块、功能和操作的格式,可以轻松地扩展和管理权限。每个模块、功能和操作都可以被单独定义和控制。当系统需要增加新的功能或操作时,可以根据需要添加新的权限字符串,而不需要修改现有的权限规则和代码。

  3. 细粒度的权限控制: 这种格式支持细粒度的权限控制,可以针对特定的功能和操作进行权限管理。通过将权限名称拆分为模块、功能和操作,可以精确地定义哪些用户或角色具有访问或操作特定功能的权限。

  4. 避免权限冲突: 使用模块、功能和操作的格式可以避免权限之间的冲突。不同模块、功能和操作的权限名称是唯一的,这样可以避免同名权限之间的混淆和冲突。

2.权限管理模型

关键数据模型如下:

  • 用户:登录账号、密码、角色

  • 角色:角色名称、角色权限字符、对应菜单、对应菜单下的权限

  • 菜单:菜单名称、菜单URL、菜单类型

  • 用户角色关系:用户编码、角色编码

  • 角色菜单关系:角色编码、菜单编码

关系图如下:

rust

复制代码

【用户】 <---多对多---> 【角色】 <---多对多---> 【菜单/权限】

3.实现思路与步骤

前端权限一般分为路由级权限和按钮级权限,这里我们先实现页面路由级的权限功能,按钮级的会在后面讲到。大致的思路如下图:

上图是用户从 登录-->路由守卫-->权限验证-->构建路由表-->跳转目标页面 的一个简单的正向流程,可以看到, 权限验证 构建路由表 这两步是发生在 路由守卫 这一步里,而实际上在开发设计阶段,我们还要做的准备有:

  1. 与后端确认权限字段及类型

  2. 确定路由表信息是由前端还是后端生成

为了方便理解,我们就按照这个正向流程来逐步实现,期间需要用到或者提前考虑设计到的内容,包括以上需要准备的两点,我会补充在步骤当中。

1.登录

登录成功后,获取到token,将token存到本地的sessionStorage里。

action.js

复制代码

async login({ commit }, userInfo) {
const { data } = await login(userInfo)
const accessToken = 'Bearer ' + data.access_token
if (accessToken) {
sessionStorage.getItem(tokenTableName)
} else {
message.error(`登录接口异常,未正确返回${tokenName}...`)
}
}

接着,进行路由跳转到首页

login.vue

复制代码

import { useRouter } from 'vue-router';
const router = useRouter();
router.push('/');

如果考虑到页面token失效后,重新登陆后返回原先的路由地址,则需要在路由守卫中添加 redirect 字段用来存储当前的路由地址

permissions.js

复制代码

next({ path: '/login', query: { redirect: to.fullPath }, replace: true })

然后在登录页中,监听路由对象中的这个值,如果有值,那么在刚刚路由跳转时,就跳转到该路由地址,而非 / 首页,另外,还需要考虑到403,404页面。所以,添加了这些逻辑后,部分代码如下:

js

复制代码

//login.vue

import { ref, watch, useRouter } from 'vue';
import { useRouter } from 'vue-router';
const redirect = ref('/');
const router = useRouter();

watch(
() => router.currentRoute.value.query.redirect,
(redirectValue) => {
redirect.value = redirectValue || '/';
},
{ immediate: true }
);

function handleRoute() {
return redirect.value === '/404' || redirect.value === '/403'
? '/'
: redirect.value
},

async handleSubmit() {
loading.value = true
try {
await login(form.value)
loading.value = false
router.push(handleRoute());
} catch {
loading.value = false
}
},


2.路由守卫与权限校验

当进行路由跳转时,会进入到路由守卫中,在路由守卫中:

  1. 路由守卫会首先判断有没有token,如果没有,先判断要去的路由地址是否包含在路由白名单内,如果要去的路由地址也不在路由白名单内,就会让用户跳转到登录页重新登陆。

  2. 如果有token,会先判断跳转的目标地址是否是登录页,如果是,则重新跳转到默认首页

  3. 此时,我们就需要对用户权限进行校验,首先,判断当前用户是否 拥有角色 信息,如果没有,就要获取用户的角色信息。

3.获取角色信息与权限信息

调取用户信息接口获取用户角色信息和权限信息,代码如下:

js

复制代码

//store/user
// 获取用户信息
GetInfo({ commit, dispatch }) {
return new Promise((resolve, reject) => {
getUserInfo()
.then(async (response) => {
const result = response.data
if (result.roles && result.roles.length > 0) {
// 验证返回的roles是否是一个非空数组
commit('SET_ROLES', result.roles)
//permissions就是对应的用户权限信息
commit('SET_PERMISSION', result.permissions)
} else {
// 如果当前用户没有角色,则赋值一个默认角色
commit('SET_ROLES', ['ROLE_DEFAULT'])
}
resolve(result)
// GetInfo一旦失败就说明这个token不是过期就是丢失了,直接走catch并让调用方跳转路由
if (!response.success) {
reject(response.errorMsg)
}
})
.catch((error) => {
reject(error)
})
})
},

根据之前上文提到的权限管理模型,从之间的关系是多对多,因此, role(角色) permssions(权限) 的类型应为数组,其中的权限permssion这个字段的格式规范也在上文提及。在与后端约定好后,后端返回的信息如图:

4.谁来生成动态路由表?

在成功获取到用户信息,拿到用户角色和对应权限后,此时,就需要根据权限生成对应的路由表了,到这里,我们需要思考一个问题:
路由表是由后端提供还是由前端提供?
A:前端根据权限生成路由表
B:后端生成路由表给前端

没错!答案是C:路由表可以由后端提供或由前端提供。

两者的优劣分别是:

  1. 后端提供路由表:

  • 前后端耦合度高:由于路由表由后端提供,前端开发人员可能需要与后端开发人员密切协作,增加了协调和沟通的成本。

  • 前端依赖后端:前端应用程序可能需要等待后端提供路由表后才能进行开发和测试,增加了开发的时间和依赖性。

  • 安全性高:后端负责验证和控制权限,可以确保只有授权的用户能够访问特定的路由和功能。

  • 隐藏敏感信息:后端可以根据用户的角色和权限隐藏不应该被访问的敏感路由和数据。

  • 适用于复杂的权限规则:后端可以使用更复杂的逻辑和规则来处理权限控制,例如基于用户角色、用户组、权限等的复杂控制逻辑。

  • 优点:

  • 缺点:

  • 前端提供路由表:

    • 安全性较低:前端提供的路由表容易受到篡改和绕过,安全性相对较低。

    • 隐藏敏感信息的难度增加:前端无法直接隐藏敏感路由和数据,需要依赖后端接口的授权验证来保护敏感信息。

    • 前后端解耦:前端可以独立开发和维护路由表,减少了与后端的依赖性和协调成本。

    • 更好的用户体验:前端可以根据用户的角色和权限动态展示路由和功能,提供更灵活和个性化的用户体验。

    • 优点:

    • 缺点:

    最终,考虑到后台管理系统的安全性优先级最高,我选择了由后端存储,并生成返回路由表信息。确定好了之后,你就会遇到一个坑: 后端提供的路由表无法直接在前端路由中添加使用 这是因为,后端存储的路由表的结构只一个JSON对象。怎么解决,我们放到后面讲。

    5.公共路由和动态路由

    我们现在把整个路由分为两个部分,分别是 公共路由 动态路由(私有路由)

    公共路由

    公共路由顾名思义就是无论当前用户是什么角色,都会出现的路由部分。一般这样的路由分别有:首页驾驶舱、登录注册页、403页、404页。 这部分路由是在前端项目中提前写死的。
    以我的项目为例,我在src/config下创建一个 publicRouter.js 文件,然后里面放入基础路由信息:

    javascript

    复制代码

    //基础公共路由
    export const constantRouterMap = [
    {
    path: '/',
    name: '',
    component: () => import('@/layout'),
    redirect: '/homePage' ,
    children: [
    {
    path: '/homePage',
    name: 'homePage',
    component: () => import('@/views/homePage/index.vue'),
    },
    ],
    },
    {
    path: '/login',
    component: () => import('@/views/login'),
    },
    {
    path: '/403',
    name: '403',
    component: () => import('@/views/403'),
    },
    {
    path: '/:pathMatch(.*)*',
    component: () => import('@/views/404'),
    }]

    这里面都是路由懒加载的写法,这个没啥好说的。需要注意的是在vue3中,vue-router的版本是4.x以上,在跳转404页面时,如果你的写法是

    css

    复制代码

    {
    path: "/404",
    name: "notFound",
    component: () => import('@/views/404')
    }

    这样写会提示报错,这是因为vue-router4.x的版本官方推荐引入了这种新写法,配置一个通配符路由,匹配所有未被其他具体路由匹配的路径。其中 :pathMatch 是参数名,而 (.*)* 是参数的匹配模式,它使用正则表达式来匹配任意路径。

    动态路由(私有路由)

    上文提到的后端返回给前端当前用户的路由表信息,其实就是私有路由,这部分的路由是动态的。那么我们只需要把 公共路由 + 动态路由 = 当前角色用户完整路由表 。然鹅, 后端返回给前端的动态路由是无法直接添加到当前页面路由中的 ,这是因为在浏览器的前后端请求当中,信息的返回体都是以JSON字符串的数据交换格式进行传输,而JSON字符串是不支持函数的。为什么要提到函数形式呢?
    因为当我们在路由表使用懒加载的写法时, component 的值是一个异步函数。也只有异步函数才能实现对路由的异步懒加载。import()会返回一个promise,这个 promise 最终会加载对应的组件模块。在格式上表现为一个func函数,因此,我们无法将compnent的值传给后端存储
    但是办法总比困难多,我们可以将对应的路径字符串传给后端存储,然后再通过后端返回的路径字符串转换成这种箭头函数的写法。
    除了这个需要转换以外,我们还要考虑一些问题:

    1. 比如,转换后的路由结构要符合router中的路由格式,否则会在调用router.addRoutes时报错,添加失败。

    2. 添加一些其他自定义属性(例如添加导航栏菜单图标、某个路由的显隐、路由缓存、跳转外链接、...),以满足特定需求。
      以我项目中的的路由示例,直接上代码看:

    json

    复制代码

    {
    "path": "/", //path,路由的路径,即访问该路由时的 URL 路径。
    "name": "homePage",//name,路由的名称,用于在代码中标识路由。通常用于编程式导航。
    "hidden": false,//hidden,是否隐藏路由,在Vue Router 4.0 中被废弃。
    "component": "Layout"//路由对应的组件,可以是通过懒加载方式导入的异步组件,或直接引入的同步组件。
    "meta": {//meta,路由元信息,可以用于存储一些额外的信息,比如页面标题、权限等
    "title": "首页", //路由标题
    "icon": "icon-Home", //路由图标
    "target": "", //是否跳转新页签
    "permission": "homePage",//权限
    "keepAlive": false//是否缓存
    },
    "redirect": "/homePage",//重定向
    "fullPath": "/",//完整的url路径,会带上?后面的参数
    "children": [//子路由
    {
    "path": "/homePage",
    "name": "homePage",
    "meta": {
    "title": "首页",
    "icon": "",
    "target": "",
    "permission": "homePage",
    "keepAlive": false
    },
    "fullPath": "/homePage"
    }
    ]
    }

    考虑完这些之后,我们就开始拿着后端返回的路由表信息动手转换吧!先看看后端返回给前端的路由表格式内容

    这里你只需要注意component这个字段的值就行了。
    可能有人会问: “那我给后端传的路由表是什么格式的,也是这样吗?”
    答案是: 当然不是!RBAC权限方案中,用户可以自己配置权限,也就是会有对应的用户管理,角色管理,菜单(权限)管理这三个基本的模块,给后端传的是对应模块的修改配置内容。前端是不需要关心传给后端什么格式。 有点啰嗦了,总之,就是你指着上文这三张图片,让后端给你生成出来就完事了。后端要是说办不到,那就是后端的问题。


    6.获取动态路由(私有路由)并转换

    接下来,这一步是整个前端权限中最重要的一步。在src/router下,我们新建一个generatorRouters.js文件。里面的内容是:

    js

    复制代码

    //getCurrentUserNav获取动态路由的接口
    import { getCurrentUserNav } from '@/api/user'
    //validURL是一个正则判断方法,用来校验当前字符串是否符合url外链格式
    import { validURL } from '@/utils/validate'
    // 前端路由表
    const constantRouterComponents = {
    // 基础页面 layout 必须引入
    Layout: () => import('@/layout'),
    403: () => import('@/views/403'),
    404: () => import('@/views/404'),
    }


    /**
    * 动态生成菜单
    * @param token
    * @returns {Promise}
    */
    export const generatorDynamicRouter = (token) => {
    return new Promise((resolve, reject) => {
    getCurrentUserNav()
    .then ((res) => {
    //接收后端返回的路由表,结构是个数组
    let menuNav = res.data
    //将路由表存到本地临时缓存中
    //这一步是为了刷新的时候不需要再调接口,增加用户体验的,没这方面需求可以不用写
    sessionStorage.setItem('generateRoutes', JSON.stringify(menuNav))
    //转化路由格式
    const routers = generator(menuNav)
    resolve(routers)
    })
    .catch((err) => {
    reject(err)
    })
    })
    }

    /**
    * 格式化树形结构数据 生成 vue-router 层级路由表
    *
    * @param routerMap //当前路由表route
    * @param parent//当前路由表route的父级component
    * @returns {*}
    */
    export const generator = (routerMap, parent) => {
    return routerMap.map((item) => {
    const { title, show, hideChildren, hiddenHeaderContent, icon, hidden } =
    item.meta || {}
    if (item.component) {
    // Layout ParentView 组件特殊处理
    //这里是对父组件/布局组件的处理,因为有可能出现嵌套布局和多种布局的情况,这个时候需要进行处理。
    if (item.component === 'Layout') {
    item.component = 'Layout'
    } else if (item.component === 'ParentView') {
    item.component = 'BasicLayout'
    item.path = '/' + item.path
    }
    }
    if (item.isFrame === 0) {
    item.target = '_blank'
    }
    const currentRouter = {
    // 如果路由设置了 path,则作为默认 path,否则 路由地址 动态拼接生成如 /dashboard/workplace
    path: item.path || `${(parent && parent.path) || ''}/${item.key}`,
    // 路由名称,建议唯一
    // name: item.name || item.key || '',
    name: item.name || item.key || '',
    // 该路由对应页面的 组件 :方案1
    // 该路由对应页面的 组件 :方案2 (动态加载)
    //这里就是将component的字符串值转换成懒加载的异步函数
    //同时,如果当前路径并没有对应的组件,catch捕获报错,然后跳转到404页,这一步很重要,否则
    component:
    constantRouterComponents[item.component || item.key] ||
    (() =>
    import(`@/views/${item.component}`).catch(() =>
    import('@/views/404')
    )),
    hidden: item.hidden,
    // redirect: '/' + item.path || `${parent && parent.path || ''}/${item.key}`,
    // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
    meta: {
    title: title,
    icon: icon,
    hiddenHeaderContent: hiddenHeaderContent,
    target: validURL(item.path) ? '_blank' : '',
    permission: item.name,
    keepAlive: false,
    hidden: hidden,
    },
    //适配本框架的跳转路径
    }
    // 是否设置了隐藏菜单
    if (show === false) {
    currentRouter.hidden = true
    }
    // 修正路径,而antdv-pro的pro-layout要求每个路径需为全路径
    //这一步的修正路径也是因人而异,正常情况下按照我这样拼接就没问题了
    if (!constantRouterComponents[item.component || item.key]) {
    if (parent && parent.path && parent.path !== '/') {
    currentRouter.path = `${parent.path}/${item.path}`
    }
    }
    // 是否设置了隐藏子菜单
    if (hideChildren) {
    currentRouter.hideChildrenInMenu = true
    }
    // 重定向
    item.redirect && (currentRouter.redirect = item.redirect)
    //添加fullPath
    currentRouter.fullPath = currentRouter.path
    // 是否有子菜单,并递归处理
    if (item.children && item.children.length > 0) {
    currentRouter.children = generator(item.children, currentRouter)
    }
    return currentRouter
    })
    }

    generatorDynamicRouter方法(promise嵌套)

    上面的代码有点多,不要觉得麻烦,其实就两个方法,第一个方法 generatorDynamicRouter 会调取后端接口, 获取后端返回的私有路由表信息 ,返回的是个 promise对象 ,因为需要调接口请求后端数据,并且其他方法依赖这个方法的接口返回值,所以是个异步函数。之所以这么写的另外一个原因,是因为在它之前还有个promise异步方法包着它。
    我们都知道: 在嵌套的 Promise 中,内部的 Promise 会先执行,然后再执行外部的 Promise。这是由于 JavaScript 的异步执行机制所决定的。 所以这种套娃式的目的只有一个—— 就是确保外层的异步方法在调用内部异步方法时,能够保证拿到内部异步方法请求成功之后的返回值 (也就是将异步方法变成同步方法,这也是Promise主要的作用之一,面试常问的)。

    而最外层的异步方法,是放在路由守卫当中直接调用的,由于我们是动态异步加载路由,那么执行的方法肯定也是异步。具体的原因是因为:
    如果动态加载路由的方法不是异步的,那么在路由守卫中使用它将导致它立即执行并返回一个 Promise,而不是在路由导航过程中延迟加载。这样会使得在路由守卫中的逻辑无法按预期执行,因为在组件加载完成之前,它依赖的组件可能还没有被加载,导致出现错误或不一致的状态。

    从后端获取路由配置往往涉及到网络请求和异步操作,不能保证立即返回所有路由信息。当你从后端获取到路由配置后,需要将其添加到 Vue Router 中,以便在前端应用程序中进行动态路由。在这个过程中,你需要使用异步操作来确保在获取到路由配置后再添加到路由中。

    generator方法(递归转化)

    言归正传, generatorDynamicRouter 方法调取后端接口,拿到后端返回路由表信息后,会紧接着调用第二个 generator 方法,并将路由表信息当做参数传入。而 generator 是一个递归方法。这个也很好理解,因为路由信息里都会包含children子路由,需要层层递归, generator 内部会对每一个路由进行 逐层转化 ,每一步都在代码中标有注释,我就不再过多赘述了。我们只需要知道: generator 的最终目的就是将后端返回的路由表结构转化成符合前端路由格式的路由 (听起来有点绕)。
    附上格式化好之后的路由信息: 再对比下格式化之前的样子: 可以看到component从字符串变成了函数,path路径也得到了拼接补充。

    7.存储格式化好的动态路由及导航栏菜单信息

    上文提到在generatorDynamicRouter异步方法之外还有一层异步方法,其实我是放在了vuex的store下某个模块中的actions里(默认大家都会用vuex)。具体代码如下

    js

    复制代码

    const actions = {
    GenerateRoutes({ commit }) {
    let sidebarList = []
    return new Promise((resolve) => {
    generatorDynamicRouter().then((routers) => {
    //sidebarList是侧边栏渲染数据
    sidebarList = routers
    //routers就是我们要存储到vuex中的动态路由
    commit('set_routers', routers)
    //侧边栏数据存到vuex中
    commit('set_sidebarList', sidebarList)
    resolve()
    })
    })
    },
    }

    8.添加动态路由

    好了,到这一步,我们就拿到了准备好的 公共路由 私有路由 。这个时候,我们再回到路由守卫,回顾一下之前的流程:

    1. 路由守卫先判断token

    2. 没有token去登录拿token(或者是直接去了白名单里)

    3. 拿到token后判断有没有角色信息,没有角色信息就去请求角色信息(调接口)

    4. 拿到角色信息后,再去获取对应角色信息的路由表(调接口)

    5. 将后端返回的路由表信息转换成前端能添加的路由表信息格式

    6. 添加路由

    7. 跳转对应路由页面

    路由守卫的代码如下,直接在src/config目录下新建文件 permissions.js

    js

    复制代码


    import router from '@/router'
    import store from '@/store'
    import getPageTitle from '@/utils/pageTitle'
    import getInfoRouter from '@/router/getInfoRouter'
    import { loginInterception, recordRoute, routesWhiteList } from '@/config'
    router.beforeEach(async (to, from, next) => {
    let hasToken = store.getters['user/accessToken']
    if (hasToken) {
    if (to.path === '/login') {
    next({ path: '/' })
    } else {
    //权限校验
    if (store.getters['user/roles'].length === 0) {
    try {
    //获取用户信息,包括角色信息和权限信息
    await store.dispatch('user/GetInfo')
    //获取动态路由表信息,并对路由表进行格式转化,然后存到vuex中
    await store.dispatch('async-router/GenerateRoutes')
    //从vuex中拿到动态路由表数据
    let accessRoutes = store.getters['async-router/addRouters']
    //循环添加路由
    accessRoutes.forEach((route) => {
    //isHttp方法用来判断是否是外链,如果是外链,就不添加到当前路由表中
    if (!isHttp(route.path)) {
    router.addRoute(route) // 动态添加可访问路由表
    }
    })
    //刷新跳转
    next({ ...to, replace: true })
    } catch






    请到「今天看啥」查看全文


    推荐文章
    广告文案  ·  春晚之外,还有22张非遗海报
    2 天前
    政治学人  ·  经典 | GOVERNANCE:治理的阐释
    7 年前
    电子商务研究中心  ·  盘点|出口电商值得大干一场的5大理由
    7 年前
    跟大厨学做菜  ·  24岁女孩天天敷面膜,皮肤却老成30岁?
    7 年前