业务需求
菜单项是业务系统的重要组成部分,一般业务系统都要支持显示多级业务菜单,但是根据每个业务人员的权责不同,看到的的菜单项也是不同的。
这就要求页面可以支持无限极菜单显示,根据每个用户的权限不同,后台服务返回对应的菜单项。
本文基于Vue 3.0实现了一个可配置的无限等级菜单,关键代码如下:
后端返回的菜单项数据结构
后端服务一般不会直接返回一个树型结构菜单集合给前端,这样做也不合理。前端应该根据自己的具体需求,构建自己的菜型单树。后端返回的数据结构一般包含以下一个字段:
- Id 菜单ID, 数字类型
- pId当前菜单的父级菜单ID, 数字类型
- title 菜单的标题
- link 菜单对应的链接
- order 同级菜单的排列顺序,数字类型
其他业务字段需要具体问题具体分析,在这里不再赘述。本文不再讨论后端如何进行菜单项的权限控制,所使用的菜单内容,包括在一个JSON文件中,具体见附录。
菜单内容是一个足球数据管理系统,包括多级菜单:
- 第一级菜单只有一项,是所有节点的祖先节点。
- 第二级菜单包括联赛管理,俱乐部管理和球员管理
- 第三级菜单包括二级菜单内容的CRUD。
关键代码
为了支持无限级菜单,本文所有关键算法全部基于递归实现。主要包括:
1.后端数据转换为树形结构
2.后端数据排序
3.基于菜单树形结构生成Vue的路由数据
4.菜单组件的递归调用
后端数据转为树形结构
dataToTree函数调用的实参是附录的JSON数据,该代码参考Vue 3.0的AST树转换的代码,具体思想是:
1.将集合的数据分为父节点和子节集合,最外层的父节点为pId为0的节点。
2.在子节点中找到当前父节点的直接子节点,将其从当前子节点集合剔除。
3.递归回到1,寻找子节点的子节点。
4.如果当前子节点不是任何节点的父节点,将该子节点放入父节点的children集合中。
在生成当前树型结构菜单数据后,可以将该数据保存在vuex中,作为公共数据便于其他模块使用。
function dataToTree(data) {
const parents = data.filter((item) => item.pId === 0);
const children = data.filter((item) => item.pId !== 0);
toTree(parents, children);
return parents;
function toTree(parents, children) {
for (var i = 0; i < parents.length; ++i) {
for (var j = 0; j < children.length; ++j) {
if (children[j].pId === parents[i].Id) {
let _children = deepClone(children, []);
toTree([children[j]], _children);
if (parents[i].children) {
parents[i].children.push(children[j]);
} else {
parents[i].children = [children[j]];
}
}
}
}
}
}
function deepClone(source, target) {
var _tar = target || {};
let keys = Reflect.ownKeys(source);
keys.map((key) => {
if (typeof source[key] === "object") {
_tar[key] =
Object.prototype.toString.call(source[key]) === "[object Array]"
? []
: {};
deepClone(source[key], _tar[key]);
} else {
_tar[key] = source[key];
}
});
return _tar;
}
菜单项排序
根据同级节点的order值进行排序,本文没有将该排序和上节的树型结构转换放在一起,主要是考虑有些系统可能不需要排序。如果需要,每次添加元素都要进行一次排序,效率低下,所以在获取树型结构后,再进行一次排序,具体排序函数如下:
function SortTree(tree) {
tree = tree.sort((a, b) => a.order - b.order);
tree.map((t) => {
if (t.children) {
t.children = SortTree(t.children);
}
});
return tree;
采用最简单的递归方式,遍历当前树型集合,按照order字段的升序方式进行排序,如果当前节点有children项,递归排序。
基于菜单树形结构生成Vue的路由数据
在获取树型菜单后后,我们可以基于当前数据,生成该用户在App中要使用到的路由项,具体代码如下:
function TreeToRoutes(treeData, routes) {
routes = routes || [];
for (var i = 0; i < treeData.length; ++i) {
routes[i] = {
path: treeData[i].link,
name: treeData[i].name,
component: () => import(`@/views/${treeData[i].name}`),
};
if (treeData[i].children) {
routes[i].children = TreeToRoutes(
treeData[i].children,
routes[i].children
);
}
}
return routes;
}
1.遍历树型菜单,将当前菜单项的link和tname复制到Vue路由数据的path和name上,component采用动态加载方式。
2.如果当前菜单项包含子节点children,递归调用,复制其子节点内容。
在main.js方法中,将菜单数据通过vuex进行读取,然后调用上述算法生成路由数据。将该数据直接加载到Vue的路由中,保证了如果当前用户没有某一个菜单的权限,即使通过URL进行访问,也是访问不到的,因为App只会为有权限的菜单项生成路由数据。如果用户没有某一个菜单的权限,也就不会从后端获取到该菜单的数据,也就不会为该菜单项生成路由。
菜单组件的递归调用
菜单组件代码如下:
<template>
<div>
<ul v-if="data.children && data.children.length > 0">
<li><router-link :to="data.link">{{data.title}}</router-link></li>
<menu-item :data="item" :key="index" v-for="(item,index) in data.children">
</ul>
<ul v-else>
<li><router-link :to="data.link">{{data.title}}</router-link></li>
</ul>
</div>
</template>
<script>
export default {
name: "MenuItem",
props:{
data: Object
}
}
</script>
如果当前菜单项包含子节点,则递归调用MenuItem组件自己
菜单组件调用的代码如下:
<template>
<div>
<menu-item :data="item" :key="index" v-for="(item,index) in data" />
</div>
</template>
<script>
import MenuItem from './MenuItem'
export default {
name: "Page",
components:{
MenuItem
}
}
</script>
由于生成的菜单数据结构最外层是数据,所以MenuItem组件需要进行循环调用。
附录-菜单项数据
export default [
{
Id: 15,
pId: 0,
name: "all",
title: "all",
link: "/all",
order: 2,
},
{
Id: 1,
pId: 15,
name: "clubs",
title: "Club Management",
link: "/clubs",
order: 2,
},
{
Id: 2,
pId: 15,
name: "leagues",
title: "League Management",
link: "/leagues",
order: 1,
},
{
Id: 3,
pId: 15,
name: "players",
title: "Player Management",
link: "/players",
order: 3,
},
{
Id: 5,
pId: 2,
name: "LeagueDelete",
title: "Delete League",
link: "/leagues/delete",
order: 3,
},
{
Id: 6,
pId: 2,
name: "LeagueUpdate",
title: "Update League",
link: "/leagues/update",
order: 2,
},
{
Id: 7,
pId: 2,
name: "LeagueAdd",
title: "Add League",
link: "/leagues/add",
order: 1,
},
{
Id: 8,
pId: 3,
name: "PlayerAdd",
title: "Add Player",
link: "/players",
order: 1,
},
{
Id: 9,
pId: 3,
name: "PlayerUpdate",
title: "Update Player",
link: "/players",
order: 3,
},
{
Id: 10,
pId: 3,
name: "PlayerDelete",
title: "Delete Player",
link: "/players",
order: 2,
},
{
Id: 11,
pId: 1,
name: "ClubAdd",
title: "Add Club",
link: "/clubs/add",
order: 3,
},
{
Id: 12,
pId: 1,
name: "ClubUpdate",
title: "Update Club",
link: "/clubs/update",
order: 1,
},
{
Id: 13,
pId: 1,
name: "ClubDelete",
title: "Delete Club",
link: "/clubs/delete",
order: 2,
},
];
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程网。