import React, { useMemo, useState } from 'react'
import { Input, Tree, Dropdown } from 'antd'
import type { TreeDataNode } from 'antd'
import { MoreOutlined } from '@ant-design/icons'
import type { MenuProps } from 'antd'
import { MdOutlineAddTask } from 'react-icons/md'
const { Search } = Input
import { FaEdit } from 'react-icons/fa'
import { RiDeleteBinLine } from 'react-icons/ri'
// title: 节点标题 key:唯一标识 children: 子节点
const defaultData: TreeDataNode[] = [
  {
    title: 'NodeA',
    key: 'nodea',
    children: [
      { title: '123', key: 'nodea1' },
      { title: '456', key: 'nodea2' },
    ],
  },
  {
    title: 'NodeB',
    key: 'nodeb',
    children: [
      { title: 'NodeB1', key: 'nodeb1' },
      { title: 'NodeB2', key: 'nodeb2' },
    ],
  },
]

// 平铺处理,把所有节点的 key 和 title 存储到 dataList 中,让它成为一层的数据
const dataList: { key: React.Key; title: string }[] = []
function generateList(data: TreeDataNode[]): void {
  data.forEach(node => {
    dataList.push({ key: node.key, title: String(node.title) })
    if (node.children) {
      generateList(node.children)
    }
  })
}
generateList(defaultData)

// 递归获取一个节点的父节点的 key
function getParentKey(key: React.Key, tree: TreeDataNode[]): React.Key | null {
  let parentKey: React.Key | null = null
  for (const node of tree) {
    if (node.children) {
      if (node.children.some(item => item.key === key)) {
        parentKey = node.key
      } else {
        const childParent = getParentKey(key, node.children)
        if (childParent) {
          parentKey = childParent
        }
      }
    }
  }
  return parentKey
}

const SideOpts: React.FC = () => {
  //存储当前展开节点的 keys 数组,用于控制树组件哪些节点处于展开状态。
  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([])
  //存储搜索框内的关键字,用于匹配节点文本。
  const [searchValue, setSearchValue] = useState('')
  //控制树形组件自动展开父节点,通常在搜索时启用,方便显示匹配结果。
  const [autoExpandParent, setAutoExpandParent] = useState(true)
  // 搜索框 onChange 事件:当有输入时展开所有节点
  function onChange(e: React.ChangeEvent<HTMLInputElement>) {
    const { value } = e.target
    setSearchValue(value)
    if (value) {
      // 筛选出匹配搜索词的节点 toLowerCase() 方法将所有字符串转换为小写 filter表示过滤出符合条件的数据
      //indexOf 方法查找转换后的标题中是否包含转换后的搜索关键字  > -1 表示包含 -1 表示不包含
      const matchedKeys = dataList
        .filter(item => item.title.toLowerCase().indexOf(value.toLowerCase()) > -1)
        .map(item => getParentKey(item.key, defaultData))
        .filter((item, i, self) => item && self.indexOf(item) === i) as React.Key[]
      // expandedKeys为所有展开节点的key的数组
      setExpandedKeys(matchedKeys)
      //当 expandedKeys 更新时,自动展开其父节点。
      setAutoExpandParent(true)
    } else {
      setExpandedKeys([])
    }
  }

  // 在 useMemo 中对树形数据做搜索高亮以及添加右侧菜单的处理
  const treeData = useMemo(() => {
    const loop = (data: TreeDataNode[]): TreeDataNode[] =>
      data.map(item => {
        const strTitle = String(item.title)
        const index = strTitle.toLowerCase().indexOf(searchValue.toLowerCase())
        let nodeTitle
        if (index > -1 && searchValue) {
          const beforeStr = strTitle.substring(0, index)
          const matchStr = strTitle.substring(index, searchValue.length)
          const afterStr = strTitle.substring(index + searchValue.length)
          nodeTitle = (
            <span>
              {beforeStr}
              <span style={{ color: '#f50' }}>{matchStr}</span>
              {afterStr}
            </span>
          )
        } else {
          nodeTitle = <span>{strTitle}</span>
        }

        // 定义菜单,每个菜单项的点击事件可根据需要进行扩展
        const items: MenuProps['items'] = [
          {
            key: 'edit',
            label: (
              <div>
                <FaEdit />
                <a onClick={() => console.log('编辑算子')}>编辑算子</a>
              </div>
            ),
          },
          {
            type: 'divider',
          },
          {
            key: 'delete',
            label: (
              <div>
                <RiDeleteBinLine />
                <a onClick={() => console.log('删除算子')}>删除算子</a>
              </div>
            ),
          },
          {
            type: 'divider',
          },
          {
            key: 'publish',
            label: (
              <div>
                <MdOutlineAddTask />
                <a onClick={() => console.log('发布算子')}>发布算子</a>
              </div>
            ),
          },
        ]

        // 用 flex 布局包装标题和右侧菜单图标
        const newTitle = (
          <div
            style={{
              display: 'flex',
              justifyContent: 'space-between',
              alignItems: 'center',
            }}
          >
            <div>{nodeTitle}</div>
            {/* 只有当节点为叶子节点时,显示下拉菜单 */}
            {!item.children && (
              <Dropdown menu={{ items }} trigger={['click']}>
                <span style={{ cursor: 'pointer', marginLeft: 8 }}>
                  <MoreOutlined />
                </span>
              </Dropdown>
            )}
          </div>
        )

        return {
          title: newTitle,
          key: item.key,
          children: item.children ? loop(item.children) : undefined,
        }
      })
    return loop(defaultData)
  }, [searchValue])

  return (
    <div>
      <Search style={{ marginBottom: 8 }} placeholder="搜索节点" onChange={onChange} />
      <Tree
        expandedKeys={expandedKeys}
        onExpand={keys => {
          setExpandedKeys(keys)
          setAutoExpandParent(false)
        }}
        autoExpandParent={autoExpandParent}
        treeData={treeData}
      />
    </div>
  )
}

export default SideOpts

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐