这篇是上一篇的延伸,详细记录下动态路由的使用。
项目中使用-子系统
动态菜单格式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
   | {   "status": 200,   "msg": "菜单获取成功",   "data": [{     "id": 2,     "pid": 1,      "name": "一级菜单",     "title": "一级菜单",     "path": "/first",     "level": 1,     "fold": "",     "component": "BasicLayout",     "icon": "first-icon",     "hidden": false,     "status": true,     "meta": {       "title": "一级菜单",       "icon": "first-icon",       "hidden": false,       "level": 1,       "multiple": false     },     "children": [{       "id": 3,       "pid": 2,       "name": "二级菜单",       "title": "二级菜单",       "path": "/second",       "level": 1,       "fold": "home",       "component": "test",       "icon": "first-icon",       "hidden": false,       "status": true,       "children": [{         "id": 4,         "pid": 3,         "name": "删除",         "level": 3,         "hidden": false,         "status": true,         "api": "/api/partner/",         "method": "DELETE",       }]     }]   }] }
 
  | 
 
其中 level=3 数组,用于前端按钮展示,为了用户友好,系统定义若用户无权限访问(例如只能查看,不能修改),直接将操作按钮隐藏。
store/modules/permission.ts中存储菜单信息,这里MenuType添加了类型 fold ,表示文件夹地址,因为前端在工程设计时将不同组件存放于不同文件夹下,如果直接在path中写几层地址,这里会导致导入组件失败。
routes表示静态路由,例如:首页(/),404(/404)等应用开放给所有用户的页面,dynamicRoutes 表示动态路由,是在应用初始化时从后端获取的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
   | import { defineStore } from 'pinia' import { store } from '@/store/index' import { BasicLayout } from '@/views/layout/index' import router from '../../router' import { getUserRoutes } from '@/api/user'
  export type MenuType = {   path: string   title: string   fold?: string   meta?: any   component: string   redirect?: string   children?: Array<MenuType> }
  interface IPermissionState {   routes: any[]   dynamicRoutes: any[] }
 
  const modules = import.meta.glob('../../views/**/*.vue')
  const _import = (path: string, fold: string) => () => import(`../../views/${fold}/${path}.vue`)
  const assembleRouter = (routers: any) => {   const addRouter = routers.filter((router: any) => {     router.title &&       router.icon &&       (router.meta = {         title: router.title                })     if (router.component === 'BasicLayout') {       router.component = shallowRef(BasicLayout)     } else {       if (import.meta.env.MODE === 'dev') {         router.component = _import(router.component, router.fold)       } else {         router.component = modules[`../../views/${router.fold}/${router.component}.vue`]       }     }     if (router.children && router.children.length) {       router.children = assembleRouter(router.children)     }     return true   })   return addRouter }
  export const usePermissionStore = defineStore('permission', {   state: (): IPermissionState => ({     routes: [],     dynamicRoutes: [],   }),   getters: {     getRoutes(): any[] {       return this.routes     },     getDynamicRoutes(): any[] {       return this.dynamicRoutes     }   },   actions: {     async getMenus() {       try {         const { data } = await getUserRoutes({})          this.dynamicRoutes = data                  const addRouter = assembleRouter(this.dynamicRoutes)                  addRouter.forEach((ts: any) => {           router.addRoute(ts)         })       } catch (err) {         return Promise.reject(err)       }     },     clearMenus() {       this.dynamicRoutes.length = 0     }   } })
  export const usePermissionStoreWithOut = () => {   return usePermissionStore(store) }
 
  | 
 
Sidebar/index.vue中渲染菜单,这里可以使用
(1)router.getRoutes()
(2)router.options.routes 和d ynamicRoutes 的组合来展示侧栏菜单
但是 getRoutes 中携带的数据不够,这里需要自定义菜单的部分内容,所以还是采用获取的后端原始数据的 dynamicRoutes。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | <template>   <el-scrollbar wrap-class="scrollbar-wrapper">     <el-menu>       <sidebar-item         v-for="menu in menuList"         :key="menu.path"         :item="menu"       />     </el-menu>   </el-scrollbar> </template>
  <script setup lang="ts">   const menuList = computed(() => {          const routes = router.options.routes || []     const menus = permissionStore.dynamicRoutes || []     return routes.concat(menus)   }) </script>
 
  | 
 
子系统permission.ts路由守卫中中判断 token。
若有,执行获取动态菜单的操作,并且在获取成功之后进行过滤,如果当前去往的地址在白名单内,则放行;反之,跳转 404 页面。
若无 token,则跳转 UMS 首页。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | import router from './router' import { useUserStore } from '@/store/modules/user' import { usePermissionStore } from '@/store/modules/permission'
  router.beforeEach(async (to, from, next) => {   const userStore = useUserStore()   const token = userStore.getToken
    if (token) {     const permissionStore = usePermissionStore()     const { dynamicRoutes, getMenus } = permissionStore     if (dynamicRoutes.length === 0) {       try {                  await getMenus()         const allowList = router.getRoutes()         const userP = allowList.filter((item) => item.path == to.path)         if (userP.length > 0) {                      next({ ...to, replace: true })         } else {           next({ path: '/404' })         }       } catch (err) {         console.log(err)       }     } else {        next()     }   } else {      window.location.href = window.location.reload() })
 
  | 
 
同时在service.ts中也需要设置若 token 失效(请求中后端返回 401),跳转系统首页。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | service.interceptors.response.use(   (response: AxiosResponse<any>) => {     const { config, status, data } = response     if (config.responseType === 'blob') {       return response      } else if (status === result_code) {       return data     } else {       ElMessage.error(data.message)     }   },   (error: AxiosError) => {     console.log('error=', error)     if (error && String(error).indexOf('401') > -1) {              removeToken()       window.location.reload()     } else if (error && String(error).indexOf('403') > -1) {       ElMessage.error('当前没有操作权限,请联系管理员。')     } else {       ElMessage.error(error.message)       return Promise.reject(error)     }   } )
 
  | 
 
UMS系统
这里设置白名单,无 token 状态下白名单路由放行。
casdoor 登录成功跳转页(/cb)在白名单中,用作系统登录中转(使用 casdoor SDK 登录),成功后跳转首页。
同理,若在子系统已清除 token 情况下,跳转 UMS 首页(/ums),则通过首页中转后间接跳转登录页(/login)。
ums中的路由守卫如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | import router from './router' import { useUserStore } from '@/store/modules/user'
  const whiteList = ['/login', '/cb']
  router.beforeEach(async (to, from, next) => {   const userStore = useUserStore()   const token = userStore.getToken   if (token) {     if (to.path === '/login') {       next({ path: '/' })     } else {       next()     }   } else {     if (whiteList.indexOf(to.path) !== -1) {       next()     } else {       next({ path: '/login' })     }   } })
 
  |