一、自定义节点
使用ant design中的Tree组件,基础树形组件只需要将treeData属性绑定一个树形结构的值(treeData={treeData})即可:
但是由于单纯的展示名称已经不能满足这里的需求,使用将treeData处理后的TreeNode加到Tree的内部,代码如下,其中onSelect在点击树节点时触发:
// 点击节点,第一次点击节点是选中,第二次点击同一个节点是取消选中,用keys来判断是否有选中
const onSelect = (keys, info) => {
if (keys.length > 0) {
setSelectNode(info.node);
} else {
setSelectNode({});
}
};
//...
<Tree
style={{ marginTop: "20px" }}
showLine={false}
showIcon={true}
onSelect={onSelect}
>
{handleTreeData(treeData)}
</Tree>
获取treeData,笔者这里treeData的格式为:
[{
id:'1',
name:'所有',
count:'21',
suffix:'江苏',
childNodes:[
{
id:'1-1',
name:'南京',
count:'21',
suffix:'',
childNodes:[]
}
]
}]
从接口获取:
const _getTreeData= async () => {
setTreeData([]);
try {
let result = await getTreeData();
setTreeData(result);
} catch (error) {
}
};
下面笔者需要处理数据,由上面的代码可知,通过handleTreeData函数处理数据treeData。分析一下基础的树形结构,Tree的节点其实是TreeNode,对于每一节点treeNode,根据官网的介绍,只需要设置其title和key属性,一般key属性即为树形数据的节点id,由此递归出所有的TreeNode:
// 重写树
const handleTreeData = (treeData) => {
return treeData?.map((treeNode) => handleNodeData(treeNode));
};
const handleNodeData = (treeNode) => {
if (treeNode.toString() === "[object Object]") {
treeNode.title = (
<div>
<span className="text-overflow" title={treeNode.name}>{treeNode.name}</span>
<span>({treeNode.other.count}) _{treeNode.suffix}</span>
</div>
);
return (
<TreeNode title={treeNode.title} key={treeNode.id}>
{treeNode?.childNodes?.map((n) => handleNodeData(n))}
</TreeNode>
);
}
return <TreeNode {...treeNode} />;
};
至此,该树已经按照笔者的需求展示:
二、重命名节点
给每个节点后面添加一个按钮,点击按钮将节点切换为编辑状态,默认是原节点名称,根据上文,很容易想到在handleNodeData()中在treeNode.title中添加编辑按钮,并绑定rename():
<Button onClick={() => rename()} size={"small"}>重命名</Button>
此外,需要给每个节点增加isEdit和defaultValue(用于取消重命名后使用原来的节点名称)的属性,isEdit为true表示编辑态,否则正常展示节点。初始化数据,将所有节点的isEdit全部置为false。defaultValue值为name的值。
// 设置不可编辑
const setAllNotEdit = (arr) => {
let data = [].concat(arr);
data.forEach((val) => {
val.isEdit = false;
if (val.childNodes && val.childNodes.length) {
setAllNotEdit(val.childNodes);
}
});
return data;
};
// 查询组织树
const _getTreeData= async () => {
//...
let data = setAllNotEdit(result);
setTreeData(data);
//...
};
点击重命名,触发rename():在树形数据中找到该节点数据(定义deepTree函数,用于找到目标节点数据,并将名为第三个入参的键的值修改为第四个入参),将isEdit改为true。在handleNodeData中针对编辑态和常规态作出单独的节点定义:
const deepTree = (arr, key, keyName, value, otherValue) => {
let data = [].concat(arr);
for (let i = 0; i < data.length; i++) {
if (data[i].id === key) {
data[i][keyName] = value;
} else if (typeof otherValue === "boolean") {
data[i][keyName] = otherValue;
}
if (data[i].childNodes && data[i].childNodes.length) {
deepTree(data[i].childNodes, key, keyName, value, otherValue);
}
}
return data;
};
// 重命名
const rename = () => {
if (selectNode && selectNode.key) {
let data = deepTree(treeData, selectNode.key, "isEdit", true, false);
setTreeData(data);
} else {
message.warning("请选择节点");
}
};
//...
const handleNodeData = (treeNode) => {
if (treeNode.toString() === "[object Object]") {
if (treeNode.isEdit) {
treeNode.title = (
<div>
<input value={treeNode.name} onChange={(e) => { changeNodeName(e,treeNode.id); }}/>
({treeNode.count})
_{treeNode.suffix}
<Button onClick={() => { saveTreeNode(treeNode); }} size={"small"} type="link" >确定</Button>
<Button onClick={() => { cancelRename(treeNode); }} size={"small"} type="link" >取消</Button>
</div>
);
} else {
//...
}
//...
}
return <TreeNode {...treeNode} />;
};
到这里,当点击重命名按钮时,节点已经变为编辑态了,input后有确定和取消两个按钮。当在input中输入新名称,触发changeNodeName(),如果没有这一步,input的值将无法修改(因为始终绑定的是节点名称,节点名称没有改变过):
// 修改节点名称
const changeNodeName = (e, key) => {
let data = deepTree(treeData, key, "name", e.target.value);
setTreeData(data);
};
点击取消时,表示取消重命名,使用原来的名称。在节点数据中找到当前节点,将值修改为之前的值(这个值我们已经保存在defaultValue中了):
// 取消修改节点名称
const cancelRename = (treeNode) => {
let dataHasReset = deepTree(
treeData,
treeNode.id,
"name",
treeNode.defaultValue
);
let data = setAllNotEdit(dataHasReset);
setTreeData(data);
};
点击确定时,表示修改节点名。
- 如果此时只需要页面的更新,那么只需要在节点数据中找到该节点,更新defaultValue:
const saveTreeNode = (treeNode) => {
let dataHasChangeDefaultVal = deepTree(
treeData,
treeNode.id,
"defaultValue",
treeNode.name
);
let data = setAllNotEdit(dataHasChangeDefaultVal);
setTreeData(data);
};
- 如果调用接口更新节点,只需要调用接口,接口成功后重新加载树:
// 保存修改的节点名称
const saveTreeNode = async (treeNode) => {
try {
await updateNode({
//...
});
_getTreeData();
} catch (e) {
}
};
在Tree控件中,点击一次节点,表示选中当前节点,再次点击,表示取消选中,但是当切换为编辑态的时候,我们可能多次点击,为了防止数据丢失,修改onSelect如下(其中dataref是TreeNode的props):
// 点击节点
const onSelect = (keys, info) => {
if (keys.length > 0 || info.node?.dataRef?.isEdit) {
setSelectNode(info.node);
} else {
setSelectNode({});
}
};
const formatNodeData = (treeNode) => {
if (treeNode.toString() === "[object Object]") {
//...
return (
<TreeNode title={treeNode.title} key={treeNode.id} dataRef={treeNode}>
{treeNode?.childNodes?.map((d) => formatNodeData(d))}
</TreeNode>
);
}
return <TreeNode {...treeData} />;
})
在对选中的节点进行重命名之后,虽然树是新的树了,但是保存了节点选中的状态,也保存了被选的节点数据,为了防止数据不同步造成的误会,这里笔者每次得到新的树时,就会把选中的状态去掉,选中的数据也置空。选中的数据置空只需要setSelectNode({})即可。但是去掉选中的状态就要求使用Tree控件的另一个属性:selectedKeys,表示选中的节点,当加上这个属性,当点击节点后,需要将绑定的值也更新:
// 点击节点
const onSelect = (keys, info) => {
setSelectedKeys(keys);
//...
};
//...
<Tree
showLine={false}
showIcon={true}
defaultExpandAll={true}
onSelect={onSelect}
selectedKeys={selectedKeys}
>
{formatTreeData(treeData)}
</Tree>
至此,重命名节点名称已经实现。
三、新增节点
笔者这里添加的是子节点,兄弟节点也类似,不再赘述。
所谓新增节点,其实就是处理树形结构的数据。
- 如果此时只需要页面的更新,在当前选中节点的childNodes中增加一个对象,所以,递归找到选中的节点,push一个新节点即可:
const onAdd = (arr) => {
let data = [].concat(arr);
data.forEach((item) => {
if (item.id === selectNode.key) {
if (!item.childNodes) {
item.childNodes = [];
}
item.childNodes.push({
name: "新节点",
defaultValue: "新节点",
id: selectNode.key + Math.random(100),
suffix:'',
count:'',
isEditable: false,
childNodes: [],
});
return;
}
if (item.childNodes) {
onAdd(item.childNodes);
}
});
return data;
};
const addNode = () => {
if (selectNode && selectNode.key) {
let data = onAdd(treeData);
setTreeData(data);
} else {
message.warning("请选择节点");
}
};
- 如果调用接口更新节点,只需要调用接口,接口成功后重新加载树:
// 添加下级
const addNode = async () => {
if (selectNode && selectNode.key) {
try {
let result = await addNode({
//...
});
_getTreeData();
} catch (e) {
}
}
};
至此,新增节点已经实现。
四、删除节点
与新增节点相对,删除节点是在数据中找到选中节点,从childNodes中删除元素。同样从两种场景出发:
- 如果此时只需要页面的更新,在当前选中节点的childNodes中删除一个对象,所以,递归找到选中的节点,splice即可:
const onDelete = (arr) => {
arr.forEach((item, index) => {
if (item.id === selectNode.key) {
arr.splice(index, 1);
return;
}
if (item.childNodes) {
onDelete(item.childNodes);
}
});
return arr;
};
const delNode = () => {
if (selectNode && selectNode.key) {
let data = onDelete(treeData);
setTreeData([].concat(data));
setSelectNode({});
} else {
message.warning("请选择节点");
}
};
- 如果调用接口更新节点,只需要调用接口,接口成功后重新加载树:
// 删除节点
const delNode = () => {
if (selectNode && selectNode.key) {
try {
let result = await deleteNode({
//...
});
_getTreeData();
} catch (e) {}
} else {}
};
至此,删除节点已经实现。文章来源:https://www.toymoban.com/news/detail-729356.html
总结
本篇详述了对于react + ant design的树形控件的自定义节点,以及对节点的增删改,如有建议,欢迎指教~文章来源地址https://www.toymoban.com/news/detail-729356.html
到了这里,关于详述react + ant desgin自定义树的节点,对节点进行重命名新增删除等操作的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!