Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tree组件增加右键菜单 #5151

Closed
bf185003 opened this issue Mar 3, 2017 · 23 comments
Closed

Tree组件增加右键菜单 #5151

bf185003 opened this issue Mar 3, 2017 · 23 comments

Comments

@bf185003
Copy link

bf185003 commented Mar 3, 2017

tree组件需要有右键上下文菜单,经常需要在某个节点上进行操作。
现在tree没有这个属性

@yesmeck
Copy link
Member

yesmeck commented Mar 3, 2017

@yesmeck yesmeck closed this as completed Mar 3, 2017
@bf185003
Copy link
Author

bf185003 commented Mar 6, 2017

这个例子给我没用,我是用antD的。

@yesmeck
Copy link
Member

yesmeck commented Mar 6, 2017

antd 的组件都是基于 react-component 封装的,Tree 的 API 是一致的,上面的例子可以直接参考。

@jcy2004x
Copy link

jcy2004x commented Mar 31, 2017

我实现了,看下关键的几行代码就明白了

<Tree onRightClick={this.treeNodeonRightClick} >

// 实现这个方法 treeNodeonRightClick

treeNodeonRightClick(e) {
        this.setState({
            rightClickNodeTreeItem: {
                pageX: e.event.pageX,
                pageY: e.event.pageY,
                id: e.node.props['data-key'],
                categoryName: e.node.props['data-title']
            }
        });
    }

// id 和 categoryName 是生成时绑上去的

<TreeNode
                        key={item.id}
                        title={title}
                        data-key={item.id}
                        data-title={item.categoryName}
                    />);

// 最后绑个菜单就可以实现了

getNodeTreeRightClickMenu() {
        const {pageX, pageY} = {...this.state.rightClickNodeTreeItem};
        const tmpStyle = {
            position: 'absolute',
            left: `${pageX - 220}px`,
            top: `${pageY - 70}px`
        };
        const menu = (
            <Menu
                onClick={this.handleMenuClick}
                style={tmpStyle}
                className={style.categs_tree_rightmenu}
            >
                <Menu.Item key='1'><Icon type='plus-circle'/>{'加同级'}</Menu.Item>
                <Menu.Item key='2'><Icon type='plus-circle-o'/>{'加下级'}</Menu.Item>
                <Menu.Item key='4'><Icon type='edit'/>{'修改'}</Menu.Item>
                <Menu.Item key='3'><Icon type='minus-circle-o'/>{'删除目录'}</Menu.Item>
            </Menu>
        );
        return (this.state.rightClickNodeTreeItem == null) ? '' : menu;
    }

@chj-damon
Copy link
Contributor

@jcy2004x 请问您定义的这个getNodeTreeRightClickMenu方法是在哪里调用的?

@jcy2004x
Copy link

jcy2004x commented Apr 10, 2017

@chj-damon
getNodeTreeRightClickMenu方法是放在生成主界面的方法里( render ),因为每一次state的变化后,render方法都会执行,所以变一下任意的this.state里面的状态,就会执行render方法 ,这样getNodeTreeRightClickMenu方法放在render方法里来生成界面的一部分。就可以了

@TengFeiHao
Copy link

可以写明白点吗?调用getNodeTreeRightClickMenu函数 后 那个 menu 怎么显示到页面上?

@lemonnoodle
Copy link

@TengFeiHao 根据@jcy2004x 的方法弄的,可以参考下https://github.com/lemonnoodle/react-mobx-dashboard/blob/master/src/components/ios/Create.jsx

@itskaiway
Copy link

itskaiway commented Sep 10, 2018

上面的方法可行,不过里面有个获取位置的方法建议用e.event.currentTarget.getBoundingClientRect()方法,目前直接用e.event.pageX以及e.event.pageY感觉有偏移。

个人写法

onRightClick = (e) => {
    const positionInfo = e.event.currentTarget.getBoundingClientRect();
    const x = positionInfo.right;
    const y = positionInfo.bottom;
    this.setState({
      NodeTreeItem: {
        pageX: x,
        pageY: y,
        parentKey: e.node.props.dataRef.parentKey,
        id: e.node.props.eventKey,
      },
    });
  };

@dargonpaul
Copy link

dargonpaul commented Sep 21, 2018

可以不用自己写,也不用antd的tree的右键事件属性,antd官方推荐了两个优秀的右键菜单组件,我只用了其中一个https://github.com/fkhadra/react-contexify
tree的子节点组件是TreeNode , 它有一个title属性是用来传递每个节点显示的给用户看到内容的,可以在这里,把右键菜单的组件ContextMenuProvider包进去,关键代码如下:

import { ContextMenu, Item, Separator, Submenu, ContextMenuProvider } from 'react-contexify';
import 'react-contexify/dist/ReactContexify.min.css';
const onContextMenuClick = ({ event, ref, data, dataFromProvider }) => console.log(ref.dataset.key);
        // create your menu first
        const MyAwesomeMenu = () => (
            <ContextMenu id='menu_id'>
              <Item onClick={onContextMenuClick}>Lorem</Item>
              <Item onClick={onContextMenuClick}>Ipsum</Item>
              <Separator />
              <Item disabled>Dolor</Item>
              <Separator />
              <Submenu label="Foobar">
                <Item onClick={onContextMenuClick}>Foo</Item>
                <Item onClick={onContextMenuClick}>Bar</Item>
              </Submenu>
            </ContextMenu>
        );
const title = (<ContextMenuProvider id='menu_id'  data-key={item.key}><span>{item.title}</span></ContextMenuProvider>);

<Tree>
  <TreeNode key={item.key} title={title}></TreeNode>
</Tree>

@guirenpei
Copy link

guirenpei commented Jan 5, 2019

@dargonpaul
只要一用ContextMenu就报错,有什么好的解决办法吗

Element type is invalid: expected a string (for built-in components) or
a class/function (for composite components) but got: undefined.
You likely forgot to export your component from the file it's defined in,
or you might have mixed up default and named imports.

Check the render method of `CContextMenu`.

@itsXX
Copy link

itsXX commented Mar 18, 2019

@guirenpei 用Menu替换ContextMenu就可以

@guirenpei
Copy link

@guirenpei 用Menu替换ContextMenu就可以

ok, thx

@ChinaShrimp
Copy link

import React from 'react';

import { Tree } from 'antd';
import { Menu, Item, MenuProvider } from 'react-contexify';
import 'react-contexify/dist/ReactContexify.min.css';

// 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包裹上下文菜单
  const renderEntityTreeNodes = data => {
    return data.map(item => {
      const title = (
        <MenuProvider id={contextMenu.menuId} data-key={item.key}>
          <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 defaultExpandedKeys={['0-0-0']}>{renderEntityTreeNodes(dataSource)}</Tree>
      <ContextMenu />
    </div>
  );
};

export default TreeWithContextMenu;

In case有人需要,贴个完整版的组件。

@truewq
Copy link

truewq commented May 10, 2020

我实现了,看下关键的几行代码就明白了

<Tree onRightClick={this.treeNodeonRightClick} >

// 实现这个方法 treeNodeonRightClick

treeNodeonRightClick(e) {
        this.setState({
            rightClickNodeTreeItem: {
                pageX: e.event.pageX,
                pageY: e.event.pageY,
                id: e.node.props['data-key'],
                categoryName: e.node.props['data-title']
            }
        });
    }

// id 和 categoryName 是生成时绑上去的

<TreeNode
                        key={item.id}
                        title={title}
                        data-key={item.id}
                        data-title={item.categoryName}
                    />);

// 最后绑个菜单就可以实现了

getNodeTreeRightClickMenu() {
        const {pageX, pageY} = {...this.state.rightClickNodeTreeItem};
        const tmpStyle = {
            position: 'absolute',
            left: `${pageX - 220}px`,
            top: `${pageY - 70}px`
        };
        const menu = (
            <Menu
                onClick={this.handleMenuClick}
                style={tmpStyle}
                className={style.categs_tree_rightmenu}
            >
                <Menu.Item key='1'><Icon type='plus-circle'/>{'加同级'}</Menu.Item>
                <Menu.Item key='2'><Icon type='plus-circle-o'/>{'加下级'}</Menu.Item>
                <Menu.Item key='4'><Icon type='edit'/>{'修改'}</Menu.Item>
                <Menu.Item key='3'><Icon type='minus-circle-o'/>{'删除目录'}</Menu.Item>
            </Menu>
        );
        return (this.state.rightClickNodeTreeItem == null) ? '' : menu;
    }

菜单是弹出来了,但是位置始终不对。

@truewq
Copy link

truewq commented May 11, 2020

import React from 'react';

import { Tree } from 'antd';
import { Menu, Item, MenuProvider } from 'react-contexify';
import 'react-contexify/dist/ReactContexify.min.css';

// 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包裹上下文菜单
  const renderEntityTreeNodes = data => {
    return data.map(item => {
      const title = (
        <MenuProvider id={contextMenu.menuId} data-key={item.key}>
          <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 defaultExpandedKeys={['0-0-0']}>{renderEntityTreeNodes(dataSource)}</Tree>
      <ContextMenu />
    </div>
  );
};

export default TreeWithContextMenu;

In case有人需要,贴个完整版的组件。

还是你这个方案好,上面直接用onRightClick鼠标位置都不正确。不过数据结构应该是个数组吧
// contextMenu: { menuId, items: [{ name, handler }]} 上下文菜单
感谢感谢

@daixianceng
Copy link

    <a-dropdown :trigger="['contextmenu']">
      <a-tree>
      </a-tree>
      <a-menu slot="overlay">
        <a-menu-item key="1">1st menu item</a-menu-item>
        <a-menu-item key="2">2nd menu item</a-menu-item>
        <a-menu-item key="3">3rd menu item</a-menu-item>
      </a-menu>
    </a-dropdown>

@ShawLyon
Copy link

ShawLyon commented Aug 6, 2020

import React from 'react';

import { Tree } from 'antd';
import { Menu, Item, MenuProvider } from 'react-contexify';
import 'react-contexify/dist/ReactContexify.min.css';

// 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包裹上下文菜单
  const renderEntityTreeNodes = data => {
    return data.map(item => {
      const title = (
        <MenuProvider id={contextMenu.menuId} data-key={item.key}>
          <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 defaultExpandedKeys={['0-0-0']}>{renderEntityTreeNodes(dataSource)}</Tree>
      <ContextMenu />
    </div>
  );
};

export default TreeWithContextMenu;

In case有人需要,贴个完整版的组件。

这行改成 <MenuProvider id={contextMenu.menuId} data={item}>, 不然有坑

@PeiHaigang
Copy link

Now you don't need react-contexify any more. Use antd Dropdown component is better, you still need to custom the title of the tree node, then use Dropdown to wrapper it, like below:
<Dropdown overlay={treeMenu} trigger={['contextMenu']}> <span>{${item.title}}</span> </Dropdown>
treeMenu like this:
const treeMenu = ( <Menu> <Menu.Item key="1">1st menu item</Menu.Item> <Menu.Item key="2">2nd menu item</Menu.Item> <Menu.Item key="3">3rd menu item</Menu.Item> </Menu> );

@nanslee
Copy link

nanslee commented May 26, 2021

上面的方法可行,不过里面有个获取位置的方法建议用e.event.currentTarget.getBoundingClientRect()方法,目前直接用e.event.pageX以及e.event.pageY感觉有偏移。

个人写法

onRightClick = (e) => {
    const positionInfo = e.event.currentTarget.getBoundingClientRect();
    const x = positionInfo.right;
    const y = positionInfo.bottom;
    this.setState({
      NodeTreeItem: {
        pageX: x,
        pageY: y,
        parentKey: e.node.props.dataRef.parentKey,
        id: e.node.props.eventKey,
      },
    });
  };

如果这个菜单在一个滚动容器内,点击坐标怎么获取呢

@nickrogit
Copy link

ContextMenuProvi

MenuProvider

react-contexify哪个版本?,react-contexify 5.0.0没有这个MenuProvider呀。

@nickrogit
Copy link

MenuProvider

什么版本?没有MenuProvider

@ityoung
Copy link

ityoung commented Jan 20, 2022

Now you don't need react-contexify any more. Use antd Dropdown component is better, you still need to custom the title of the tree node, then use Dropdown to wrapper it, like below: <Dropdown overlay={treeMenu} trigger={['contextMenu']}> <span>{${item.title}}</span> </Dropdown> treeMenu like this: const treeMenu = ( <Menu> <Menu.Item key="1">1st menu item</Menu.Item> <Menu.Item key="2">2nd menu item</Menu.Item> <Menu.Item key="3">3rd menu item</Menu.Item> </Menu> );

4.5以上版本基于 @PeiHaigang 的补充:

使用 titleRender + treeData 不需要再遍历树结构、生成TreeNode

<Tree 
  ...
  titleRender={(item) => (<Dropdown><span>{item.title}</span></Dropdown>)}
  treeData={treeData}
/>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests