前情回顾
上回说到 上回:超详细vue案例解析 不怕入不了门,vue繁杂的分级目录有个初步认识之后,对怎么入手开发想必已经知晓,那么vue最牛逼的地方-Vue Router
: 动态路由鉴权 今天咱也来破一破【正经聊天哈】
要提到动态路由,首先你得知道当你从GitHub拉去的模版项目使用的是静态路由,有必要了解一下 , 以我拉取的Vue-design-pro 【有效结合vue和ant-design组件库真TM香】项目来说 ,上篇末尾其实我已经提到哪里用了静态路由:store目录 ->module目录 -〉permission.js
这里是静态路由鉴权和页面构造路由树的核心,那么问题来了,具体是怎么操作的? 注意别眨眼
静态路由起飞
注意跟紧思路哈, 本次车速就直接3档起步,请系好安全带:
-
登录成功回调里调用:router.push({path:’/’})
-
直接被permission.js前置路由守卫: beforeEach拦截 ,拦截方法先校验token 然后校验store里是否有路由,第一次登录必然没有,
调用store【user状态组件】的multion【你可以理解为controller】 :GetInfo , GetInfo 里获取用户角色信息【作者想要通过先拿角色 在请求mock里的权限数据】,然后对返回的角色信息进行过滤【为按钮级别权限操作鉴权作准备】,之后返回,又调用了store【permission.js】 的GenerateRoutes,并传递了上次请求获取角色信息列表
,所讲如下图 这段代码萌新一定很苦逼吧稍后我会在动态路由介绍更骚的操作
if (store.getters.roles.length === 0) { // request login userInfo store .dispatch('GetInfo') .then(res => { const roles = res.result && res.result.role // generate dynamic router store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表 // 动态添加可访问路由表 router.addRoutes(store.getters.addRouters) // 请求带有 redirect 重定向时,登录自动重定向到该地址 const redirect = decodeURIComponent(from.query.redirect || to.path) if (to.path === redirect) { // set the replace: true so the navigation will not leave a history record next({ ...to, replace: true }) } else { // 跳转到目的路由 next({ path: redirect }) } }) })
-
到了permission.js 好戏才算开始: 将实现事先写好的静态路由
asyncRouterMap
[router.config.js] 进行改map 和 传入的角色权限 进行过滤【俗称自嗨式鉴权】【作者为了教会我们实属不易】,具体过滤就不cue了,递归有啥讲的 -
鉴权完了之后,就是路由渲染了,一样返回后直接调用router.addRoutes 将步骤3保存在状态管理组建的路由 放进去,这是真正意义上的渲染,【vue3.0已经不这么用,md为了区分就是少了个s,防止杠精:逻辑肯定变了,只不过好歹区分度大一点啊】
-
之后就是玩哲学三个基本点: who ? where from, where to go, 先去找当前路由的基本信息是否有 redirect参数且有值,还记得登录成功后怎么匹配的吗:
/ 从根路由开始匹配,发现有一个redirect 就去重定向:
这一块我被坑了老惨了,还是深陷其中无法自拔的那种,待会在嘲笑我
就是走了上述代码的这个分支:next({ path: redirect })
之后就是组建渲染完毕,看到你看到的默认界面了, 问一个问题【笔者开始转型咯】,你们说如果我在generator-router.js 配置的重定向和src目录下permission.js 配置默认跳转路由 优先访问那个?【试一下评论区自信点回复我哦】。
动态路由Action:排坑
现在请忘记刚才所讲的所有内容,和动态路由一点关系没有!【惊不惊喜,意不意外】
动态路由不同之处就在于它是我们后端按照vue路由树识别的规则 进行设计封装后返回给前端,过程繁琐,就直接唠干的 :
-
参考作者设计的路由,构造后端vue列表
mock 模版
后台字段统一对应返回就好,注意:踩坑之路开始
有了数据怎么构造并鉴权了呢? (什么?鉴权?你在想p吃,你是怎么拿到后台数据的,还需要鉴权吗) 【知道为啥让你忘记静态路由吗, 笔者傻傻套用静态的差点没把自己干崩溃】, 所以重点怎么构造路由树:两种方案:【我选了最懒但也是坑最多那种, 虽然我无聊的把另一种写了】
-
直接将数据库的权限表 构造对应vue router 参数 返回,就是这么简单,但也是贼恶心的一种
动态路由的核心入口在:
咋一看那么像静态的, 一点也不! 这里我做了封装,【别瞧不起我low b 的注释,开发vue 没有注释你都像是在做梦一样】
-
将返回的数据在登录结束后就保存到本地【够骚气吧】咱们不是探讨安全性问题,就先这样了
-
在登录跳转之后 ,依然进入到permisson.js 不过动态路由现在代码是这样滴:
router.beforeEach((to, from, next) => { NProgress.start() // start progress bar to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${i18nRender(to.meta.title)} - ${domTitle}`)) // 获取本地token if (storage.get(ACCESS_TOKEN)) { console.log('to from next', to, from, next) if (to.path === loginRoutePath) { console.log('跳转到目标首页') next({ path: defaultRoutePath }) NProgress.done() } else { // 取出后台放的权限缓存 const routers = storage.get(ROURTERS) if (routers && store.getters.addRouters.length === 0) { // request login userInfo // generate dynamic router const perms = routers || {} store.dispatch('ActiveGenerateRoutes', { perms }) // 根据roles权限生成可访问的路由表 // 动态添加可访问路由表 router.addRoutes(store.getters.addRouters) console.log('vue 路由', store.getters.addRouters) // 请求带有 redirect 重定向时,登录自动重定向到该地址 // const redirect = decodeURIComponent(from.query.redirect || to.path) const redirect = decodeURIComponent(to.path) console.log('第一次重定向', redirect) if (to.path === redirect) { // set the replace: true so the navigation will not leave a history record next({ ...to, replace: true }) } else { // 跳转到目的路由 next({ path: redirect }) } } else { console.log('从哪里来:', from) console.log('到哪里去:', to) next() } } } else { if (allowList.includes(to.name)) { // 在免登录名单,直接进入 next() } else { next({ path: loginRoutePath, query: { redirect: to.fullPath } }) NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it } } })
阅读思路和静态路由类似, 回过头来继续看 动态路由树的构建
-
vue作者确实周到,也帮我们写好了
generator-router.js
export const generatorDynamicRouterV2 = (res) => { const { perms } = res const menuNav = [] const childrenNav = [] // 后端数据, 根级树数组, 根级 PID listToTree(perms, childrenNav, 0) rootRouter.children = childrenNav // 转成列表形式 menuNav.push(rootRouter) const routers = generator(menuNav) routers.push(notFoundRouter) return routers } /** * 格式化树形结构数据 生成 vue-router 层级路由表 * * @param routerMap * @param parent * @returns {*} */ export const generator = (routerMap, parent) => { return routerMap.map(item => { // const { title, show, hideChildren, hiddenHeaderContent, target, icon } = item.meta || {} if (item.key === 'System') { console.log('前端存在的路由', constantRouterComponents[item.key]) console.log(item) } const { title, icon, permission } = item.meta || {} const currentRouter = { // 如果路由设置了 path,则作为默认 path,否则 路由地址 动态拼接生成如 /dashboard/workplace // path: item.path || `${parent && parent.path || ''}/${item.key}`, path: item.path || `${parent && parent.path || ''}/${item.key}`, // 路由名称,建议唯一 name: item.name || item.key || '', // 该路由对应页面的 组件 :方案1 // component: constantRouterComponents[item.component || item.key], // 该路由对应页面的 组件 :方案2 (动态加载)生产使用 // 在webpack打包方式下import只能动态加载components包下面的组件,其他路径的包只能通过字符串写死方式如:import('@/views/dashborad/Workpalce')否则会出现cant not find module // component: (constantRouterComponents[item.component || item.key]) || (() => import(`@/views/${item.component}`)) // 由此为了避开报错,只能写死在前端,然后动态匹配map的key, 目前只有一种如下方案!!! component: constantRouterComponents[item.component], // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉) meta: { title: title, icon: icon || undefined, // hiddenHeaderContent: hiddenHeaderContent, // target: target, permission: permission } } // g代表全部 // const reg = new RegExp('//', 'g') // const curStr = JSON.stringify(currentRouter).replace(reg, '/') // const cur = JSON.parse(curStr) // // 是否设置了隐藏菜单 // if (show === false) { // currentRouter.hidden = true // } // 是否设置了隐藏子菜单 // if (hideChildren) { // currentRouter.hideChildrenInMenu = true // } // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠 if (!currentRouter.path.startsWith('http')) { currentRouter.path = currentRouter.path.replace('//', '/') } console.log('当前路由', currentRouter) // 重定向 item.redirect && (currentRouter.redirect = item.redirect) // 是否有子菜单,并递归处理 if (item.children && item.children.length > 0) { // Recursion currentRouter.children = generator(item.children, currentRouter) } return currentRouter }) } /** * 数组转树形结构 * @param list 源数组 * @param tree 树 * @param parentId 父ID */ const listToTree = (list, tree, parentId) => { list.forEach(item => { // 判断是否为父级菜单 if (item.parentId === parentId) { const child = { ...item, key: item.key || item.name, children: [] } // 迭代 list, 找到当前菜单相符合的所有子菜单 listToTree(list, child.children, item.id) // 删掉不存在 children 值的属性 if (child.children.length <= 0) { delete child.children } // 加入到树中 tree.push(child) } }) }
笔者遇到的大部分坑 尽在代码注释中,拿走不谢 😭
-
还有单独的两个坑,拿出来
-
记得在状态组建管理库对外的公共module 进行动态路由组件库替换:
这是替换动态路由时,容易忘记的地方【记得看控制台报错】
这块需要你对vuex 有一定的了解,不多费口舌了,去官网瞅瞅吧
将构造好的路由树返回后,permission.js 不要再去解析是否有重定向了,因为你动态返回的数据 你怎么知道那个菜单改重定向?就是这一块 被坑哭了都【TMD,不声明一下改处代码是可以变动的】
const redirect = decodeURIComponent(to.path)
由此也可以看出,后端的path字段必须是有的!
-
在啰嗦一句
上面就是动态路由的全部了,基本上按照我上面代码走下去,能保证一定能动态鉴权, 有问题评论区截图反馈,另外以后笔者开始走提问路线了,别问为啥,问就是太寂寞了,没有女票 只好跟各位童鞋唠嗑了。