手搓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.实现思路
- 首先要根据后端传来的
component
位置找到对应组件 - 把列表数据转换成树状结构
- 遍历菜单找到页面项
- 将页面添加到路由
router.addRoute()
- 渲染
el-menu
组件
4.动起手来
- 找组件可以通过
import.meta.glob('/src/views/**/*.vue')
找到views
文件夹下面所有都.vue
文件 - 将列表转成树状
/**
* 列表转树状
* @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
})
}
- 遍历数据
/**
* 遍历路由
* @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)
}
}
}
- 添加路由
/**
* 添加路由
* @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)
}
- 页面渲染
<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
的层级配对了,但是menu
的children
居然是空的,在跳转时也会报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>