Vue-router + element-plus realizes dynamic display of multi-level routing

need

Front-end routing is often hierarchical, and all routings are combined like a tree. For the convenience of illustration, let's give a simple example, as shown in the following figure:
insert image description here

There are six routes except the root node in the figure above, each leaf node route corresponds to a page, and each parent node route corresponds to a group.
When developing a single-page application, we often need a navigation bar or menu bar, which is convenient for users to click to jump between routes. Based on the element-plus component library, we hope that these routes form a side menu bar in the form of the following figure. When we click on a leaf node route, we will jump to the corresponding route page.
insert image description here

I made a website based on vue3+typescript by myself. The website uses the method described in this blog to implement a side routing navigation bar, which is the website preview address and gitee warehouse address . (ps: This website is not a demo, but a website with complete front-end and back-end interactions and certain functions. You need to register and log in to enter the main page and use it. Registration is very simple, you only need to set a user name, account number, and password. **You can also log in to the test account: admin, password: 123456.** But if two people log in to this test account at the same time, the latter will squeeze the former, of course, the probability of this is very small.)

static display method

Static display is to use ui components to form a corresponding hierarchical structure according to the hierarchy and grouping of routes, and then directly write the routing information into the components to form a static menu bar, just like the official website example of element-plus – Menu sidebar menu like that. Then bind the click event for each el-menu-item component, and jump to the corresponding route after being clicked, so that a static route menu bar is written.

There is nothing special to pay attention to in this way of writing, and the specific details will not be shown.

But this way of writing has several obvious disadvantages:

  1. It is too cumbersome. A large single-page application may have dozens of routes. These routes will be divided into many groups and levels. We have to manually enter these groups and levels, which is very troublesome.
  2. Too bloated, the display of dozens of routes may require one or two hundred lines or even more <template>
  3. Difficult to read, bloated code and complex nesting relationship between routes make the code look like a headache
  4. It is not easy to modify. With the iterative update of the product, we may change the routing information and the grouping and hierarchical relationship between routing, so we have to modify the source code of the menu bar.

Dynamic display method

import {
    
     useRouter } from 'vue-router'
const router = useRouter()
const routes = router.getRoutes() //routes数组,以一维数组的形式存放着所有路由

The so-called dynamic display means that we only create a hierarchical menu bar based on the contents of the routes array without knowing the hierarchy and grouping of the routes.

First, filter the routes array to get the top-level route, that is, the route without a parent route, such as Mars and Earth in the routing tree diagram .

Next, use the v-for instruction on the custom component MenuEl.vue to traverse the top-level routes and pass the top-level routes as props to the MenuEl component.

  • MenuEl is the core component to realize the dynamic display of routes . It receives a route as a parameter. Its function is to display the route and recursively display the sub-routes of the route. The approximate implementation steps are as follows:
    1. If the route in the incoming parameter has no children, then display the route in the el-menu-item component
    1. If the route parameter passed in has children, then display the route in the el-sub-item component, and recursively call the MenuEl component in the el-sub-item, use the v-for command to traverse route. A chilld is passed to MenuEl as a props parameter.

Then the hierarchical menu is created, and the specific effect is shown in the figure above .

Many things cannot be fully described in words. For details, you still have to look at the following code. Let me briefly explain the general content of each file:

  • router.ts, define routes, create router
  • handleRoutes.ts, provides a method to filter the routes array handleRoutes(), returns the top-level route in the route array
  • MenuEl, the core component to achieve dynamic display, its functions and implementation steps are as described above
  • MenuIcon, this component is mainly to configure the icon corresponding to each route
  • App.vue, dynamic display of routing

When we need to change the routing hierarchy and grouping structure, we only need to change the routes in router.ts without changing any other files . For example, I now want to create a route called universe, and Mars and Earth are sub-routes of the universe, then I only need to change the routes, I create a route described as universe, the path is /universe, and then put Mars and Earth The corresponding route is moved to the children of the universe route, and the effect of the code operation is as follows:
insert image description here

source code

router.ts

import {
    
     createWebHashHistory, createRouter, RouteRecordRaw} from 'vue-router'

declare module 'vue-router'{
    
    
    interface RouteMeta{
    
    
        description:string
    }
}
export const routes:readonly RouteRecordRaw[] = [
    {
    
    
        component: () => import('../views/Nothing.vue'), 
        path: '/mars',
        name: 'Mars',
        meta: {
    
     description: '火星'}
    },
    {
    
    
        path: '/earth',
        name: 'Earth',
        component: () => import('../views/Nothing.vue'),
        meta: {
    
    description: '地球'},
        children: [
            {
    
    
                component: () => import('../views/Nothing.vue'), 
                path: 'asia',
                name: 'Asia',
                meta: {
    
     description: '亚洲'},
                children: [
                    {
    
    
                        component: () => import('../views/Nothing.vue'), 
                        path: 'china',
                        name: 'China',
                        meta: {
    
     description: '中国'}
                    },
                    {
    
    
                        component: () => import('../views/Nothing.vue'), 
                        path: 'singapore',
                        name: 'Singapore',
                        meta: {
    
     description: '新加坡'}
                    },
                ]
            },
            {
    
    
                component: () => import('../views/Nothing.vue'), 
                path: 'europe',
                name: 'Europe',
                meta: {
    
     description: '欧洲'}
            },
        ]
    } 
]
const router = createRouter({
    
    
    history:createWebHashHistory(),
    routes
})
export default router

handleRoutes.ts

import {
    
     RouteRecordRaw } from "vue-router";

function isChild(route:RouteRecordRaw, pRoute:RouteRecordRaw):boolean{
    
    
    if(pRoute.children && pRoute.children.find(child =>`${
      
      pRoute.path}/${
      
      child.path}` === route.path)){
    
    
        return true
    }
    return false
}

export function handleRoutes(routes:RouteRecordRaw[]):RouteRecordRaw[]{
    
    
    let map = new Map<string, boolean>() //该路由是否为顶级路由
    routes.forEach(route => {
    
    
        if(routes.find(pRoute => isChild(route, pRoute))){
    
    
            map.set(route.path, false) //该路由是某个路由的子路由
        }
        else{
    
    
            map.set(route.path, true) //该路由不是任何路由的子路由,是顶级路由
        }
    })
    return routes.filter(route => map.get(route.path)) //返回所有顶级路由
}

MenuIcon.vue

<template>
  <el-icon v-if="route.name === 'Earth'"><Watermelon /></el-icon>
  <el-icon v-if="route.name === 'Asia'"><Pear /></el-icon>
  <el-icon v-if="route.name === 'China'"><Sugar /></el-icon>
  <el-icon v-if="route.name === 'Singapore'"><Coffee /></el-icon>
  <el-icon v-if="route.name === 'Europe'"><Grape /></el-icon>
  <el-icon v-if="route.name === 'Mars'"><Cherry /></el-icon>
</template>

<script setup lang="ts">
import {
    
     RouteRecordRaw } from "vue-router";
const props = defineProps<{
      
      
  route: RouteRecordRaw
}>()
</script>

<style scoped>

</style>

MenuEl.vue

<template>

  <!-- 如果传进来的路由没有子路由,则当前路由为子路由,展示路由信息,并设置跳转 -->
  <el-menu-item v-if="!route.children?.length" :index="route.path" @click="goToRoute(route)">
    <slot name="description"></slot>
  </el-menu-item>

  <!-- 如果传进来的路由有子路由,则当前路由为父路由,展示路由信息为标题,然后递归套用MenuEl展示其子路由 -->
  <el-sub-menu v-if="route.children?.length" :index="route.path">
    <template #title>
      <slot name="description"></slot>
    </template>
    <!-- 递归套用MenuEl -->
    <MenuEl v-for="childRoute in route.children" :key="childRoute.path" :route="childRoute">
      <template #description>
            <MenuIcon :route="childRoute"></MenuIcon>
            {
   
   {childRoute.meta?.description}}
        </template>
    </MenuEl>
  </el-sub-menu>
</template>

<script setup lang="ts">
import {
      
       RouteRecordRaw } from "vue-router";
import {
      
       useRouter } from 'vue-router'
const props = defineProps<{
      
      
  route: RouteRecordRaw
}>()
const router = useRouter()

function goToRoute(route:RouteRecordRaw){
      
      
  router.push(route)
}
</script>

<style scoped>

</style>

App.vue automation display

<template>
    <el-menu 
      active-text-color="#ffd04b"
      background-color="#545c64"
      class="el-menu-vertical-demo"
      default-active="0"
      text-color="#fff"
    >
      <MenuEl v-for="route in routes" :key="route.path" :route="route">
        <template #description>
          <MenuIcon :route="route"></MenuIcon>
          {
   
   {route.meta?.description}}
        </template>
      </MenuEl>
    </el-menu>
</template>

<script setup lang="ts">
import MenuEl from './components/MenuEl.vue'
import {
      
      ref, computed} from 'vue'
import {
      
       useRouter } from 'vue-router'
import {
      
       handleRoutes } from './utils/handleRoutes';
const router = useRouter()
console.log(router.getRoutes())
const routes = handleRoutes(router.getRoutes())

console.log(routes)

function test(){
      
      
  console.log(handleRoutes(router.getRoutes()))
}

</script>

<style >
  .el-menu{
      
      
    width: 250px;
  }
  body, html{
      
      
    width: 100%;
    height: 100%;
    min-height: 600px;
    min-width: 900px;
    margin: 0;
  }
  #app{
      
      
    display: flex;
    position: relative;
    width: 100%;
    height: 100%;
  }
</style>

Guess you like

Origin blog.csdn.net/m0_52744273/article/details/128178390