简单记录一下 Vue 3 + Vue-Router 4 实现动态路由

lxf2023-04-07 07:16:01

简单记录一下碰到的问题,以及实现方式

项目前端写好静态路由,通过router.name标志路由显示。
后端登陆之后回传权限列表permissionList,前端通过判断路由的name属性是否在permissionList之中来控制具体该用户可以看到哪些路由。

  • Vue-Router 4 中addRoute之后,路由并不会立即生效,地址栏输入路由,会报错No match found for location with path,然后页面白屏。查阅官方文档,发现问题,addRoute之后需要手动调用router.push新增的路由才会生效。

    API 参考 | Vue Router (vuejs.org)
    提示
    请注意,添加路由并不会触发新的导航。也就是说,除非触发新的导航,否则不会显示所添加的路由。 解决方案

    // 在路由守卫中,addRoute之后需要做一下判断
    if (!to.matched.length) {
        router.push({
          path: to.path,
          query: to.query //防止刷新页面query丢失
        })
    }
    
  • 登录之后跳转,调用登录接口之后使用router.push,此时依然直接报错No match found for location with path,并且不会触发路由守卫中的事件。通过router.getRoutes()发现此时的路由还是写好的静态路由,所以需要手动触及addRoute
    解决方案
    import { generateRouter } from '@/utils/generateRoute';
    
    const onFinish = async (values: FormState) => {
      try {
        const res = await userStore.login(values);
        notification['success']({
          message: '登录成功',
          description: '正在跳转'
        })
        handleJump()
        btnLoading.value = false
      } catch (err: any) {
        console.log(err.message)
        if (~err.message.indexOf('No match for')) {
          // 这里需要主动触发一下更新路由
          await generateRouter(router)
          handleJump()
        }
        btnLoading.value = false
      }
    }
    const handleJump = () => {
      const { redirect, ...othersQuery } = router.currentRoute.value.query;
      router.push({
        name: (redirect as string) || 'HOME_PAGE',
        query: {
          ...othersQuery,
        },
      })
    }
    

具体代码实现

utils/lib/permission.ts
import { set, get } from './storage'

const KEY = 'PERMISSION_LIST';

const setPermission = (list: string[]) => {
  set(KEY, JSON.stringify(list))
}

const getPermission = () => {
  let v = get(KEY)
  return v ? JSON.parse(v) : []
}

export { setPermission, getPermission };
utils/lib/generateRoute.ts
import { getPermission } from '@/utils/permission'
import { asyncRouterMap } from '@/router/route'
import type { Router, RouteRecordRaw } from 'vue-router'

const hasPermission = (route: any) => {
  const permissionList = getPermission()
  if (route.meta && !route.meta.hideInMenu) {  
    // 判断route的name值是否存在于permissionList
    return permissionList.includes(route.name)
  }
  return true
}

export const filterAsyncRouter = (routerMap: any) => {
  const accessedRouters = routerMap.filter((route: any) => {
    if (hasPermission(route)) {
      if (route.children && route.children.length) {
        route.children = filterAsyncRouter(route.children)
      }
      return true
    }
    return false
  })
  return accessedRouters
}

export const generateRouter = (router: Router) => {
  return new Promise(resolve => {
    const validRouter = filterAsyncRouter(asyncRouterMap)
    validRouter.forEach((item: RouteRecordRaw) => {
      router.addRoute(item)
    })
    resolve(true)
  })
}

router/permission.ts

该文件是路由守卫

import { nextTick } from 'vue';
import router from '.'
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import { canTurnTo, setTitle } from '@/utils/lib';
import { getToken, isLogin } from '@/utils/auth';
import type { LocationQueryRaw } from 'vue-router';
import { constantRouterMap } from './route'
import { generateRouter } from '@/utils/generateRoute';

const LOGIN_PAGE_NAME = "login"
const HOME_PAGE_NAME = 'ROLE_PROD_MANAGE'

router.beforeEach(async (to, from, next) => {
  NProgress.start()
  const token = getToken();
  // const userStore = useUserStore();
  if (token) {
    await generateRouter(router)
    if (to.name === LOGIN_PAGE_NAME) {
      next({
        name: HOME_PAGE_NAME // 跳转到homeName页
      });
      NProgress.done()
    } else {
      if (isLogin()) {
        // turnTo(to, userStore.access, next);
        // next({ ...to, replace: true })
        // 解决vue-router4 addRoute找不到路由问题
        if (!to.matched.length) {
          router.push(to.path)
        }
        next()
        NProgress.done()
      } else {
        next({
          name: LOGIN_PAGE_NAME,
          query: {
            redirect: to.name,
            ...to.query,
          } as LocationQueryRaw,
        });
        NProgress.done()
      }
    }
  } else {
    //  未登录
    if (to.name === LOGIN_PAGE_NAME) {
      //  去登录页面
      next();
      NProgress.done()
    } else {
      next({
        name: LOGIN_PAGE_NAME // 跳转到登录页
      });
      NProgress.done()
    }
  }
})

router.afterEach(to => {
  setTitle(to)
  window.scrollTo({ top: 0, behavior: 'smooth' });
  nextTick(() => {
    const content = document.querySelector('.ant-layout-content');
    content && content.scrollTo({ top: 0, behavior: 'smooth' });
  });
});