这篇是上一篇的延伸,详细记录下动态路由的使用。
项目中使用-子系统
动态菜单格式如下:
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' }) } } })
|