react 可拖拽有功能列表树组件

antd 有一个可拖拽的树组件,很好用,但是展开只能点击前面的小图标,而且样式固定。
为了满足需求,拓展了一个点击列表展开收缩树,可拖拽,有功能列表的树组件。
在这里插入图片描述

const { Tree, Icon } = antd;

const { TreeNode } = Tree;

const x = 3;
const y = 2;
const z = 1;
const gData = [];

const generateData = (_level, _preKey, _tns) => {
  const preKey = _preKey || '0';
  const tns = _tns || gData;

  const children = [];
  for (let i = 0; i < x; i++) {
    const key = `${preKey}-${i}`;
    tns.push({ title: key, key });
    if (i < y) {
      children.push(key);
    }
  }
  if (_level < 0) {
    return tns;
  }
  const level = _level - 1;
  children.forEach((key, index) => {
    tns[index].children = [];
    return generateData(level, key, tns[index].children);
  });
};
generateData(z);
/*
  *@sourceTree Array<{title, key, children}> 数据源
  *@setList Array<{title, handleFunc}>
*/
class Demo extends React.Component {
  state = {
    gData: [],
	  expandedKeys: [],
	  autoExpandParent: true,
    selectedKeys: [],
    activeFocusKeys: '', //鼠标悬浮展示图标
    activeListFocusKeys: '', //鼠标悬浮展示列表
    treeDialog: []
  };
  
  componentDidMount() {
    const { sourceTree, expandedKeys } = this.props;
    this.setState({
      gData: sourceTree || gData,
      expandedKeys: [] || expandedKeys,
      treeDialog: [{title: '增加子货架', handleFunc: ()=>{}}, {title: '增加', handleFunc: ()=>{}}, {title: '删除', handleFunc: ()=>{}}, {title: '修改', handleFunc: ()=>{}}]
    })
  }

  onDragEnter = info => {
    console.log(info);
    // expandedKeys 需要受控时设置
    // this.setState({
    //   expandedKeys: info.expandedKeys,
    // });
  };

  onDrop = info => {
    const dropKey = info.node.props.eventKey;
    const dragKey = info.dragNode.props.eventKey;
    const dropPos = info.node.props.pos.split('-');
    const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);

    const loop = (data, key, callback) => {
      data.forEach((item, index, arr) => {
        if (item.key === key) {
          return callback(item, index, arr);
        }
        if (item.children) {
          return loop(item.children, key, callback);
        }
      });
    };
    const data = [...this.state.gData];

    // Find dragObject
    let dragObj;
    loop(data, dragKey, (item, index, arr) => {
      arr.splice(index, 1);
      dragObj = item;
    });

    if (!info.dropToGap) {
      // Drop on the content
      loop(data, dropKey, item => {
        item.children = item.children || [];
        // where to insert 示例添加到尾部,可以是随意位置
        item.children.push(dragObj);
      });
    } else if (
      (info.node.props.children || []).length > 0 && // Has children
      info.node.props.expanded && // Is expanded
      dropPosition === 1 // On the bottom gap
    ) {
      loop(data, dropKey, item => {
        item.children = item.children || [];
        // where to insert 示例添加到头部,可以是随意位置
        item.children.unshift(dragObj);
      });
    } else {
      let ar;
      let i;
      loop(data, dropKey, (item, index, arr) => {
        ar = arr;
        i = index;
      });
      if (dropPosition === -1) {
        ar.splice(i, 0, dragObj);
      } else {
        ar.splice(i + 1, 0, dragObj);
      }
    }
    console.log(data)
    this.setState({
      gData: data,
    });
  };
  //选中的回调
  onSelect = (selectedKeys, obj) => {
	  let {expandedKeys} = this.state;
    let selectedKey = this.state.selectedKeys;
	//选中的状态
    if (obj.selected) {
      // 没有 children 代表当前已没有下一级目录
      if (obj.event && obj.selectedNodes.length === 1 && !obj.selectedNodes[0].props.children) {
        //do something
        return;
      }
      //判断是否已经展开,未展开就添加到 expandedKeys 中
      //已经展开就不用管
      let index = expandedKeys.indexOf(selectedKeys[0])
      if (index === -1) {
        this.setState({
          expandedKeys: [...expandedKeys, selectedKeys[0]],
          selectedKeys: selectedKeys
        })
      } else {
        const expanedeNews = expandedKeys.filter(t=>t!==selectedKeys[0])
        this.setState({
          expandedKeys: expanedeNews,
          selectedKeys: selectedKeys
        })
      }

    } else {
      //selectedKey 是上次选中的元素 在 expandedKeys 肯定是存在的 
      //找到下标后需要过滤掉子类目录 例如上次选中的元素为 /a ,
      //子类 /a/a_1 已经展开就需要从 expandedKeys 中移除这个元素
      let index = expandedKeys.indexOf(selectedKey[0])
      if (index !== -1) {
        //过渡掉子类元素
        expandedKeys = expandedKeys.filter((ele) => {
          return !ele.includes(selectedKey[0])
        })
        this.setState({
          expandedKeys: expandedKeys,
          selectedKeys: selectedKey
        })
      } else {
        this.setState({
          selectedKeys: selectedKey,
          expandedKeys: [...expandedKeys, ...selectedKey]
        })
      }
    }
}
//比较出2个数组中不一样的元素
diffArray = (arr1, arr2) => {
	let arr3 = [];
	for (let i = 0; i < arr1.length; i++) {
		if (arr2.indexOf(arr1[i]) === -1)
			arr3.push(arr1[i]);
	}
	for (let j = 0; j < arr2.length; j++) {
		if (arr1.indexOf(arr2[j]) === -1)
			arr3.push(arr2[j]);
	}
	return arr3;
}
  // 悬浮展示图表
 iconLFocus= (item, tag) => {
  this.setState({activeFocusKeys: tag ? item : ""})
}
  // 悬浮鼠标展示列表
  listFocus = (item, tag) => {
    this.setState({activeListFocusKeys: tag ? item : ""})
  }
  render() {
    const { activeFocusKeys, activeListFocusKeys, treeDialog } = this.state;
    const {listTag = true, listStyle={}, listItemStyle={}} = this.props;
    const loop = data =>
      data.map(item => {
        if (item.children && item.children.length) {
          return (
            <TreeNode key={item.key} title={<div className="title-box"><div><Icon type="desktop" style={{marginRight: 5}}/>{item.title}</div><Icon type="caret-down" /></div>}>
              {loop(item.children)}
            </TreeNode>
          );
        }
        return <TreeNode key={item.key} 
          title={<div className="title-box title-box-item"
            onMouseOver={() => this.iconLFocus(item.key, true)} 
            onMouseLeave={() => this.iconLFocus(item.key)}>
              {item.title}
            {(activeFocusKeys === item.key && listTag) ? 
              <Icon type="more" className="title-box-icon" onMouseOver={()=>this.listFocus(item.key, true)}/>
            : null}
            {(activeListFocusKeys === item.key && listTag) ? 
              <TreeList list={treeDialog} key={item.key} listFocus={this.listFocus} listStyle={listStyle} listItemStyle={listItemStyle}/> 
               : null}
            </div>} /> 
      });
    return (
      <Tree
        className="draggable-tree"
        showIcon={true}
        expandedKeys={this.state.expandedKeys}
        onSelect={this.onSelect}
        onExpand={this.onExpend}
        draggable
        blockNode
        onDragEnter={this.onDragEnter}
        onDrop={this.onDrop}
      >
        {loop(this.state.gData)}
      </Tree>
    );
  }
}

function TreeList(props) {
  const {list, key, listFocus, listStyle, listItemStyle} = props;
  const mouseOverFunc = function(event) {
    event.stopPropagation();
  }
  const treeItemClick = function(event, func, key) {
     event.stopPropagation();
     event.preventDefault();
     func(key);
  }
  return (
    <div className="tree-list-dialog-mask" onMouseOver={mouseOverFunc} onMouseLeave={() => listFocus(key, false)}>
      <div className="tree-list-dialog" style={listStyle} >
      {list.map(t=>(
          <div className="tree-list-dialog-list" style={listItemStyle} onClick={(event) => treeItemClick(event, t.handleFunc, key)}>{t.title}</div>
        ))}
      </div>
    </div>
  )
}
ReactDOM.render(<Demo />, mountNode);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Ant Design 提供了一个 `Dragger` 组件来实现拖拽上传文件,但如果你想要实现拖拽组件功能,可以使用 `react-dnd` 库来实现。 1. 安装 `react-dnd` 和 `react-dnd-html5-backend` 库: ``` npm install --save react-dnd react-dnd-html5-backend ``` 2. 创建一个可拖拽组件 ```jsx import React from 'react'; import { DragSource } from 'react-dnd'; const boxSource = { beginDrag(props) { return { name: props.name, }; }, }; function collect(connect, monitor) { return { connectDragSource: connect.dragSource(), isDragging: monitor.isDragging(), }; } class Box extends React.Component { render() { const { connectDragSource, isDragging } = this.props; const opacity = isDragging ? 0.4 : 1; return connectDragSource( <div style={{ ...style, opacity }}> {this.props.children} </div> ); } } const style = { border: '1px dashed gray', padding: '0.5rem 1rem', marginBottom: '.5rem', }; export default DragSource('box', boxSource, collect)(Box); ``` 3. 创建一个可放置组件 ```jsx import React from 'react'; import { DropTarget } from 'react-dnd'; const boxTarget = { drop(props, monitor, component) { const item = monitor.getItem(); component.setState({ droppedBoxName: item.name, }); }, }; function collect(connect, monitor) { return { connectDropTarget: connect.dropTarget(), isOver: monitor.isOver(), canDrop: monitor.canDrop(), }; } class TargetBox extends React.Component { constructor(props) { super(props); this.state = { droppedBoxName: null, }; } render() { const { canDrop, isOver, connectDropTarget } = this.props; const isActive = canDrop && isOver; const backgroundColor = isActive ? 'lightgreen' : '#FFF'; return connectDropTarget( <div style={{ ...style, backgroundColor }}> {isActive ? 'Release to drop' : 'Drag item here'} <br /> {this.state.droppedBoxName} </div> ); } } const style = { height: '8rem', width: '8rem', marginRight: '1.5rem', marginBottom: '1.5rem', color: 'black', border: '1px solid black', textAlign: 'center', fontSize: '1rem', lineHeight: 'normal', }; export default DropTarget('box', boxTarget, collect)(TargetBox); ``` 4. 在组件中使用 ```jsx import React from 'react'; import Box from './Box'; import TargetBox from './TargetBox'; class Container extends React.Component { render() { return ( <div> <div style={{ overflow: 'hidden', clear: 'both' }}> <Box name="Glass" /> <Box name="Banana" /> <Box name="Paper" /> </div> <div style={{ overflow: 'hidden', clear: 'both' }}> <TargetBox /> </div> </div> ); } } export default Container; ``` 这样,你就可以在 `Box` 组件拖拽并在 `TargetBox` 组件上放置了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值