阅读量:2
动态路由功能写法因人而异,本文只做参考!
实现思路
1.在路由钩子里面判断是否首次进入系统(permission.ts)
2.判断token是否有值。没有值回到登陆页面,
3.token有值判断MenusList是否有值,没有则获取路由
4.解析路由,拼接路由,放行路由
1. 引入vue-router 设置静态路由 登陆,首页
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/', redirect: '/home/index' }, { path: '/login', name: 'login', component: () => import('@/views/login/index.vue') }, { path: '/layout', name: 'layout', component: () => import('@/layout/layout.vue'), redirect: '/home/index', children: [] }, { path: '/:pathMatch(.*)*', component: () => import('@/views/404.vue') }, ] // qiankun 子应用路由 (项目中接入qiankun微前端路由写法) // import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper' // const router = createRouter({ // history: createWebHistory( // qiankunWindow.__POWERED_BY_QIANKUN__ // ? '/cms/' // : '/' // ), // routes // }) // 正常路由写法 const router = createRouter({ history: createWebHistory(),// HTML5的history模式 routes: constantRoutes }) export default router
2. 动态获取后端路由 (stores/menu.ts)
递归解析路由 根据后端返回文件路径匹配路由
import { defineStore } from 'pinia' import { getShowMenuList, generateRouter, arrayToTree } from "@/utils/tool" import { getAllMenuByUserName } from '@/api/commonApi' export const useMenuStore = defineStore({ id: 'menu', state: (): any => ({ menusList: [], cmsMenusList: [] }), getters: { showMenuListGet: state => getShowMenuList(state.cmsMenusList) }, actions: { async getMenu() { await getAllMenuByUserName({}).then(res => { if (res.data?.status == 200) { // 过滤出 parentId 为 2 的菜单项 let filteredA = res.data.data.sysMenuResp.filter((item: any) => item.parentId === 2) || []; // 过滤出其 parentId 存在于 filteredA 中的菜单项 let filteredB = res.data.data.sysMenuResp.filter((item: any) => { return filteredA.some((itemA: any) => itemA.menuId == item.parentId); }) || []; // 将 filteredA 和 filteredB 合并为一个扁平化的树结构数组 let flattenedArray = generateRouter(arrayToTree([...filteredA, ...filteredB])); // 构建最终的菜单列表,包括固定的首页菜单项和动态生成的菜单项 this.cmsMenusList = [ { name: 'home', path: '/home/index', component: () => import('@/views/home/index.vue'), meta: { icon: 'home-outlined', title: '首页', isKeepAlive: false } }, ...flattenedArray, ]; } }); } } }) // import { getShowMenuList, generateRouter, arrayToTree } from "@/utils/tool" // 过滤菜单 不需要在菜单栏中显示 export function getShowMenuList(menuList: Menu.MenuOptions[]) { let newMenuList: Menu.MenuOptions[] = JSON.parse(JSON.stringify(menuList)); let arr: any = newMenuList.filter(item => { item.children?.length && (item.children = getShowMenuList(item.children)); return !item.meta?.isHide; }); return arr } // 数组转树 export function arrayToTree(array: any) { const map: any = {}; // 使用对象存储每个节点的引用 // 遍历数组,将每个节点的引用存储到 map 中 array.forEach((item: any) => { map[item.menuId] = { ...item, children: [] } // 创建一个包含子节点数组的新节点对象 }) const tree: any = [] // 遍历数组,将每个节点添加到其父节点的 children 数组中 array.forEach((item: any) => { const node = map[item.menuId] // 注意:这里是父级菜单的id,本项目中返回的数据是多个系统菜单的集合数据, // 在项目开发中后端可能返回只属于当前系统的数据, if (item.parentId === 2) { // 没有父节点,说明是根节点 tree.push(node) } else { const parent = map[item.parentId] parent.children.push(node) } }) // console.log(tree) return tree } const modules = import.meta.glob("../views/**/*.vue") export const generateRouter = (routerMap: any) => { return routerMap.map((item: any) => { const currentRouter: any = { path: item.singlePath, name: item.query, redirect: item.redirect, meta: { icon: item.icon, title: item.menuName, isKeepAlive: true, // keepAlive 缓存 可以根据菜单设置动态显示,此处写死的, isHide: item.visible == 0 ? false : true // 显示隐藏 0 显示 1隐藏 } }; if (item.singleComponent && typeof item.singleComponent == "string") { currentRouter.component = modules[`../views/${item.singleComponent}.vue`] } // 是否存在子路由 item?.children?.length && (currentRouter.children = generateRouter(item.children)) return currentRouter; }) }
切记 keepAlive 缓存需要绑定组件name!!
const modules = import.meta.glob(“…/views//*.vue") : 解析
动态导入符合模式 "…/views//*.vue” 的模块(文件)
import.meta.glob 函数:这是一个特殊的函数,在ES模块环境中它允许根据文件模式动态导入模块
文件模式 (“…/views/**/*.vue”):该模式指定了要导入的所有以 .vue 扩展名结尾的模块(或文件),这些文件位于 …/views/ 目录下的任何子目录中
import.meta.glob 函数返回一个对象,对象的键是匹配到的文件路径,对应的值是一个函数。这个函数调用时返回一个 Promise,该 Promise 解析为导入模块的默认导出。
3.动态挂载路由permission.ts
import type { RouteRecordRaw } from 'vue-router' import { useMenuStore } from '@/stores/menu' import router from './index' import nprogress from 'nprogress' import 'nprogress/nprogress.css' import { useCounterStore } from '@/stores/counter' import { useLocalStore } from '@/stores/local' import { qiankunWindow, } from 'vite-plugin-qiankun/dist/helper' /** 免登录白名单 */ const whiteList: Array<string> = ['/login', '404'] router.beforeEach(async (to, from, next) => { const menuStore = useMenuStore() // const counterStore = useCounterStore() const localStore = useLocalStore() nprogress.start() // ps qiankun子应用处理菜单特殊处理 不用看 // counterStore.latoutStatus = !qiankunWindow.__POWERED_BY_QIANKUN__ // !qiankunWindow.__POWERED_BY_QIANKUN__ ? window.document.title = (to.meta.title || localStore.homeBasic?.sysName) as string : '' const token = localStorage.token if (token) { if (!menuStore.cmsMenusList.length) { await localStore.getInfo() // 获取个人信息 await menuStore.getMenu() // 获取菜单 await localStore.getUserPermiss() // 获取 按钮权限数据 menuStore.cmsMenusList.forEach((item: any) => { if (item.meta.isFull) { router.addRoute(item as unknown as RouteRecordRaw) } else { router.addRoute('layout', item as unknown as RouteRecordRaw) } }) return next({ ...to, replace: true }) } else { next() } // next(); } else { if (whiteList.indexOf(to.path) !== -1) { next() } else { next('/login') nprogress.done() } } }) router.onError((error) => { localStorage.clear() router.replace('/login') location.reload() console.warn('路由错误', error.message) }) router.afterEach(() => { const counterStore = useCounterStore() counterStore.showVisible = 1 nprogress.done() })
左侧菜单栏组件 使用
<MenuItem :menus="menus" /> const menus: any = computed(() => menuStore.showMenuListGet)
// 菜单组件
<template> <template v-for="item in menus" :key="item.path"> <!-- 目录 --> <template v-if="item.children && item.children.length"> <a-sub-menu :key="item.path"> <template #icon> <component :is="item.meta.icon"></component> </template> <template #title> <span>{{ item.meta.title }}</span> </template> <template #default> <!-- 递归生成菜单 --> <meun-item :menus="item.children"></meun-item> </template> </a-sub-menu> </template> <!-- 菜单 --> <template v-else> <a-menu-item :key="item.path"> <template #icon v-if="item.meta.icon"> <component :is="item.meta.icon"></component> </template> <span>{{ item.meta.title }}</span> <!-- <a :href=`'/'+${item.path}`>11</a> --> <router-link :to="item.path"></router-link> </a-menu-item> </template> </template> </template> <script lang="ts"> import { type PropType } from 'vue' import { RouteRecordRaw } from 'vue-router' export default { name: 'MeunItem', props: { menus: { type: Array as PropType<RouteRecordRaw[]>, default: () => [] } } } </script> <style scoped></style>
// 全局icon组件 在main.ts 引入
import * as Icons from '@ant-design/icons-vue' Object.keys(Icons).forEach((key) => { app.component(key, Icons[key as keyof typeof Icons]) })