后台管理
持续更新中。。。
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>