前言
最近接收到一个打造知识图库的需求,类似于实现一个思维导图,具体的功能是:
- 展示一个树状的思维导图,节点的样式和内容是自定义的;
- 可以实现增加和删除节点;
- 像chatgpt一样动态的生成节点;
- 在节点中进行操作(勾选);
自己实现的话太过复杂,在网上搜索找到了simple-mind-map这个库。
simple-mind-map 是一个轻量级、可定制、无依赖的思维导图 JavaScript 库;基本的使用可以查看文档wanglin2.github.io/mind-map-do...
记录下来是因为踩了很多坑以及具体实现的过程中有许多困难的点,希望可以帮助大家避开。
难点
难点一:自定义节点
要实现每一层节点样式不同,有的是图片加文字,有的是添加背景等,在原节点的基础上设置样式是行不通的,这时候就要用到isUseCustomNodeContent和customCreateNodeContent。 isUseCustomNodeContent
: 是否使用自定义节点内容,如果设置为 true
,则需要使用 customCreateNodeContent
选项创建节点内容。官方提供了在vue和react中使用的示例,地址:wanglin2.github.io/mind-map-do...
其次,连接的线条的样式和颜色可以通过设置自定义主题来设置:github.com/wanglin2/mi...
难点二:包含输入框的节点无法输入
需要注意的是:在使用了自定义节点并设置节点包含输入框时,输入框无法输入!
官方给出了提示,但是我在第一次看的时候漏掉了这句话,导致浪费了好多时间😑 具体的写法是:
ini
<Input autoFocus onMouseDown={(e) => { e.stopPropagation(); }} />
难点三:像AI一样动态生成节点
官方给出了使用updateData的方法wanglin2.github.io/mind-map-do...
但是具体的业务是我已经拿到了这棵树的所有数据,只需要一个一个展示出来,怎么样把手中的树一层一层的给到mindMap实例呢,这里提供一种方法:
ini
function* simulateGenerateNodes(fullData) {
/**
* 生成器函数,用于模拟逐层或逐个节点生成思维导图的数据。
*
* @param {object} fullData - 完整的思维导图数据,包含根节点和所有子节点。
* fullData 的数据结构应符合 simple-mind-map 的数据格式要求,
* 即包含 data 和 children 属性,其中 data 属性建议包含 uid 和 parentUid。
* @yields {object} - 每次调用 next() 方法时,会返回当前的思维导图数据状态。
* 初始状态只包含根节点,后续每次迭代会逐步添加节点,直到所有节点都被添加。
*/
// 初始化根节点,只包含根节点的 data 数据,children 为空数组
const root = {
data: { ...fullData.data },
children: [],
};
// 缓存当前层级的节点,初始值为根节点
let currentLevelNodes = [root];
// 缓存下一层级的数据,初始值为根节点的 children 数据
let nextLevelData = fullData.children;
// 循环直到 nextLevelData 为空或长度为 0
while (nextLevelData && nextLevelData.length > 0) {
// 缓存下一层级的节点
const nextLevelNodes = [];
// 遍历当前层级的所有节点
for (let i = 0; i < currentLevelNodes.length; i++) {
const currentNode = currentLevelNodes[i];
// 根据 parentUid 过滤出当前节点的直接子节点数据
const currentData = nextLevelData.filter(
(item) => item.data.parentUid === currentNode.data.uid,
);
// 遍历当前节点的直接子节点数据
for (let j = 0; j < currentData.length; j++) {
// 创建新的节点对象,只包含 data 数据,children 为空数组
const newNode = {
data: { ...currentData[j].data },
children: [],
};
// 将新节点添加到当前节点的 children 数组中
currentNode.children.push(newNode);
// 将新节点添加到 nextLevelNodes 数组中
nextLevelNodes.push(newNode);
// 注释掉这行代码,因为我们希望每次生成一层之后再 yield,而不是每个节点
// yield JSON.parse(JSON.stringify(root)); // Yield after adding each node
}
}
// 当添加完一层后,yield 当前的树状态
yield JSON.parse(JSON.stringify(root));
// 更新 currentLevelNodes 为下一层级的节点
currentLevelNodes = nextLevelNodes;
// 更新 nextLevelData 为下一层级节点的 children 数据
nextLevelData = nextLevelData.flatMap((node) => node.children || []);
}
}
使用示例
import
const generator = simulateGenerateNodes(fullMindMapData);
const intervalId = setInterval(() => {
const next = generator.next();
if (!next.done) {
setMindMapData(next.value); // 更新思维导图数据
} else {
clearInterval(intervalId);
}
}, 100); // 每 100 毫秒更新一次
基础的使用
1、鼠标悬浮节点,出现删除/新增按钮
在customCreateNodeContent中添加鼠标悬浮事件:
customCreateNodeContent:
let div = document.createElement('div');
// 添加鼠标悬浮事件监听
div.addEventListener('mouseenter', function (e) {
console.log(div.getBoundingClientRect());
const { top, left, width, height } = div.getBoundingClientRect();
addIcon.style.top = `${top + height / 2 - 10}px`;
addIcon.style.left = `${left + width - 4}px`;
addIcon.style.display = 'block';
addIcon.addEventListener('click', function (e) {
setCreateNodeParent(node);
setIsModalOpen(true);
});
});
addIcon.addEventListener('mouseenter', function (e) {
addIcon.style.display = 'block';
});
addIcon.addEventListener('mouseleave', function (e) {
addIcon.style.display = 'none';
});
// 添加鼠标离开事件监听
div.addEventListener('mouseleave', function () {
addIcon.style.display = 'none';
});
})
div.className = 'node_1';
div.style.cssText = `xxx`;
div.innerHTML = `
<div style='display:flex;flex-direction: column;align-items: center;font-size: 22rem'>
<img src=${sysImg[count]} style='width: 120rem;height: 100rem;'></img>
<div style=';border-radius: 4px;
background: #0BE59D;color:#19232D;padding:4rem 18rem;font-size:16rem;margin-top:-20rem
'>${node.nodeData.data.text}</div>
</div>
`;
return div;
}
鼠标悬浮的过程中滚动鼠标会出现增加/删除按钮移位的情况,
可以这样解决:
ini
mindMap.on('mousewheel', () => {
addIcon.style.display = 'none';
});
2、删除节点
const
mindMap.execCommand('REMOVE_NODE', node);
3、新增节点
php
// 在根节点下插入一个子节点
mindMap.execCommand('INSERT_CHILD_NODE', {
data: {
text: '新的子节点',
uid: 'xxx', // uid如果在数据中不设置,simple-mind-map会自动生成,为了后续操作更方便,最好自己设置
},
});
// 在指定节点下插入一个子节点
const parentNode = mindMap.renderer.findNodeByUid('指定节点的uid');
mindMap.execCommand('INSERT_CHILD_NODE', true, parentNode, {
data: {
text: '新的子节点',
uid: 'xxx',
parentUid: parentNode.data.uid
},
});
4、查找节点
ini
const node = mindMap.renderer.findNodeByUid('节点的uid'); // 通过 uid 查找
const node = mindMap.renderer.findNodeByData('text', '节点文本'); // 通过 data 属性查找
5、修改节点
ini
const node = mindMap.renderer.findNodeByUid('要修改的节点的uid');
mindMap.execCommand('UPDATE_NODE_TEXT', node, '新的节点文本');
总结:
simple-mind-map
是一个功能强大且易于使用的思维导图库。它提供了丰富的配置选项、API 方法和事件,可以满足各种场景下的需求。通过自定义主题和节点内容,可以创建出高度定制化的思维导图。特别是其自定义节点内容和动态生成节点的功能,使得可以构建出更加复杂和交互性更强的思维导图应用。