vue-router + element-plus实现多层级路由的动态展示

需求

前端的路由往往是分层级的,所有的路由组合起来就像是一棵树,为了方便说明,我们举一个简单的例子,如下图所示:
在这里插入图片描述

上图中除去根节点共有六个路由,每个叶子节点路由对应一个页面,每个父节点路由对应一个分组。
在开发单页面应用时,我们往往会需要一个导航栏或者称之为菜单栏,方便用户点击以实现路由间的跳转。基于element-plus组件库,我们希望这些路由组成如下图的形式的侧边菜单栏,当我们点击叶子节点路由时,我们会跳转到相应的路由页面。
在这里插入图片描述

我自己做了一个基于vue3+typescript的网站,网站中使用本博客中讲述的方式实现了一个侧边路由导航栏,这是网站预览地址gitee仓库地址。(ps:此网站并不是一个demo,而是具有完整前后端交互和一定功能的网站,需要注册并登陆才能进入主页面并使用,注册很简单,只需要设置用户名、账号、密码即可。也可登陆测试账号: admin,密码: 123456。 但如果有超过五个人同时登陆这个测试账号,那么后者会把前者挤下去,当然这种概率很小罢了。这里附上后端仓库地址,我是用go写的,大概总共二十多个接口吧)

静态展示方法

静态展示就是依据路由的层级和分组,使用ui组件形成相应的分层结构,然后直接把路由信息写到组件里,形成一个静态的菜单栏,就像element-plus的官网示例–Menu侧栏菜单那样。然后再为每个el-menu-item组件绑定点击事件,被点击后跳转到相应的路由,这样一个静态的路由菜单栏就写好了。

这种写法没有什么需要特别注意的地方,具体细节不再展示。

但这种写法有几个很明显的缺点:

  1. 过于繁琐,一个大型的单页面应用可能有几十个路由,这些路由会分为很多分组和很多层级,我们要手动地去录入这些分组和层级,这非常麻烦。
  2. 过于臃肿,几十个路由的展示可能需要一两百行甚至更多的<template>
  3. 不易阅读,臃肿的代码加上路由之间复杂的嵌套关系,使代码看起来就让人头疼
  4. 不易修改,随着产品的迭代更新,我们可能会改变路由信息和路由之间的分组与层级关系,这样我们就不得不修改菜单栏的源码。

动态展示方法

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

所谓的动态展示,就是我们在不知道路由的层级和分组的情况下,仅仅根据routes数组的内容来创建分级菜单栏。

首先先对routes数组进行筛选,得到最顶级的路由,也就是没有父路由的路由,譬如路由树图中的火星和地球。

接下来对自定义组件MenuEl.vue使用v-for指令,遍历顶级路由,把顶级路由作为props传递给MenuEl组件。

  • MenuEl是实现路由动态展示的核心组件,它接收一个路由作为参数,它的功能是展示这个路由,并递归地展示该路由的子路由,大致实现步骤如下:
    1. 如果传入参数中的route没有children,那么把该路由展示在el-menu-item组件中
    1. 如果传入的route参数有children,那么把该路由展示在el-sub-item组件中,并再el-sub-item中递归调用MenuEl组件,使用v-for指令遍历route.children,将route的每个chilld作为props参数传递给MenuEl。

然后分级的菜单就创建完毕了,具体的效果如上图所示

很多东西用文字是无法完全描述清楚的,具体还是得看下面的代码,先大致解释一下各个文件的大致内容:

  • router.ts,定义routes,创建router
  • handleRoutes.ts,提供筛选routes数组的方法handleRoutes(),返回路由数组中的顶级路由
  • MenuEl,实现动态展示的核心组件,功能和实现步骤如上所述
  • MenuIcon,这个组件主要是配置每个路由对应的图标
  • App.vue,路由的动态展示

当我们需要对路由层级和分组结构进行改变时,我们只需要更改router.ts中的routes即可,无需更改其他任何文件。比如我现在想创建一个名为宇宙的路由,而火星地球都是宇宙的子路由,那么我只需要更改routes即可,我创建一个描述为宇宙的路由,路径为/universe,然后把火星和地球对应的路由挪到宇宙路由的children里,代码运行的效果如下:
在这里插入图片描述

源代码

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自动化展示

<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>
  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Vite是一个用于快速构建现代化的Web项目的构建工具,它专注于开发阶段的快速热重载,并使用ES模块作为原生的开发模式。Vue3是Vue.js的最新版本,它在性能、开发体验和可维护性上都有所提升。 针对你提到的具体的库和框架: - Vue RouterVue.js官方的路由管理器,用于实现页面之间的导航和路由控制。你可以通过npm安装vue-router,并在项目中进行配置和使用。 - Pinia是Vue.js的状态管理库,它提供了一种简单而强大的方式来管理应用程序的状态。你可以集成Pinia到你的Vue项目中,以便更好地组织和共享你的应用程序状态。 - Axios是一个基于Promise的HTTP客户端,用于通过网络发送异步请求。你可以使用Axios来处理与服务器的通信,并获取数据来更新你的Vue应用程序。 - Element Plus是一套基于Vue3的组件库,包含了丰富的UI组件,可以帮助你快速搭建漂亮的用户界面。你可以在项目中安装和使用Element Plus来实现各种交互效果和用户界面。 如果你想使用Vite、Vue3和以上提到的库和框架来创建一个项目,你可以按照以下步骤进行: 1. 安装Vite:通过npm全局安装Vite,然后使用Vite命令初始化一个新的项目。 2. 配置Vite:根据你的项目需求,在Vite的配置文件中添加Vue Router、Pinia、Axios和Element Plus的相关配置。 3. 安装和配置Vue Router:通过npm安装Vue Router,并在项目中配置和使用Vue Router来管理应用程序的路由。 4. 集成Pinia:通过npm安装Pinia,并在项目中引入和配置Pinia,以便在应用程序中使用Pinia来管理状态。 5. 使用Axios:通过npm安装Axios,并在项目中引入和配置Axios,以便进行网络请求和数据获取。 6. 引入Element Plus:通过npm安装Element Plus,并在项目中按需引入和使用Element Plus的组件,以搭建漂亮的用户界面。 希望以上信息对你有帮助,祝你在使用Vite、Vue3和这些库和框架时取得成功!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [vite-vue-ts精简模版集成pinia+svg+router+@src](https://download.csdn.net/download/ldy889/85018930)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [vite+vue3+ts+vue-router+pinia+axios+element-plus](https://blog.csdn.net/zy_080400/article/details/127125359)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值