ant-design之Input+Tree组件结合进行“对树结构的数据进行搜索且带出含搜索内容的数据的所有上级“组件的封装

目录

一、功能效果图:

二、实现思路

三、使用方法

四、完整代码


已将该组件封装进我常用的react组件库中,对应的使用文档如下https://lsuyun.github.io/suyun-react-components/tree-select

一、功能效果图:

看题目标题可能还是会感觉有点绕,那我们先来看功能效果:

1、当不输入搜索内容的时候数据如下显示:

 2、当输入“可以”进行搜索时会搜出带有“可以”的数据以及其所有的上级

二、实现思路

 由于我在ant-design没有找到可以现用的组件,于是就用Input+Tree组件结合重新封装一个组件,

先来分析一下我实现这个功能的思路:

1、我在接口拿到的数据是树结构的一个数据结构:

[

    { id:1,

       menuName:'一级测试',

       parentId:0,

       children:[]

    }

]

我是先将树结构的数据类型扁平化arr1

//treeData-树状结构的数组
const flattenTree = getFlatArr(treeData);

// 扁平化数组
const getFlatArr = (treeArr) => {
  let res = [];
  treeArr.map((item) => {
    let data = {};
    data = { children: [], child: item.children }; //child对于勾选功能有用
    const finItem = { ...item, ...data };
    res.push(finItem);
    if (item.children && Array.isArray(item.children)) {
      res = res.concat(getFlatArr(item.children));
    }
  });
  return res;
};

2、将扁平化后的数组arr1进行轮询,拿到包含搜索内容的数组arr2

if (fieldNames) {//fieldNames 自定义搜索内容title,值key取哪个字段
      flattenTree.forEach((ele) => {
        if (ele[fieldNames.title].indexOf(searchV) !== -1) {
          searchArr.push(ele);
        }
      });
    } else {
      flattenTree.forEach((ele) => {
        if (ele.title.indexOf(searchV) !== -1) {
          searchArr.push(ele);
        }
    });
}

3、接着再将arr2进行循环找到其所有父级,将arr2和其里面每条数据的所有上级放在一个数组里,定为arr3

let parentNode = [];
searchArr.forEach((sA) => {
      parentNode = [
        ...parentNode,
        ...getAllParentN(flattenTree, sA.parentId, fieldNames ? fieldNames.key : 'key', parentId),
      ];
});

/**
 * @description: 获取全部父级
 * @param {*} arr:遍历的数组 key:和id比对的key  id:当前唯一值 parentId:最高级父节点id
 * @return {*}  返回全部的父级 []
 */
const getAllParentN = (arr, id, key, parentId = -1) => {
  let res = [];
  arr.forEach((item) => {
    if (item[key] == id) {
      res.push(item);
      if (item.parentId != parentId) {
        res = res.concat(getAllParentN(arr, item.parentId, key, parentId));
      }
    }
  });
  return res;
};

4、这个时候获得的arr3肯定会有重复的,因此对arr3进行去重,去重之后再将arr3转为树结构的数组。

const allArr = getTreeData(
      arrNotequal([...searchArr, ...parentNode], fieldNames ? fieldNames.key : 'key'),
      fieldNames ? fieldNames.key : 'key',
      parentId,
);

// 数组去重
const arrNotequal = (arr, key) => {
  const obj = {};
  const peno = arr.reduce((cur, next) => {
    obj[next[key]] ? '' : (obj[next[key]] = true && cur.push(next));
    return cur;
  }, []);
  return peno;
};

/**
 * @description: 将平级数组转为tree
 * @param {*}  arr:数组 必须包含parentId parentId:最顶层父节点的parentId
 * @return {*}
 */
const getTreeData = (arr, key = 'id', parentId = -1) => {
  arr.forEach((item) => {
    if (item.hasOwnProperty('parentId')) {
      item.parentN = item.parentId;
    }
  });

  const tree = arr.filter((father) => {
    const arrN = arr.filter((child) => {
      return father[key] === child.parentN;
    });
    if (arrN.length > 0) {
      father.children = arrN;
    }
    return father.parentN == parentId;
  });
  return tree;
};

三、使用方法

使用方法如下:

1、可通过treeData传入树结构的数组,默认值[]

2、checkable控制是否有勾选的功能,默认值为true

3、onCheck为点击勾选时的触发的方法

4、fieldNames自定义字段(title:对该字段的值进行搜索匹配  key为选中的值)

5、parentId当该数据为最顶级父级节点时的parentId的值,默认值为-1

6、onSelect选中整条数据时触发的事件

7、checkedKeysExt传入的选中的值-为数组,当checkable为true时有用

8、checkStrictly:父子节点选中状态是否有关联,默认值为false

9、isSelfRender:是否自定义渲染的内容,默认值为false

例1:

<TreeSelect treeData={treeData} isSelfRender={true} fieldNames={fieldNames}  />

isSelfRender为true时可自定义内容样式

const treeData = [
   title:<div>自定义内容{item.menuName}</div>, //注:自定义的内容必须放在title字段
   id:'10',
   children:[]
]

const fieldNames = {
   key: 'id',
   title: 'menuName', //对搜索内容进行匹配的字段
};

四、完整代码

TreeSelect.jsx

import React from 'react';
import { Tree, Input } from 'antd';
import { useState, useEffect, useMemo } from 'react';
import { arrNotequal, getAllParentN, getTreeData, getFlatArr } from '@/utils';
import { useDebounce } from '@umijs/hooks';

/**
 * @description:
 * @param {*}treeData-树类型数据(title,key,children)  isSelfRender-是否自定义title  onSelect-点击,返回点击的数据 fieldNames(key,title)修改渲染字段
 * @return {*}
 */
const TreeSelect = ({
  treeData,
  checkable,
  onCheck,
  fieldNames,
  parentId = -1,
  onSelect,
  checkedKeysExt,
  checkStrictly = false,
  isSelfRender = false,
}) => {
  const [checkedKeys, setCheckedKeys] = useState([]);
  const [showTreeD, setShowTreeD] = useState(treeData);
  const [searchV, setSearchV] = useState('');
  const debouncedValue = useDebounce(searchV, 300); // 防抖
  // const [defaultExpandAll, setDefaultExpandAll] = useState(true);
  let allCheckedKeys = [];

  // 获取所有子级
  const getChildNode = (arr, key) => {
    let res = [];
    arr.forEach((arrD) => {
      res.push(arrD[key]);
      if (arrD.children) {
        res = res.concat(getChildNode(arrD.children, key));
      }
    });
    return res;
  };

  const handleCheck = (checkedKeysValue, e) => {
    if (checkStrictly) {
      setCheckedKeys(checkedKeysValue.checked);
      return false;
    }
    const flattenTree = getFlatArr(treeData);
    let doId = [];
    if (e.node.children) {
      getFlatArr(e.node.children).forEach((ele) => {
        doId.push(fieldNames ? ele.key : ele[fieldNames.key]);
      });
    }
    doId.push(e.node.key);
    if (e.checked) {
      allCheckedKeys = Array.from(new Set([...checkedKeys, ...doId]));
    } else {
      // 当取消选中时需要将上级的父节点清除掉,数据筛选时需要
      let childId = [];
      flattenTree.forEach((ft) => {
        // eslint-disable-next-line
        if (ft.id == e.node.key) {
          if (ft.child) {
            childId = getChildNode(ft.child, fieldNames ? fieldNames.key : 'key');
          }
        }
      });
      doId = [...childId, ...doId];
      const parentNode = getAllParentN(
        flattenTree,
        e.node.key,
        fieldNames ? fieldNames.key : 'key',
        parentId,
      );
      parentNode.forEach((pd) => {
        doId.push(fieldNames ? pd[fieldNames.key] : pd.key);
      });
      allCheckedKeys = checkedKeys.filter((item) => !doId.includes(item));
    }
    setTimeout(() => {
      setCheckedKeys(allCheckedKeys);
    });
    if (onCheck) {
      onCheck(allCheckedKeys);
    }
  };
  const changeTreeD = (data) => {
    data.forEach((ele) => {
      ele.key = ele[fieldNames.key];
      ele.title = ele[fieldNames.title];
      if (ele.children) {
        changeTreeD(ele.children);
      }
    });
  };

  const filterTreeF = () => {
    const flattenTree = getFlatArr(treeData);
    const searchArr = [];
    if (fieldNames) {
      flattenTree.forEach((ele) => {
        if (ele[fieldNames.title].indexOf(searchV) !== -1) {
          searchArr.push(ele);
        }
      });
    } else {
      flattenTree.forEach((ele) => {
        if (ele.title.indexOf(searchV) !== -1) {
          searchArr.push(ele);
        }
      });
    }
    let parentNode = [];
    searchArr.forEach((sA) => {
      parentNode = [
        ...parentNode,
        ...getAllParentN(flattenTree, sA.parentId, fieldNames ? fieldNames.key : 'key', parentId),
      ];
    });
    const allArr = getTreeData(
      arrNotequal([...searchArr, ...parentNode], fieldNames ? fieldNames.key : 'key'),
      fieldNames ? fieldNames.key : 'key',
      parentId,
    );
    setShowTreeD(allArr);
    setTimeout(() => {
      setCheckedKeys(checkedKeys);
    }, 100);
  };

  // 搜索
  const onChange = (e) => {
    setSearchV(e.target.value);
  };

  const handleSelect = (value, e) => {
    if (onSelect) {
      const flattenTree = getFlatArr(treeData);
      if (fieldNames) {
        flattenTree.forEach((ele) => {
          // eslint-disable-next-line
          if (ele[fieldNames.key] == e.node.key) {
            onSelect(ele);
          }
        });
      } else {
        flattenTree.forEach((ele) => {
          // eslint-disable-next-line
          if (ele.key == e.node.key) {
            onSelect(ele);
          }
        });
      }
    }
  };

  useMemo(() => {
    if (!isSelfRender) {
      changeTreeD(treeData);
    }
    setShowTreeD(treeData);
  }, [treeData, isSelfRender]);

  useEffect(() => {
    if (checkable) {
      allCheckedKeys = checkedKeys;
    }
    filterTreeF();
  }, [debouncedValue]);

  useEffect(() => {
    if (checkedKeysExt) {
      setCheckedKeys(checkedKeysExt);
    }
  }, [checkedKeysExt]);

  return (
    <>
      <Input
        style={{ marginBottom: 8 }}
        placeholder="搜索"
        onChange={onChange}
        value={searchV}
        style={{ width: '80%', marginBottom: '20px' }}
      />
      {showTreeD.length ? (
        <Tree
          onCheck={handleCheck}
          checkedKeys={checkedKeys}
          onSelect={handleSelect}
          checkable={checkable}
          checkStrictly={checkStrictly}
          blockNode
          defaultExpandAll
          treeData={showTreeD}
        >
          {/* {renderNode(showTreeD)} */}
        </Tree>
      ) : (
        ''
      )}
    </>
  );
};
export default React.memo(TreeSelect);

utils/index.js

// 数组去重
const arrNotequal = (arr, key) => {
  const obj = {};
  const peno = arr.reduce((cur, next) => {
    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
    obj[next[key]] ? '' : (obj[next[key]] = true && cur.push(next));
    return cur;
  }, []);
  return peno;
};

/**
 * @description: 获取全部父级
 * @param {*} arr:遍历的数组 key:和id比对的key  id:当前唯一值 parentId:最高级父节点id
 * @return {*}  返回全部的父级 []
 */
const getAllParentN = (arr, id, key, parentId = -1) => {
  let res = [];
  arr.forEach((item) => {
    if (item[key] == id) {
      res.push(item);
      if (item.parentId != parentId) {
        res = res.concat(getAllParentN(arr, item.parentId, key, parentId));
      }
    }
  });
  return res;
};

/**
 * @description: 将平级数组转为tree
 * @param {*}  arr:数组 必须包含parentId parentId:最顶层父节点的parentId
 * @return {*}
 */
const getTreeData = (arr, key = 'id', parentId = -1) => {
  arr.forEach((item) => {
    if (item.hasOwnProperty('parentId')) {
      item.parentN = item.parentId;
    }
  });

  const tree = arr.filter((father) => {
    const arrN = arr.filter((child) => {
      return father[key] === child.parentN;
    });
    if (arrN.length > 0) {
      father.children = arrN;
    }
    return father.parentN == parentId;
  });
  return tree;
};

// 扁平化数组
const getFlatArr = (treeArr) => {
  let res = [];
  treeArr.map((item) => {
    let data = {};
    data = { children: [], child: item.children };
    const finItem = { ...item, ...data };
    res.push(finItem);
    if (item.children && Array.isArray(item.children)) {
      res = res.concat(getFlatArr(item.children));
    }
  });
  return res;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值