持续更新中。。。
1. 动态路由
-
后台路由模型数据 (如果后端不知道怎么转为 这种树结构的路由,可以参考 普通数组转树结构的数组)
const dynamicRoutes = [{path: '/',name: 'Layout',redirect: '/home',component: 'Layout',meta: {label: '',icon: '',hidden: false,},children: [{path: '/home',name: 'Home',component: 'Home',meta: {label: '首页',icon: 'HomeFilled',hidden: false}},]},{path: '/perm',name: 'perm',component: 'Layout',redirect: '/perm/user',meta: {label: '权限管理',icon: 'Lock',hidden: false},children: [{path: '/perm/user',name: 'user',component: 'User',meta: {label: '用户管理',icon: 'User',hidden: false},},{path: '/perm/role',name: 'role',component: 'Role',meta: {label: '角色管理',icon: 'Avatar',hidden: false},},{path: '/perm/menu',name: 'permMenu',component: 'PermMenu',meta: {label: '菜单管理',icon: 'Grid',hidden: false},},]},// 这个路由不能放到,常量路由里面,必须放到动态路由// 原因:在刷新页面时,动态路由丢失,需要重新加载动态路由,// 因为当前还没有动态路由,只能重定向到404,// 所以即使后面动态路由加载成功,也不会再去重定向到 动态路由{path: '/:pathMatcher(.*)*',redirect: '/404',name: 'any',meta: {label: '',icon: '',hidden: true}},]
-
后台路由模型 转 前端需要的路由对象
2.1 准备前端组件信息。这相当于是一个Map,在下面 转换 的时候会用到。import Layout from '@/layout/index.vue' import Home from '@/views/home/index.vue' import Screen from '@/views/screen/index.vue' import User from '@/views/perm/user/index.vue' import Role from '@/views/perm/role/index.vue' import PermMenu from '@/views/perm/menu/index.vue'export const allRouteComponents = {Layout,Home,Screen,User,Role,PermMenu, }
2.2 转换操作
// 将后端获取到的动态路由 添加到 router // 在路由守卫中使用,刷新页面时,重新添加动态路由,解决内存中路由丢失,白屏问题 // 这里传入的路由数组就是后端传过来的 const addRoutes = (routesArr) => {const routesObjArr = convertRoutes(routesArr)routesObjArr.forEach(item => {router.addRoute(item)}) }// 将后端获取到的路由对象的 component(字符串类型) 转为 component(组件对象) const convertRoutes = (routesArr) => {return routesArr.map(item => {let routeObj = allRouteComponents[item.component]if (routeObj) {item.component = routeObj}if (item.children) {item.children = convertRoutes(item.children)}return item}) }
-
动态路由添加 addRoutes()
const userStore = useUserStore()
const menuStore = useMenuStore()
仓库函数的使用要写在 路由守卫里面,写在外面会报pinia没有激活错误。
原因:
1、 在项目启动时,仓库可能还没有被use,肯定会报错,
2、 如果写在路由守卫里面,只有在切换路由的时候才会使用到仓库,这个时候仓库肯定已经被use了。registerFlag ,每次刷新页面这个值都会重新初始化为false,内存中路由也被清除。这个就需要重新添加 routes。添加routes后,registerFlag 置为 true。再执行路由切换不需要重复添加(只有刷新,才会重新触发)
// 刷新页面时,重新添加动态路由 const registerFlag = ref(false)router.beforeEach(async (to, from, next) => {const userStore = useUserStore()const menuStore = useMenuStore()// 判断用户是否登录if (userStore.token) {// 解决刷新页面,内存中路由丢失问题if (!registerFlag.value) {addRoutes(menuStore.userMenus)registerFlag.value = true// 添加完路由,重新访问目标页面await router.replace(to)}// 已经登录,访问/login,会重定向到 / (首页)if (to.path === '/login') {next('/home')} else {next()}} else {// 未登录if (to.path === '/login') {next()} else {// 重定向到/login,并追加一个 登录成功后重定向地址next({path: '/login', query: {redirect: to.path}})}} })
2. 动态侧边栏菜单
-
后端数据模型
const dynamicRoutes = [{path: '/',name: 'Layout',redirect: '/home',component: 'Layout',meta: {label: '',icon: '',hidden: false,},children: [{path: '/home',name: 'Home',component: 'Home',meta: {label: '首页',icon: 'HomeFilled',hidden: false}},]},{path: '/perm',name: 'perm',component: 'Layout',redirect: '/perm/user',meta: {label: '权限管理',icon: 'Lock',hidden: false},children: [{path: '/perm/user',name: 'user',component: 'User',meta: {label: '用户管理',icon: 'User',hidden: false},},{path: '/perm/role',name: 'role',component: 'Role',meta: {label: '角色管理',icon: 'Avatar',hidden: false},},{path: '/perm/menu',name: 'permMenu',component: 'PermMenu',meta: {label: '菜单管理',icon: 'Grid',hidden: false},},]},// 这个路由不能放到,常量路由里面,必须放到动态路由// 原因:在刷新页面时,动态路由丢失,需要重新加载动态路由,// 因为当前还没有动态路由,只能重定向到404,// 所以即使后面动态路由加载成功,也不会再去重定向到 动态路由{path: '/:pathMatcher(.*)*',redirect: '/404',name: 'any',meta: {label: '',icon: '',hidden: true}}, ]
-
后端路由数据–>过滤出hidden==false(用于侧边栏展示的)
<script setup> import AsideMenu from '@/layout/menu/index.vue' import {computed,defineOptions} from "vue"; defineOptions({name: 'Layout'})// 去 hidden 函数 // 如果父路由 隐藏,那么子路由hidden不论true,false,都不会显示 // 如果父路由 显示,那么子路由hidden为false会显示,hidden为true会隐藏 const filterHiddenRoute = (routes) => {return routes.filter(item => {if (!item.meta.hidden) {if (item.children) {item.children = filterHiddenRoute(item.children)}return true} else {return false}}) } // 去掉hidden后的菜单树 // 这里传入的routes,就是后端传来的路由数组(树结构) const menuList = computed(() => {return filterHiddenRoute(routes) }) </script> <template><el-menu><aside-menu :menuList="menuList"></aside-menu></el-menu> </template>
-
动态菜单递归组件定义
递归菜单必须有组件名,用于在组件中调用自身
<script setup> import router from '@/router'defineOptions({name: 'AsideMenu'}) const menus = defineProps(['menuList'])// 跳转路由 const goRoute = (e) => {router.push(e.index) } </script><template><template v-for="item of menus.menuList" :key="item.path"><!-- 没有子菜单--><el-menu-item v-if="!item.children" :index="item.path" @click="goRoute"><el-icon><component :is="item.meta.icon"></component></el-icon><template #title><span>{{ item.meta.label }}</span></template></el-menu-item><!-- 有子菜单,但是只有1个--><el-menu-item v-if="item.children && item.children.length===1" :index="item.children[0].path" @click="goRoute"><el-icon><component :is="item.children[0].meta.icon"></component></el-icon><template #title><span>{{ item.children[0].meta.label }}</span></template></el-menu-item><!-- 有子菜单,子菜单大于1个--><el-sub-menu v-if="item.children && item.children.length>1" :index="item.path"><template #title><el-icon><component :is="item.meta.icon"></component></el-icon><span>{{ item.meta.label }}</span></template><AsideMenu :menuList="item.children"></AsideMenu></el-sub-menu></template> </template> <style scoped lang="scss"> </style>