vue3+Element采用递归调用封装导航栏实现

目录
  • 效果预览
  • 模拟数据
    • 父组件aside.vue 
    • 子组件subAside.vue
  • 配置

    效果预览

    模拟数据

    • 数据来源有很多,可以是自己写死的,也可以是后端调用得到的,也可以从别的组件中拿到
    • 这里采用从路由中拿
    • 定义数据源src/router/module.js/
    const Login = () => import('../views/Login/Login.vue');
    const Layout = () => import('../layout/layout.vue');
    const Home = () => import('../views/Home.vue');
    const User = () => import('../views/About.vue');
    const Avatar = () => import('../views/Users/Avatar.vue');
    const Password = () => import('../views/Users/Password.vue');
    
    const routes = [
      {
        path: '/',
        redirect: '/home',
      },
      {
        path: '/',
        name: 'Layout',
        component: Layout,
        meta: {
          permission: true,
        },
        children: [
          {
            path: '/home',
            name: 'Home',
            component: Home,
            meta: {
              title: '首页',
              icon: '<span class="iconfont icon-shouye"/>', // iconfont图标
              inSide: true,
            },
          },
          {
            path: '/user',
            name: 'User',
            component: User,
            meta: {
              title: '个人中心',
              icon: '<span class="iconfont icon-yonghuzhongxin1"/>',
            },
            children: [
              {
                path: '/user/avatar',
                name: 'Avatar',
                component: Avatar,
                meta: {
                  title: '修改头像',
                },
                children: [
                  {
                    path: '/setUp/avatar',
                    name: 'setUp',
                    component: Avatar,
                    meta: {
                      title: '暂无',
                    },
                  },
                ],
              },
              {
                path: '/user/password',
                name: 'Password',
                component: Password,
                meta: {
                  title: '修改密码',
                },
              },
            ],
          },
          {
            path: '/setUp',
            name: 'SetUp',
            meta: {
              title: '系统设置',
              icon: '<span class="iconfont icon-celveguanli"/>',
            },
            children: [
              {
                path: '/setUp/avatar',
                name: 'setUp',
                component: Avatar,
                meta: {
                  title: '暂无',
                },
              },
              {
                path: '/setUp/avatar',
                name: 'setUp',
                component: Avatar,
                meta: {
                  title: '暂无',
                },
              },
            ],
          },
        ],
      },
    
      {
        path: '/login',
        name: 'Login',
        component: Login,
      },
    ];
    export default routes;
    

    递归实现导航栏渲染

    • 对于导航栏渲染难点在于不知道有多少层级的导航,可能一级也可能两级或者更多
    • 为了方便采用两个组件父组件aside.vue与子组件subAside.vue渲染导航
    • 这时候就需要采用递归的方式
      • 首先判断哪些数据需要渲染,需要的拿出来
      • 判断是否有子节点需要渲染
        • 有子节点,递归调用子组件本身
        • 没有子节点,返回导航项进行渲染

    父组件aside.vue 

    <template>
      <el-radio-group v-model="isCollapse" style="margin-bottom: 20px">
        <el-radio-button :label="false">expand</el-radio-button>
        <el-radio-button :label="true">collapse</el-radio-button>
      </el-radio-group>
      <el-menu
          default-active="2"
          class="el-menu-vertical-demo"
          :collapse="isCollapse"
          select="handleSelect"
          router
          unique-opened
      >
        <!-- 将渲染导航每一项传给子组件渲染,item代表要渲染每一项 -->
        <SubAside :isCollapse="isCollapse" v-for="(item,index) in navs" :key="item.path" :menu="item" :index="item.path" />
      </el-menu>
    
    </template>
    
    <script lang="ts" setup>
    import { ref } from 'vue';
    import router from '../router/module';
    const navs =router.filter((item) => item.meta?.permission)[0].children // 过滤拿到数据
    console.log(navs);
    
    const isCollapse = ref(true); // 是否收起,默认不收起
    
    </script>
    
    <style lang="scss" scoped></style>
    

    父组件处理后的用于渲染的数据

    [
    	{
    		component: () => import('/src/views/Home.vue')
    		meta: {title: '首页', icon: '<span class="iconfont icon-shouye"/>', inSide: true}
    		name: "Home"
    		path: "/home"
    	},
    	{
    		component: () => import('/src/views/About.vue')
    		meta: {title: '个人中心', icon: '<span class="iconfont icon-yonghuzhongxin1"/>'}
    		name: "User"
    		path: "/user"
    		chilren:[
    		{
    			children: [{…}]
    			component: () => import('/src/views/Users/Avatar.vue?t=1655544364909')
    			meta: {title: '修改头像'}
    			name: "Avatar"
    			path: "/user/avatar"
    		},
    		{
    			component: () => import('/src/views/Users/Password.vue')
    			meta: {title: '修改密码'}
    			name: "Password"
    			path: "/user/password"
    		}
    		]
    	},
    	{
    		meta: {title: '系统设置', icon: '<span class="iconfont icon-celveguanli"/>'}
    		name: "SetUp"
    		path: "/setUp"
    		chilren:[
    		{
    			component: () => import('/src/views/Users/Avatar.vue?t=1655544364909')
    			meta: {title: '暂无'}
    			name: "setUp"
    			path: "/setUp/avatar"
    		},
    		{
    			component: () => import('/src/views/Users/Avatar.vue?t=1655544364909')
    			meta: {title: '暂无'}
    			name: "setUp"
    			path: "/setUp/avatar"
    		}
    		]
    	}
    ]
    
    

    子组件subAside.vue

    <template>
        <!-- 有子节点渲染这个 -->
        <el-sub-menu :index="menu.path" v-if="menu?.children">
            <template #title>
                <el-icon v-html="menu?.meta.icon"></el-icon>
                <span>{{menu?.meta.title}}</span>
            </template>
            <!-- 递归调用本身,该组件在index.ts中全局注册了 -->
            <SubAside v-for="item in menu.children" :menu="item" :isCollapse="isCollapse"/>
        </el-sub-menu>
        <!-- 没有子节点渲染这个 -->
        <el-menu-item  v-else  :index="menu?.path">
                <el-icon v-html="menu?.meta.icon"></el-icon>
                <span slot="title">{{menu?.meta.title}}</span>
        </el-menu-item >
      
    </template>
       
    <script lang="ts" setup>
    import { ref } from "vue"
    // 拿到父组件传入的值
    defineProps({
      isCollapse:Boolean,
      menu:Object
    })
    
    
    </script>
    
    <style lang="scss" scoped>
    
    </style>
    

    配置

    版本

    "vue": "^3.2.25",
     "element-plus": "^2.2.6",
    

    main.ts中配置

    import { createApp } from 'vue';
    import App from './App.vue';
    // import 'virtual:windi.css';
    import router from './router/index';
    /**
     * 引入elment
     */
     import ElementPlus from 'element-plus'
     import 'element-plus/dist/index.css'
     import 'element-plus/theme-chalk/dark/css-vars.css'
     import './styles/dark/css-vars.css'
    
    
    // 引入 Pinia 状态管理工具
    import pinia from './stores'
    
    const app=createApp(App)
     /**
      * 全局注册组件
      */
    import SubAside from './components/subAside.vue'
    app.component('SubAside', SubAside)
    
    
    // 注册Element全局可用
    app.use(ElementPlus).use(router).use(pinia).mount('#app');
    

    本文转自网络,如有侵权请联系客服删除。