手搓VUE-动态菜单(Vue/VueRouter/ElementPlus)

手搓VUE系列

这是个人学习Vue过程中的经验与问题小结
手搓VUE-Tabs标签页(Vue/VueRouter/ElementPlus)



前言

闲着没事就想着用Vue搭一个后台管理系统练练手,项目结构就不说了,那是后端的事情,这里只讲前端一些功能模块的实现


一、功能需求

接收后端传来的数据列表在前端转换为树状导航菜单

二、步骤

1.必要依赖

# Vue3官方路由库
npm add vue-router
# Element组件库
npm add element-plus

2.准备样例数据

后端发来的数据结构如下:

 interface MenuData {
    uid: string // 主键
    pid: string // 父ID
    name: string // 名称
    type: 'MENU' | 'PAGE' // 类型:菜单,页面
    title: string // 标题
    path: string // 路径
    component: string // 组件位置
}

3.实现思路

  1. 首先要根据后端传来的component位置找到对应组件
  2. 把列表数据转换成树状结构
  3. 遍历菜单找到页面项
  4. 将页面添加到路由router.addRoute()
  5. 渲染el-menu组件

4.动起手来

  1. 找组件可以通过import.meta.glob('/src/views/**/*.vue')找到views文件夹下面所有都.vue文件
  2. 将列表转成树状
/**
 * 列表转树状
 * @param list 列表数据
 * @param root  根
 * @param idKey 主键
 * @param pidKey 父键
 */
const list2Tree = (list: any, root = '', idKey = 'id', pidKey = 'pid') => {
    let obj: any = {}
    list.forEach((item: any) => {
        item.children = []
        obj[item[idKey]] = item
    })
    return list.filter((item: any) => {
        if (item[pidKey] !== root) {
            obj[item[pidKey]].children.push(item)
            return false
        }
        return true
    })
}
  1. 遍历数据
/**
 * 遍历路由
 * @param menus 菜单
 * @param components 组件
 */
const addMenuRoute = (menus: any[], components: any) => {
    for (let i = 0; i < menus.length; i++) {
        const menu = menus[i]
        const component = components[menu.component]
        if (menu.type == 'PAGE' && component != undefined) {
            addRoute(menu, component)
        } else {
            addMenuRoute(menu.children, components)
        }
    }
}
  1. 添加路由
/**
 * 添加路由
 * @param menu  路由菜单
 * @param components 组件
 */
const addRoute = (menu: MenuData, component: any) => {
    const route: RouteRecordRaw = {
        path: menu.path,
        name: menu.name,
        component: component,
        meta: {
            title: menu.title,
        },
    }
    router.addRoute(route)
}
  1. 页面渲染
<el-menu router>
	<MenuItem v-model="menuTree" />
</el-menu>
            
<template v-for="menu in modelValue">
    <template v-if="menu.children && menu.children.length > 0">
        <el-sub-menu :key="menu.path" :index="menu.path">
            <template #title>
                {{ menu.title }}
            </template>
            <MenuItem v-model="menu.children" />
        </el-sub-menu>
    </template>
    <template v-else>
        <el-menu-item :key="menu.path" :index="menu.path">
            {{ menu.title }}
        </el-menu-item>
    </template>
</template>

三、遇到的坑

通过打印router.getRoutes()可以发现虽然path的层级配对了,但是menuchildren居然是空的,在跳转时也会报Record with path "/menu1" is either missing a "component(s)" or "children" property.的提示
在这里插入图片描述

经过反复尝试,发现

router.addRoute({ name: 'admin', path: '/admin', component: Admin })
router.addRoute('admin', { path: 'settings', component: AdminSettings })

router.addRoute({
  name: 'admin',
  path: '/admin',
  component: Admin,
  children: [{ path: 'settings', component: AdminSettings }],
})

还是有本质区别的

那么,就换一个思路,在转换成树形结构的时候就按照RouteRecordRaw结构,把元素都填错到位,然后直接一发router.addRoute()给塞进去,不就可以了吗!

于是

/**
 * 列表直转路由
 *
 * @param list 菜单列表
 * @param components 本地组件
 * @param root 根值
 */
const listToRoutes: any = (list: any, components: any, root = '') => {
    let obj: any = {}
    list.forEach((item: any) => {
        item.children = []
        item.component = components[item.component]
        item.meta = {
            title: item.title,
            addMenu: true,
        }
        obj[item.id] = item
    })
    return list.filter((item: any) => {
        if (item.pid !== root) {
            obj[item.pid].children.push(item)
            return false
        }
        return true
    })
}

然后直接添加
注意:需要先给路由的外层加一个根目录

    router.addRoute({
        name: 'menu',
        path: '/menu',
        component: () => import('@/layout/index.vue'),
        children: routes,
    })

这样就把他们都塞到children中了

在这里插入图片描述

之后可以直接把routes交给el-menu就可以直接渲染出来
这里需要对MenuItem组件稍微进行修改,增加一个通过route.name进行跳转的方法

<script setup lang="ts">
import { RouteRecordRaw } from 'vue-router'

interface Props {
    modelValue: RouteRecordRaw[]
}
defineProps<Props>()
</script>

<template>
    <template v-for="route in modelValue">
        <template v-if="route.meta?.addMenu">
            <template v-if="route.children && route.children.length > 0">
                <el-sub-menu :key="route.name" :index="route.name as string">
                    <template #title>
                        {{ route.meta?.title || '无标题' }}
                    </template>
                    <MenuItem v-model="route.children" />
                </el-sub-menu>
            </template>
            <template v-else>
                <el-menu-item
                    :key="route.name"
                    :index="route.name as string"
                    @click="$router.push({ name: route.name })"
                >
                    {{ route.meta?.title || '无标题' }}
                </el-menu-item>
            </template>
        </template>
    </template>
</template>
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WARN "css.modules" option in vue.config.js is deprecated now, please use "css.requireModuleExtension" instead. INFO Starting development server... 98% after emitting CopyPlugin WARNING Compiled with 17 warnings 09:43:57 warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'computed' was not found in 'vue' warning in ./src/router/index.js "export 'default' (imported as 'VueRouter') was not found in 'vue-router' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'defineComponent' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'getCurrentInstance' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'h' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'inject' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'nextTick' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'onActivated' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'onDeactivated' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'onUnmounted' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'provide' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'reactive' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'ref' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'shallowRef' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'unref' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'watch' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'watchEffect' was not found in 'vue'这个报错因为什么
06-09

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值