Vue dynamically adds routes

1. Idea

Each user has his own role, and different roles have different asynchronous routes (each role has constant routes). At this time, it is necessary to calculate the routing information in all user information returned by the backend after the user successfully logs in. The routes owned by the user are filtered out in asynchronous routing, and then dynamically added to the router. Finally, with the recursive creation of the route navigation menu, the routes corresponding to the user’s role are displayed to the user.

Related codes in 2.router
//Implement template routing configuration through vue-router plug-in
import { createRouter, createWebHashHistory } from 'vue-router'
import { constantRoute } from './routes'
//Create router
const router = createRouter({
  //routing mode hash
  history: createWebHashHistory(),
  routes: constantRoute,
  //Scroll behavior
  scrollBehavior() {
    return {
      left: 0,
      top: 0,
    }
  },
})
export default router
//Exposed configuration route (constant route): a route that all users can access
export const constantRoute = [
  {
    //Log in
    path: '/login',
    component: () => import('@/views/login/index.vue'),
    name: 'login',
    meta: {
      title: 'Login', //Menu title
      hidden: true, //Represents whether the routing title is hidden in the menu true: hidden false: not hidden
      icon: 'Promotion', //The icon to the left of the menu text, supports all element-plus icons
    },
  },
  {
    //Route to display data after successful login
    path: '/',
    component: () => import('@/layout/index.vue'),
    name: 'layout',
    meta: {
      title: '',
      hidden: false,
      icon: '',
    },
    redirect: '/home',
    children: [
      {
        path: '/home',
        component: () => import('@/views/home/index.vue'),
        meta: {
          title: 'Home',
          hidden: false,
          icon: 'HomeFilled',
        },
      },
    ],
  },
  {
    //404
    path: '/404',
    component: () => import('@/views/404/index.vue'),
    name: '404',
    meta: {
      title: '404',
      hidden: true,
      icon: 'DocumentDelete',
    },
  },
  {
    path: '/screen',
    component: () => import('@/views/screen/index.vue'),
    name: 'Screen',
    meta: {
      hidden: false,
      title: 'Data Big Screen',
      icon: 'Platform',
    },
  },
]

//Asynchronous routing
export const asnycRoute = [
  {
    path: '/acl',
    component: () => import('@/layout/index.vue'),
    name: 'Acl',
    meta: {
      title: 'Permission Management',
      icon: 'Lock',
    },
    redirect: '/acl/user',
    children: [
      {
        path: '/acl/user',
        component: () => import('@/views/acl/user/index.vue'),
        name: 'User',
        meta: {
          title: 'User Management',
          icon: 'User',
        },
      },
      {
        path: '/acl/role',
        component: () => import('@/views/acl/role/index.vue'),
        name: 'Role',
        meta: {
          title: 'Role Management',
          icon: 'UserFilled',
        },
      },
      {
        path: '/acl/permission',
        component: () => import('@/views/acl/permission/index.vue'),
        name: 'Permission',
        meta: {
          title: 'Menu Management',
          icon: 'Monitor',
        },
      },
    ],
  },
  {
    path: '/product',
    component: () => import('@/layout/index.vue'),
    name: 'Product',
    meta: {
      title: 'Product Management',
      icon: 'Goods',
    },
    redirect: '/product/trademark',
    children: [
      {
        path: '/product/trademark',
        component: () => import('@/views/product/trademark/index.vue'),
        name: 'Trademark',
        meta: {
          title: 'Brand Management',
          icon: 'ShoppingCartFull',
        },
      },
      {
        path: '/product/attr',
        component: () => import('@/views/product/attr/index.vue'),
        name: 'Attr',
        meta: {
          title: 'Attribute Management',
          icon: 'ChromeFilled',
        },
      },
      {
        path: '/product/spu',
        component: () => import('@/views/product/spu/index.vue'),
        name: 'Spu',
        meta: {
          title: 'SPU Management',
          icon: 'Calendar',
        },
      },
      {
        path: '/product/sku',
        component: () => import('@/views/product/sku/index.vue'),
        name: 'Sku',
        meta: {
          title: 'SKU Management',
          icon: 'Orange',
        },
      },
    ],
  },
]
3.Related codes in store
//Introduce all routes
import { constantRoute, asnycRoute} from '@/router/routes'

//Introduce deep copy method
//@ts-expect-error
import cloneDeep from 'lodash/cloneDeep'
import router from '@/router'
//Used to filter the asynchronous routes that the current user needs to display
function filterAsyncRoute(asnycRoute: any, routes: any) {
  return asnycRoute.filter((item: any) => {
    if (routes.includes(item.name)) {
      if (item.children & amp; & amp; item.children.length > 0) {
        //Silicon Valley 333 account: product\trademark\attr\sku
        item.children = filterAsyncRoute(item.children, routes)
      }
      return true
    }
  })
}
 //Calculate the asynchronous routes that the current user needs to display
        const userAsyncRoute = filterAsyncRoute(
          cloneDeep(asnycRoute),
          result.data.routes,
        )
        //The data required for the menu has been sorted out
        this.menuRoutes = [...constantRoute, ...userAsyncRoute];
        //Currently, the router manages only constant routes: asynchronous routes are dynamically added after the user has calculated them.
        [...userAsyncRoute].forEach((route: any) => {
          router.addRoute(route)
        })
4. Layout the menu component in the container component
<template>
    <template v-for="(item, index) in menuList" :key="item.path">
        <!--No sub-routes-->
        <template v-if="!item.children">
            <el-menu-item :index="item.path" v-if="!item.meta.hidden" @click="goRoute">
                <el-icon>
                    <component :is="item.meta.icon"></component>
                </el-icon>
                <template #title>
                    <span>{<!-- -->{ item.meta.title }}</span>
                </template>
            </el-menu-item>
        </template>
        <!-- There are sub-routes but there is only one sub-route -->
        <template v-if="item.children & amp; & amp; item.children.length == 1">
            <el-menu-item :index="item.children[0].path" v-if="!item.children[0].meta.hidden" @click="goRoute">
                <el-icon>
                    <component :is="item.children[0].meta.icon"></component>
                </el-icon>
                <template #title>
                    <span>{<!-- -->{ item.children[0].meta.title }}</span>
                </template>
            </el-menu-item>
        </template>
        <!-- There are sub-routes and the number is greater than one 1 -->
        <el-sub-menu :index="item.path" v-if="item.children & amp; & amp; item.children.length > 1">
            <template #title>
                <el-icon>
                    <component :is="item.meta.icon"></component>
                </el-icon>
                <span>{<!-- -->{ item.meta.title }}</span>
            </template>
            <Menu :menuList="item.children"></Menu>
        </el-sub-menu>
    </template>
</template>

<script setup lang="ts">
import { useRouter } from 'vue-router';
//Get all routing arrays passed by the parent component
defineProps(['menuList']);

//Get router object
let $router = useRouter();
//Callback for click menu
const goRoute = (vc: any) => {
    //route jump
    $router.push(vc.index);
}
</script>

<style scoped></style>

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Vue entry skill tree Home page Overview 39844 people are learning the system