Vue3后台管理-项目总结

后台管理

  • 1. 动态路由
  • 2. 动态侧边栏菜单

持续更新中。。。

1. 动态路由

  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. 后台路由模型 转 前端需要的路由对象
    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
        })
    }
    
  3. 动态路由添加 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. 动态侧边栏菜单

  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. 后端路由数据–>过滤出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>
    
  3. 动态菜单递归组件定义

    递归菜单必须有组件名,用于在组件中调用自身

    <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>
    

你可能感兴趣的:(树结构,递归组件,前端,Vue3,动态路由,动态菜单)