持续更新中。。。
后台路由模型数据 (如果后端不知道怎么转为 这种树结构的路由,可以参考 普通数组转树结构的数组)
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}})
}
}
})
后端数据模型
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>
<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>
<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>