由于业务需要绘制思维导图,我探索了市面上的一些组件,最终选择了OpenTiny
的MindMap
组件。
Demo介绍
官网Demo体验:opentiny.design/tiny-vue/zh…
待讨论的业务相关问题
简单介绍一下我要做的业务:二级节点的数据是可选择的,并且还要给它携带一些自己的属性,然后三级节点根据二级节点的数据,做联动选择和编辑。
二级节点只能选一个国家,并且输入分数;三级节点只能选该国家的城市,并且输入分数;不允许创建四级节点。(这里简化了,实际上的业务比这更复杂)
如果我们直接使用这个组件,显然是不符合业务要求的,因为编辑操作有诸多的限制。如果把一切都交给最后一步保存的时候去校验,用户体验是非常不好的,并且还有中文匹配操作,接口复杂度很高,而且容易出错。
业务的关键点
-
对于编辑操作,不能使用组件自带的双击编辑文本,要用接口获取国家、城市,用
Select
组件去选择城市,用Input
组件去输入分数; -
对于新增操作,需要进行拦截。先判断当前选中的节点是什么类型的,才能决定它的子节点或兄弟节点是什么类型的;
-
对于删除操作,需要有二次确认框,避免误删;
-
每一个操作结束之后,就要保存一下,类似于草稿的作用;
-
不允许复制节点、移动节点。(不允许把钓鱼岛从中国移到日本)
解决方案的讨论
针对上述问题,考虑到组件支持的功能,我做了如下的技术方案设计:
- 不允许拖拽、不用键盘
const options = {
draggable: false,
editable: true,
keypress: false,
};
- 自己监听键盘事件,做特殊的逻辑处理
document.addEventListener('keydown', function (event) {
if (event.key === 'Tab' || event.key === 'Enter') {
addNode();
} else if (event.key === 'Delete') {
deleteNode();
}
});
- 根据当前选中的节点,做新增、编辑、删除操作
const D = ref(null)
const onCreate = (e) => {
D.value = e; // 这个就是思维导图的实例
};
// 获取当前选中的节点
function getSelectedNode() {
if (D && D.value && D.value.currentNode) {
return D.value.currentNode.nodeObj;
}
return null;
}
// 调用 getSelectedNode 获取当前选中的节点
// 新增操作
function addNode() {
let curr = getSelectedNode();
if (curr) {
let nodeType = curr.nodeType;
if (nodeType == 'three') {
// 三级节点不允许创建子节点
createMessage.error('三级科室下不能再添加新记录');
} else {
openModal(true, {
parent: nodeType, // 当前选中的节点数据 其实就是待新建的父节点,根据这个节点的类型,我们就知道新建的节点是国家还是城市
});
}
}
}
// 删除操作
function deleteNode() {
let curr = getSelectedNode();
if (curr) {
let nodeType = curr.nodeType;
if (nodeType == 'root') {
createMessage.error('根节点不允许删除');
} else {
Modal.confirm({
content: '选中的节点和子节点会被删除,确定吗?',
async onOk() {
doDel(curr.id); // 调用删除接口
},
onCancel() {},
});
}
}
}
-
新增、编辑、删除,都是先调用接口,接口调用成功之后,再去更新页面。所以在这个业务里,只用了思维导图组件的展示功能,增删改操作都被拦截了,然后通过接口去改变数据,再去用新数据更新页面。
-
编辑可以写在键盘事件上,也可以用自带的编辑事件。
const onOperation = ({ info, render }) => {
if (info.name == 'beginEdit') {
if (info.obj.nodeType == 'root') {
createMessage.error('根节点不允许删除');
} else {
openModal(true, {
curr: info.obj, // 当前选中的节点数据进行编辑
});
}
}
};
其他优化
绘制完成的思维导图,会有一个默认居中的逻辑,源码在/node_modules/.pnpm/@opentiny+vue-mind-map@3.15.0/node_modules/@opentiny/vue-mind-map/lib/pc.js
中的toCenter
方法里
// 修改前
Xt = function Xt2() {
this.container.scrollTo(1e4 - this.container.offsetWidth / 2, 1e4 - this.container.offsetHeight / 2);
}
// 修改后,向左的偏移量改小一点,避免左侧留太多的空白
Xt = function Xt2() {
this.container.scrollTo(1e4 - this.container.offsetWidth / 5, 1e4 - this.container.offsetHeight / 2);
}
组件优化方向思考
-
是否可以给组件新增 beforeEdit、beforeDelete等方法,这样就能在这个函数里处理逻辑了;
-
节点是否可以支持插槽,这样就可以在节点上放一个选择框和输入框了;
-
是否可以支持右键菜单;
Demo地址:github.com/beat-the-bu…