React hooks +antd 树搜索 + 拖拽

204 阅读1分钟
  1. 下载拖拽功能的依赖
npm i react-dnd-html5-backend react-dnd
  1. 编写拖拽功能
import HTML5Backend from 'react-dnd-html5-backend';
import {useDrag, DndProvider, useDrop} from 'react-dnd';
import {Tree} from 'antd'
import useSearchTree from '../hooks/useSearchTree'

  const [value,setValue] = useState();
  const [dropDataList,setDropDataList] = useState([]);
  const [expandedKeys, setExpandedKeys] = useState([]);
  const [autoExpandParent,setAutoExpandParent] = useState(false);
    const {onChange, newTreeData, initialize, searchExpandedKeys} = useSearchTree()
  
    const renderTreeNodes = data =>
    data && data.map(item => ({
      ...item,
      _title: item.title,
      title: <Title  {...item} />,
      children: item.children ? renderTreeNodes(item.children) : [],
    }))
    
    
    const Title = (props) => {
    const {title} = props;
    const [{}, dragRef] = useDrag({
      item: {props, type: 'box'},
      collect: () => ({}),
    });
    if (!_.isEmpty(props.children)) {
      return <span>{title}</span>
    }
    return <span className='treeSearchData' ref={dragRef}>{title}</span>
  }
    

  const DropTarget = ({dragHandler, children}) => {
    const dropCon = useRef();
    const [_, dropRef] = useDrop({
      accept: 'box',
      drop: item => dragHandler(item)
    });
    return <div ref={dropRef(dropCon)}>{children}</div>;
  };
  
  const onExpand = (e) => {
    setExpandedKeys(e)
    setAutoExpandParent(false)
  }

<div>
    <Input value={value} onChange={e=>{
        const {value} = e.target
        setValue(value)
    }}}  />
    <Button onClick={_=>onChange(value)}>查询</Button>
</div>

<DndProvider backend={HTML5Backend}>
    <div>
       <div className="left">
           <Tree
             treeData={newTreeData}
             autoExpandParent={autoExpandParent}
              expandedKeys={expandedKeys}
             onExpand={onExpand}
           />
       </div>
       <div className='right'>
         <DropTarget dragHandler={(props)=>{
              setDropDataList(props.concat(dropDataList))
         }}>
         {
             dropDataList && dropDataList.map((item)=>{
                 <div>{item.title}</div>
             })
         }
         </DropTarget>
       </div>
    </div>
</DndProvider>
  1. 搜索逻辑钩子 useSearchTree.jsx
import React, {useRef, useState} from 'react'
import {message} from 'antd'
import _ from 'lodash'


const expandedCount = 50;

const freezeData = []

// 将树形结构转成扁平结构
const generateData = (treeData = []) => treeData.reduce((pre, cur) => {
  const {children = [], ...i} = cur;
  return pre.concat([{...i}], generateData(children))
}, [])

const getParentKey = (key, tree) => {
  let parentKey;
  for (let i = 0; i < tree.length; i++) {
    const node = tree[i];
    if (node.children) {
      if (node.children.some((item) => item.key === key)) {
        parentKey = node.key;
      } else if (getParentKey(key, node.children)) {
        parentKey = getParentKey(key, node.children);
      }
    }
  }
  return parentKey;
};

const useSearchTree = () => {
  const dataList = useRef([])
  const [newTreeData, setNewTreeData] = useState([])
  const [searchExpandedKeys, setExpandedKeys] = useState([])


  const initialize = (data) => {
    setNewTreeData(data)
    const cloneData = _.cloneDeep(data)
    freezeData.push(...cloneData)
    dataList.current = generateData(data)
  }

  const onChange = (text) => {
    const value = text?.toLowerCase();

    if (value) {
      const newExpandedKeys = dataList.current.map((item) => {
        if (item._title.indexOf(value) > -1) {
          return getParentKey(item.key, freezeData);
        }
        return null;
      }).filter((item, i, self) => item && self.indexOf(item) === i);

      if (newExpandedKeys.length > expandedCount) {
        message.warn('数据量过大,不自动展开')
      } else {
        setExpandedKeys(newExpandedKeys)
      }
      if (!newExpandedKeys.length) {
        message.warn('没有搜索结果')
      }
      const canSearch = (n) => new RegExp(value, 'i').test(n)

      const filterData = (nodes) => {
        if (!(nodes && nodes.length)) {
          return false;
        }
        const newNodes = []
        for (let i = 0; i < nodes.length; i++) {
          if (canSearch(nodes[i]._title)) {
            newNodes.push(nodes[i])
            nodes[i].children = filterData(nodes[i].children)
          } else {
            const subs = filterData(nodes[i].children);
            if ((subs && subs.length) || canSearch(nodes[i].title)) {
              nodes[i].children = subs;
              newNodes.push(nodes[i])
            }
          }
        }
        return newNodes
      }
      titleLoop(filterData(newTreeData), value)
    } else {
      setNewTreeData(titleLoop(freezeData, value))
    }
  }

  const titleLoop = (data, searchValue) => {
    return data.map((item) => {
      const index = item._title.indexOf(searchValue);
      const beforeStr = item._title.substr(0, index);
      const afterStr = item._title.substr(index + searchValue.length);
      const title = index > -1 ? (
        <span>{beforeStr}
          <span className="site-tree-search-value">{searchValue}</span>
          {afterStr}
          </span>
      ) : (<span>{item._title}</span>);
      item.title = React.cloneElement(item.title, {...item.title.props, title, _title: item._title})
      if (item.children) {
        return {...item, children: titleLoop(item.children, searchValue)};
      }
    })
  }
  return {onChange, newTreeData, initialize, searchExpandedKeys}
}

4.添加 css

.site-tree-search-value {
  color: #f50;
}