前言
提示:
一般侧边菜单数据都是静态、少量数据。如何实现动态可配置?
可以通过接口返回的树形结构数据动态更新菜单条目
1、先上效果图
- 树形结构数据(模拟数据,实际开发中通过后台接口获取即可)
[
{
"nodeid": "777718a5-18a8-4955-928a-64b683513590",
"parentid": "",
"nodename": "目录1",
"children": [
{
"nodeid": "b278006e-cb28-4459-8f1a-e42202a96ce8",
"parentid": "777718a5-18a8-4955-928a-64b683513590",
"nodename": "目录1-1"
}
]
},
{
"nodeid": "c47d3f14-9b78-46fc-ad76-52e5d4df2ef7",
"parentid": "",
"nodename": "目录2",
"children": [
{
"nodeid": "7c913e5d-0ec0-4152-b183-b001d6d92ed8",
"parentid": "c47d3f14-9b78-46fc-ad76-52e5d4df2ef7",
"nodename": "目录2-1"
},
{
"nodeid": "f1844722-ecaa-4e10-86f1-b64e3352d5ea",
"parentid": "c47d3f14-9b78-46fc-ad76-52e5d4df2ef7",
"nodename": "目录2-2"
},
{
"nodeid": "ffff9811-ac84-41d9-b4d6-bcb98bf0196f",
"parentid": "c47d3f14-9b78-46fc-ad76-52e5d4df2ef7",
"nodename": "目录2-3"
}
]
},
{
"nodeid": "f59286d3-1fdc-4b7d-8fa3-c5df26e86e32",
"parentid": "",
"nodename": "目录3",
"children": [
{
"nodeid": "127eb83b-2c58-4313-96ee-aa35c01b07e6",
"parentid": "f59286d3-1fdc-4b7d-8fa3-c5df26e86e32",
"nodename": "目录3-1",
"children": [
{
"nodeid": "4749d51d-db82-4302-8f99-3c51bd1bd093",
"parentid": "127eb83b-2c58-4313-96ee-aa35c01b07e6",
"nodename": "目录3-1-1"
},
{
"nodeid": "f73a4b82-cc2a-4b5e-8015-c28478718fca",
"parentid": "127eb83b-2c58-4313-96ee-aa35c01b07e6",
"nodename": "目录3-1-2"
},
{
"nodeid": "9d38b195-e5d8-43c5-8c75-71ef52a1e1ec",
"parentid": "127eb83b-2c58-4313-96ee-aa35c01b07e6",
"nodename": "目录3-1-3"
}
]
}
]
},
{
"nodeid": "f4872c95-9e15-4d9a-81f8-4c04c7b275a8",
"parentid": "",
"nodename": "目录4",
"children": [
{
"nodeid": "4bbbb72c-848a-4046-97fe-40939a335299",
"parentid": "f4872c95-9e15-4d9a-81f8-4c04c7b275a8",
"nodename": "目录4-1"
},
{
"nodeid": "ccb75eba-1355-457a-8c67-a53af258e024",
"parentid": "f4872c95-9e15-4d9a-81f8-4c04c7b275a8",
"nodename": "目录4-2"
},
{
"nodeid": "bec8cc74-84e7-4909-8cdc-9650aabf75ad",
"parentid": "f4872c95-9e15-4d9a-81f8-4c04c7b275a8",
"nodename": "目录4-3"
},
{
"nodeid": "b7c6e31b-ce62-40a7-83d0-0363d48ad0a5",
"parentid": "f4872c95-9e15-4d9a-81f8-4c04c7b275a8",
"nodename": "目录4-4"
}
]
},
{
"nodeid": "5e54c8f3-502a-4bed-8542-1e7208d18dad",
"parentid": "",
"nodename": "目录5",
"children": [
{
"nodeid": "4264fae0-e7e9-4d85-826e-f195e178704a",
"parentid": "5e54c8f3-502a-4bed-8542-1e7208d18dad",
"nodename": "目录5-1"
},
{
"nodeid": "01d8e744-8b29-42e1-8f59-829dcf773644",
"parentid": "5e54c8f3-502a-4bed-8542-1e7208d18dad",
"nodename": "目录5-2"
}
]
}
]
- 效果
2、具体实现
- 使用element-ui 导航菜单组件NavMenu ,递归实现。
2.1 定义Menu组件
- 思路 :
1、递归,如果数据元素存在子树(children),调用Menu组件自身,传递子树数据(item.children)。
2、 有子树用el-submenu,反之用 el-menu-item 。
<template>
<div>
<div v-for="(item, index) in menuData" :key="index">
<!-- 思路: 有子元素使用el-submenu 没有子元素使用el-menu-item -->
<el-submenu :index="item.nodeid" v-if="item.children && item.children.length > 0">
<template slot="title">
{{ item.nodename }}
</template>
//递归调用
<Menu :menuData="item.children"></Menu>
</el-submenu>
<el-menu-item :index="item.nodeid" v-else>
{{ item.nodename }}
</el-menu-item>
</div>
</div>
</template>
<script>
export default {
//递归 这个名称要保持驼峰转短横线链接规则
name: "Menu",
props: {
menuData: {
type: Array,
default: () => {
return [];
}
}
},
components: {},
data() {
return {};
},
created() {},
mounted() {},
methods: {}
};
</script>
<style lang='less' scoped>
</style>
2.2 定义Slider组件,及树结构数据处理
<template>
<div style="width:200px;height:100%">
<el-menu
:default-active="defaultActive"
:default-openeds="openeds"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
>
<Menu :menuData="menuData"></Menu>
</el-menu>
</div>
</template>
<script>
import Menu from "@/views/elementUi/pages/Menu";
import { uuid } from "@/common/utils.js";
export default {
name: "elSlider",
components: { Menu },
data() {
return {
menuData: [],
nodePathData: [] //节点路径数据
};
},
created() {},
computed: {
//默认展开的子菜单
openeds() {
let openedList = [];
let resList = this.nodePathData.filter(el => {
return el.some(item => {
return item.nodename === "目录3-1-2";
});
});
resList = resList[0];
if (resList) {
resList.forEach(item => {
openedList.unshift(item.nodeid);
});
}
console.log(JSON.stringify(openedList));
return openedList;
},
//默认激活的子菜单
defaultActive() {
return this.openeds[this.openeds.length - 1];
}
},
mounted() {
this.$axios.get("JsonFile/opitions.json").then(res => {
this.menuData = res.data;
console.time("x");
let nodeData = this.getNodeMsg(this.menuData, "目录3-1-2", "", []);
console.log('节点数据:',JSON.stringify(nodeData));
this.nodePathData = this.getAllNodePath(this.menuData, nodeData);
console.log(JSON.stringify(this.nodePathData));
console.timeEnd("x");
});
},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
},
/**
* @author: DuHui
* @description:通过节点名称 递归获取节点信息
* @param {array} data 树结构数据
* @param {string} nodename 结点名称
* @param {string} nodeid 节点id
* @param {array} result 查询结果,可能会查到多个节点 格式见return
* @returns {array}
* [
* {
* nodename, //节点名称 通过名称查找结果不唯一
* nodeid, //节点id 通过id查找结果唯一
* parentId, //父节点id
* }
* ...
* ]
*/
getNodeMsg(data, nodename, nodeid, result) {
data.forEach(item => {
if (item.nodename === nodename || item.nodeid === nodeid) {
let { nodename, nodeid, parentid } = item;
result.push({ nodename, nodeid, parentid });
}
if (item.children && item.children.length > 0) {
this.getNodeMsg(item.children, nodename, nodeid, result);
}
});
return result;
},
/**
* @author: DuHui
* @description: 获取节点路径:通过节点parentid找父节点,再通过父节点找祖父节点,以此类推
* @param {array} data 树形结构数据
* @param {object} node 当前节点对象
* @param {array} result 含有当前节点的数组
* @returns {array} 当前节点的全路径节点数组
*/
getNodeFullPath(data, node, result) {
//查找当前节点的父节点,
let parent = this.getNodeMsg(data, "", node.parentid, []);
result.push(parent[0]);
if (parent[0] && parent[0].parentid) {
this.getNodeFullPath(data, parent[0], result);
}
return result;
},
/**
* @author: DuHui
* @description: 一个或多个节点得全路径
* @param {array} data 原始树形结构数据
* @param {array} nodeData 节点数据集含parentid
* @returns {array}
*/
getAllNodePath(data, nodeData) {
let result = [];
nodeData.forEach(item => {
//查找当前节点的全路径
let path = this.getNodeFullPath(data, item, [item]);
result.push(path);
});
return result;
}
}
};
</script>
<style lang='less' scoped>
</style>
- 详解:
1、定义Slider组件,引入上面定义Menu组件,并通过 props 传递树结构数据。
2、getNodeMsg(data, nodename, nodeid, result):通过nodename或nodeid,递归获取当前结点数据。如获取"目录3-1-2",结果:
[{“nodename”:“目录3-1-2”,“nodeid”:“f73a4b82-cc2a-4b5e-8015-c28478718fca”,“parentid”:“127eb83b-2c58-4313-96ee-aa35c01b07e6”}]
3、getNodeFullPath(data, node, result):获取当前节点到根节点的路径。含有每个节点基本数据。如下:
[[{“nodename”:“目录3-1-2”,“nodeid”:“f73a4b82-cc2a-4b5e-8015-c28478718fca”,“parentid”:“127eb83b-2c58-4313-96ee-aa35c01b07e6”},{“nodename”:“目录3-1”,“nodeid”:“127eb83b-2c58-4313-96ee-aa35c01b07e6”,“parentid”:“f59286d3-1fdc-4b7d-8fa3-c5df26e86e32”},{“nodename”:“目录3”,“nodeid”:“f59286d3-1fdc-4b7d-8fa3-c5df26e86e32”,“parentid”:""}]]
4、getAllNodePath(data, nodeData) 通过nodename查到节点数据可能存在重复情况,通过循环获取所有满足节点路径即可。
3、激活菜单和展开菜单
3.1 激活菜单
- default-acivite: 激活当前菜单。设置后高亮并展开当前节点
- 效果
3.2 展开菜单
- default-openeds :设置默认展开菜单。这里default-openeds
是一个key数组,就是在组件中设置的Index值(我们index绑定值为nodeid)。
没有顺序要求,但是节点不能中断,含有其父节点、祖父节点、一直到根节点
怎么理解? 比如要你要展开3-1-2,那么也要添加3-1 和 3 这两个节点,不能只设置3-1-2。
如下:
- 效果
3.3 动态实现。
3.1 、3.2 为了了解组件展开机制,使用了静态数据演示,方便理解其组件内部原理。那么如何动态实现?
到这里就很简单了 ,2.2中getNodeFullPath和getAllNodePath 这两个方法我们已经可以获取到节点路径,使用vue计算属性,做简单计算即可。
- 代码实现
computed: {
//默认展开的子菜单
openeds() {
let openedList = [];
let resList = this.nodePathData.filter(el => {
return el.some(item => {
return item.nodename === "目录3-1-2"; //要展开的节点
});
});
resList = resList[0];
if (resList) {
resList.forEach(item => {
openedList.unshift(item.nodeid);
});
}
console.log(JSON.stringify(openedList));
return openedList;
},
//默认激活的子菜单
defaultActive() {
return this.openeds[this.openeds.length - 1];
}
},