最近由于产品功能需求,需要对后端树状数据进行增删改操作。前端使用ant design的tree控件。为了在某个树节点上实现增删改操作,决定采用鼠标右键弹出操作菜单的方式。但是tree控件本身并不支持鼠标右键弹出菜单功能需要自己实现。这里把自己遇到的坑以及最终的方案给分享下。
onRightClick坑
tree控件中有个onRightClick属性,可以设置右键点击的事件处理函数。onRightClick参数有两个属性:{event, node}。但是,通过获取event中的pageX、pageY坐标,并用absolute方式弹出菜单,位置总是不对,离真正点击的位置相差很远。暂时没想到好的解决办法,请路过的高手指教。
react-contexify填坑
为了解决onRightClick弹出菜单位置不正确的问题,采用react-contexify右键菜单组件。原理是,将ant design tree控件中,每个treeNode的title设置为ReactNode,也就是替换成react-contexify中的MenuProvider,从而实现右键弹出,而且位置非常准确。
首先,treeData是从服务器异步拉取下来的。在增删改之后会重新拉取treeData
import { Menu, Item, MenuProvider } from 'react-contexify';
import 'react-contexify/dist/ReactContexify.min.css';
const [treeData, setTreeData] = useState([]);
const [loading, setLoading] = useState<boolean>(false);
const fetchTree = async()=>{
try{
const response = await getTreeData({
});
if(response && response.status === 0){
setTreeData(response.tree);
}else{
message.error('拉取树失败');
}
}catch (e) {
message.error('拉取树失败');
}finally {
setLoading(false);
}
};
其次,定义右键菜单
// 我使用的菜单定义数据结构是这样的
// { menuId, items: [{ name, handler }]}
// handler函数
const deleteNode= ({event, props}) =>{
// props是当前点击的树节点,需要在MenuProvider中用属性传进去才能在这里获得
};
const testMenu = {menuId:1,
items:[
{name:'添加子节点', handler:addChildNode},
{name:'添加同级节点', handler:addSiblings},
{name:'修改节点', handler:updateNode},
{name:'删除节点', handler:deleteNode},
]
};
第三,刷新treeData,把title从字符串格式,包装成MenuProvider
// contextMenu: { menuId, items: [{ name, handler }]} 上下文菜单
// dataSource: 树节点
const TreeWithContextMenu = ({ dataSource, contextMenu }) => {
// 上下文菜单
const ContextMenu = () => (
<Menu id={contextMenu.menuId}>
{contextMenu.items.map(item => (
<Item onClick={item.handler}>{item.name}</Item>
))}
</Menu>
);
// 渲染树节点,主要目的是为了通过react-contexify包裹上下文菜单
// 其中 data={item}是把当前数据节点传入MenuProvider会在右键事件回调中使用,方便确定当前点击的数据
const renderEntityTreeNodes = data => {
return data.map(item => {
const title = (
<MenuProvider id={contextMenu.menuId} data={item}>
<span>{`${item.title}`}</span>
</MenuProvider>
);
if (item.children) {
const result = (
<Tree.TreeNode {...item} key={item.key} title={title}>
{renderEntityTreeNodes(item.children)}
</Tree.TreeNode>
);
return result;
}
return <Tree.TreeNode {...item} key={item.key} title={title} />;
});
};
return (
<div>
<Tree autoExpandParent defaultExpandAll defaultExpandParent>{renderEntityTreeNodes(dataSource)}</Tree>
<ContextMenu/>
</div>
);
};
第四,在render中使用TreeWithContextMenu方法
return(<div>
TreeWithContextMenu({dataSource:treeData, contextMenu:testMenu})
</div>)