vue动态路由菜单权限控制

vue3 动态路由菜单权限控制

前置:
1.路由数据由后端接口返回
2.通过 pinia、路由导航守卫实现动态路由控制

一.定义常量路由和任意路由

// routes.ts
// 常量路由
const constantRoute = [
  {
    path: '/',
    name: 'Layout',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
        path: '/dashboard',
        name: 'Dashboard',
        component: Dashboard,
        meta: { title: '首页', icon: 'HomeFilled', hidden: false }
      }
    ]
  },
  {
    path: '/login',
    name: 'Login',
    component: Login,
    meta: { title: '登录', hidden: true }
  },
  {
    path: '/404',
    name: '404',
    component: Error,
    meta: { title: '404', hidden: true }
  }
]
// 任意路由
const anyRoute = [
  {
    // 任意路由,匹配不到路由重定向到404
    path: '/:pathMatch(.*)*',
    redirect: '/404',
    name: 'Any',
    meta: { title: '任意路由', hidden: true }
  }
]

export default [...constantRoute, ...anyRoute]

二.获取、存储动态路由

// store/modules/user.ts
import { defineStore } from 'pinia'
import routeList from '@/router/routes'
import { reqAsyncRoutes } from '@/api/user/index'
import router from '@/router/index.ts'
import Layout from '@/layout/index.vue'
import { markRaw } from 'vue'
// const modules = import.meta.glob('@/views/**/index.vue')

export const useUserStore = defineStore('User', {
  state: (): UserState => {
    return {
      menuRoute: routeList,
      asyncRoute: []
    }
  },
  actions: {
    userLoginOut() {
      this.token = ''
      // 清除路由信息
       this.asyncRoute.forEach((route: any) => {
        router.removeRoute(route.name)
      })
      this.asyncRoute = []
    },
    async fetchAsyncRoutes() {
      let result: any = await reqAsyncRoutes(this.userInfo.roleCode)
      if (result.code === 200) {
        // markRaw转成非响应式数据,减少vue开销
        this.asyncRoute = markRaw(this.filterASyncRoutes(result.data))
        this.asyncRoute.forEach(route => {
          router.addRoute(route)
        })
        return 'ok'
      } else {
        return Promise.reject(new Error(result.msg))
      }
    },
    filterASyncRoutes(routes: any) {
      const asyncRoute = routes.map((route: any) => {
        if (route.component === 'Layout') {
          route.component = Layout
        } else {
          // 第二次坑,route.component不能直接放里面
          const componentPath = route.component
          // 第三次坑,@/无法解析,使用/src代替
          /*
           * 1.@/无法解析,使用/src
           * 2.@vite-ignore 注释,vite不能分析导入的路径,实际应用无问题,使用注释解决,消除warning
           */
          route.component = () => import(/* @vite-ignore */ `/src/views${componentPath}/index.vue`)
          // route.component =modules[`/src/views${route.component}/index.vue`];
        }
        if (route.children) {
          // 递归  ,第一次坑,没返回
          route.children = this.filterASyncRoutes(route.children)
        }
        return route
      })
      return asyncRoute
    }
  },
  getters: {
    // 渲染菜单用
    allRoute: state => {
      if (state.asyncRoute.length > 0) {
        return state.menuRoute.concat(state.asyncRoute)
      } else {
        return state.menuRoute
      }
    }
  }
})

1.第一次坑是递归调用filterASyncRoutes的后没有赋值,所以子路由数据没有正确处理

2.第二次坑是 route.component 不能直接放里面,一开始是这样写的

route.component = () => import(`@/views${route.component}/index.vue`)
// 当前赋值看似没有问题,但是到addroute的时候,route.component为() => import(`@/views${route.component}/index.vue`),就会解析成下图的样子了

20240517192150
改:

const componentPath = route.component
route.component = () => import(`@/views${componentPath}/index.vue`)

3.第三次坑是@/无法解析,使用/src代替

20240517192132
最终:

const componentPath = route.component
route.component = () => import(/* @vite-ignore */ `/src/views${componentPath}/index.vue`)

4.关于上面两行注释的代码

const modules = import.meta.glob('@/views/**/index.vue')
route.component = modules[`/src/views${route.component}/index.vue`]
// 使用这种方法也可以导入路由组件,感觉和映射的方式差不多

三.路由守卫控制数据获取

// permisstion.ts
router.beforeEach(async (to, from, next) => {
  nprogress.start()
  if (userStore.token) {
    // 登录成功,不允许访问登录页
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      // 异步路由为空,则获取
      if (userStore.asyncRoute.length == 0) {
        await userStore.fetchAsyncRoutes()
         if (to.path == '/404') {
          // 跳转到原始路由
          next({ path: to.redirectedFrom?.path, replace: true })
        } else {
          next()
        }
        // 异步路由添加完成之后再添加404路由和任意路由,否则会直接跳转到404页面
        // router.addRoute({
        //   path: '/404',
        //   name: '404',
        //   component: Error,
        //   meta: { title: '404', hidden: true }

        // })
        // router.addRoute({
        //   // 任意路由,匹配不到路由重定向到404
        //   path: '/:pathMatch(.*)*',
        //   redirect: '/404',
        //   name: 'Any',
        //   meta: { title: '任意路由', hidden: true }
        // })
        // 路由表里面没有当前访问的路由,则会跳转到404页面

        // next({ ...to, replace: true })
        // next(to.path)
      } else {
        next()
      }
    }
  } else {
    if (to.path === '/login') {
      next()
    } else {
      next({ path: '/login', query: { redirect: to.path } })
      ElMessage.error('请先登录')
    }
  }
})

关于页面刷新,路由还未存在的处理

我使用的方法是:
1.将 404 和任意路由添加到常量路由里面,任意路由重定向到 404;
2.如果访问的路由在路由表里面没有,则跳转到任意路由,任意路由会重定向到 404;
3.在路由守卫里面判断,如果访问的路由为 404,则跳转到原始路由;
这样处理虽然感觉有点奇怪,但是控制台不会再出现 warning 了

关于上面代码注释的内容

router.addRoute({
  path: '/404',
  name: '404',
  component: Error,
  meta: { title: '404', hidden: true }
})
router.addRoute({
  // 任意路由,匹配不到路由重定向到404
  path: '/:pathMatch(.*)*',
  redirect: '/404',
  name: 'Any',
  meta: { title: '任意路由', hidden: true }
})
next({ ...to, replace: true })

1.不在常量路由里面添加 404 和任意路由,在动态添加路由之后,再添加 404 和任意路由,这样页面不会跳转到 404 页面,但是控制台会报 warning

20240517195408

2.还了解到一种方法,直接在常量路由里面添加任意路由,但是不重定向到 404,在路由导航守卫里面直接next({ ...to, replace: true }),能实现一样的功能(页面不空白,不 404,控制台不 warning),但是打印出来的要跳转的路由,看着怪怪的

const anyRoute = [
  {
    path: '/:pathMatch(.*)*',
    component: Error,
    name: 'Any',
    meta: { title: '任意路由', hidden: true }
  }
]

20240517195750
本质上我使用的方法和上述方法是一样的,我拿的 to.redirectedFrom 的数据,meta 也是图片中的样子
3.我的代码中next(to.path)next({ ...to, replace: true })好像没有区别

记录:
通过查找资料,发现将路由添加到路由表里面还有很多钟方法
1.前端把路由写好,通过后端传递的角色信息筛选添加到路由表里面
2.前端定义好路由组件的名称,根据后端返回的 component 值,进行映射,添加到路由表里面

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值