一个树状结构的参数需求

90 阅读3分钟

基于react antd设计的树状参数需求,之前遇到过这个层级比较深的参数需求,类似请求后端时设计参数,类型可以选择数组对象数字字符串等

image.png

代码这里需要注意的是 const [treeData, setTreeData] = useState({tree: []});这行代码。这里之所以定义成对象,是因为这个树状在编辑回显的时候,拿的是引用,所以要提前写好引用,后面数据有修改只修改tree的值。直接定义数组,从后端拿到数据再用setTreeData(data)修改数据,势必会改变引用,控件就拿不到更新的数据了。内部源码我还没看,我猜它内部就接收过一次参数就没有监听了。我也是跳过很多坑才发现的,如有错误,欢迎指正。




import React, { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
import { Form, Select, Input, Row, Col, Button, Tree, Space } from 'antd';
import { PlusCircleOutlined, MinusCircleOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import styles from '../index.module.less';
import { cloneDeep } from 'lodash';
import {randomNumCount} from '@/utils/common'
// import Api from '@/api/handling';

const ParamSet = forwardRef((props, ref) => {
  const { onChange, value = [], paramTypeList } = props;
  const [treeData, setTreeData] = useState({tree: []});
  const [key, setKey] = useState(0);
const resetComponent = () => setKey(prev => prev + 1);
  // const [paramTypeList, setParamTypeList] = useState([
  //   { value: 'string', label: '字符串' },
  //   { value: 'integer', label: '整型' },
  //   { value: 'long', label: '长整型' },
  //   { value: 'boolean', label: '布尔型' },
  //   { value: 'object', label: '对象' },
  //   { value: 'array', label: '数组' },
  // ]);
  // const getDict = async () => {
  //   try {
  //     const res = await Api.getDictValues({ dictCode: 'paramTypeEnum' });
  //     const paramTList = res.data?.map((item) => ({ value: item.dictValue, label: item.dictName }));
  //     console.log('paramTList', paramTList); // 输出
  //     setParamTypeList(paramTList);
  //   } catch (error) {}
  // };
  // useEffect(() => {
  //   getDict();
  // }, []);
  const formatNewChildren = (children, parentNode) => {
    if (!children) {
      return [];
    }
    const res = children.map((item) => {
      const newItem = {
        title: (key) => renderTreeNodesTitle(key),
        key: `${parentNode?.key || '0'}-${randomNumCount()}`,
        isLeaf: ['array', 'object'].includes(item.type) ? false : true,
        parentType: parentNode?.type,
        parentNode: parentNode,
        name: item.name,
        type: item.type,
        // children: ['object'].includes(item.type)? formatNewChildren(item.children, item) : [],
        value: item.value,
        description: item.description,
      };
      if (['array', 'object'].includes(item.type)) {
        newItem.children = formatNewChildren(item.children, newItem);
      }
      return newItem;
    });
    return res;
  };
  const addNode = (currNode) => {
    console.log('添加节点', currNode)
    if (currNode.isLeaf) {
      currNode.isLeaf = false;
    }
    // 情况1:当前是数组添加子节点,数组内的子元素类型必须一致
    if (currNode.type === 'array' && currNode.children && currNode.children.length > 0) {
      const firstChild = cloneDeep(currNode.children[0]);
      currNode.children.push({
        title: (key) => renderTreeNodesTitle(key),
        key: `${currNode.key}-${randomNumCount()}`,
        isLeaf: ['array', 'object'].includes(firstChild.type) ? false : true,
        parentType: currNode.type,
        parentNode: currNode,
        name: '',
        type: firstChild.type,
        children: ['object'].includes(firstChild.type) ? formatNewChildren(firstChild.children, firstChild) : [],
        value: '',
        description: '',
      });
      console.log('treeData',treeData)
      setTreeData({...treeData});
      resetComponent();
      handleOnChange();
      return;
    }
    // 情况2:数组的子元素是对象添加子节点,对象内的子元素类型必须一致
    if (currNode.type === 'object' && currNode.parentType === 'array') {
      currNode.parentNode.children.forEach((item) => {
        if (!item.children) {
          item.children = [];
        }
        item.children.push({
          title: (key) => renderTreeNodesTitle(key),
          key: `${item.key}-${randomNumCount()}`,
          isLeaf: true,
          parentType: item.type,
          parentNode: item,
          name: '',
          type: 'string',
          value: '',
          description: '',
        });
      });
      console.log('currNode',currNode)
      setTreeData({...treeData});
      resetComponent();
      handleOnChange();
      return;
    }
    // 情况3:当前是根节点添加子节点
    if (!currNode.children) {
      currNode.children = [];
    }
    currNode.children.push({
      title: (key) => renderTreeNodesTitle(key),
      key: `${currNode.key}-${randomNumCount()}`,
      isLeaf: true,
      parentType: currNode.type,
      parentNode: currNode,
      name: '',
      type: 'string',
      value: '',
      description: '',
    });
    console.log('currNode',currNode)
    console.log('treeData',treeData)
    setTreeData({...treeData});
    resetComponent();
    handleOnChange();
  };
  const findNodes = (treeData, currNode) => {
    const res = treeData.filter((item) => {
      if (item.key !== currNode.key) {
        if (item.children?.length) {
          item.children = findNodes(item.children, currNode);
        }
        return true;
      } else {
        return false;
      }
    });
    return res;
  };
  const delNode = (currNode) => {
    if (currNode.parentType === 'object' && currNode.parentNode.parentType === 'array') {
      const arrayChildren = currNode.parentNode.parentNode.children;
      console.log('currNode.parentNode.children', currNode.parentNode.children); // 输出
      const indexChildren = currNode.parentNode.children.findIndex((item) => item.key === currNode.key);
      console.log('key', currNode.key); // 输出
      console.log('indexChildren', indexChildren); // 输出
      arrayChildren.forEach((item) => {
        if (item.children && item.children[indexChildren]) {
          item.children.splice(indexChildren, 1);
        }
      });
      setTreeData({...treeData});
      resetComponent();
    } else {
      const res = findNodes(treeData.tree, currNode);
      treeData.tree = res;
      setTreeData({...treeData});
      resetComponent();
    }
    handleOnChange();
  };
  const changeParamName = (e, currNode) => {
    console.log(e.target.value);
    console.log(currNode);
    currNode.name = e.target.value;
    if (currNode.parentType === 'object' && currNode.parentNode.parentType === 'array') {
      const arrayChildren = currNode.parentNode.parentNode.children;
      console.log('currNode.parentNode.children', currNode.parentNode.children); // 输出
      const indexChildren = currNode.parentNode.children.findIndex((item) => item.key === currNode.key);
      console.log('key', currNode.key); // 输出
      console.log('indexChildren', indexChildren); // 输出
      arrayChildren.forEach((item) => {
        if (!item.children) {
          item.children = [];
          item.children.push({
            ...currNode,
            name: e.target.value,
            key: `${item.key}-${randomNumCount()}`,
            parentType: item.type,
            parentNode: item,
          });
        } else {
          if (!item.children[indexChildren]) {
            item.children[indexChildren] = {
              ...currNode,
              name: e.target.value,
              key: `${item.key}-${randomNumCount()}`,
              parentType: item.type,
              parentNode: item,
            };
          } else {
            item.children[indexChildren].name = e.target.value;
          }
        }
      });
    }
    setTreeData({...treeData});
    // resetComponent();
    handleOnChange();
  };
  const changeParamType = (e, currNode) => {
    currNode.type = e;
    if (e === 'object' || e === 'array') {
      currNode.children = null;
      currNode.isLeaf = false;
      currNode.value = '';
    } else {
      currNode.children = null;
      currNode.isLeaf = true;
    }
    if (currNode.parentType === 'array') {
      currNode.parentNode.children.forEach((item) => {
        item.type = currNode.type;
        item.children = null;
        item.isLeaf = ['object', 'array'].includes(currNode.type) ? false : true;
      });
    }
    setTreeData({...treeData});
    resetComponent();
    handleOnChange();
  };
  const changeParamValue = (e, currNode) => {
    currNode.value = e.target.value;
    setTreeData({...treeData});
    // resetComponent();
    handleOnChange();
  };
  const changeParamDesc = (e, currNode) => {
    currNode.description = e.target.value;
    setTreeData({...treeData});
    // resetComponent();
    handleOnChange();
  };
  const renderTreeNodesTitle = (currNode) => {
    console.log('整棵树', treeData);
    return (
      <div key={currNode.key}>
        <Space size={3}>
          <Input
            placeholder="参数名"
            value={currNode.name}
            disabled={currNode.parentType === 'array'}
            onChange={(e) => changeParamName(e, currNode)}
          />
          <Select
            placeholder="参数类型"
            style={{ width: 120 }}
            value={currNode.type}
            onChange={(e) => changeParamType(e, currNode)}
            options={paramTypeList}
          />
          <div style={{ width: '120px' }}>
            <Input
              placeholder="参数值"
              disabled={['object', 'array'].includes(currNode.type)}
              value={currNode.value}
              onChange={(e) => changeParamValue(e, currNode)}
            />
          </div>
          <div style={{ width: '120px' }}>
            <Input placeholder="备注" value={currNode.description} onChange={(e) => changeParamDesc(e, currNode)} />
          </div>
          <div style={{ width: '10px' }}>
            {['object', 'array'].includes(currNode.type) && (
              <PlusCircleOutlined className={styles.headerIcon} onClick={() => addNode(currNode)} />
            )}
          </div>
          <div style={{ width: '10px' }}>
            {!currNode.children?.length && (
              <MinusCircleOutlined className={styles.headerIcon} onClick={() => delNode(currNode)} />
            )}
          </div>
        </Space>
      </div>
    );
  };

  const formatTreeData = (treeData = []) => {
    const res = treeData.map((item) => {
      if (item.type === 'object' || item.type === 'array') {
        if (item.children?.length) {
          item.children = formatTreeData(item.children);
        }
        return {
          name: item.name,
          type: item.type,
          children: item.children,
          description: item.description,
        };
      }
      return {
        name: item.name,
        type: item.type,
        value: item.value,
        description: item.description,
      };
    });
    return res;
  };
  useEffect(() => {
    if (!value.length) {
      treeData.tree =[
        {
          title: (key) => renderTreeNodesTitle(key),
          key: '0-0',
          isLeaf: false,
          name: 'root',
          type: 'object',
          description: 'root',
          children: [],
        },
      ];
      setTreeData({...treeData});
      resetComponent();
      return;
    }
    const copyData = cloneDeep(value);
    console.log('value', value); // 输出
    const resTreeData = formatNewChildren(copyData, null);
    console.log('resTreeData', resTreeData); // 输出
    const data = cloneDeep(resTreeData);
    treeData.tree = data
    setTreeData({...treeData});
    resetComponent();
  }, []);
  useEffect(() => {
    console.log('treeData是否变化了', treeData); // 输出
  },[treeData])
  const handleOnChange = () => {
    const copyData = cloneDeep(treeData.tree);
    const resTreeData = formatTreeData(copyData);
    onChange(resTreeData);
  };
  useImperativeHandle(ref, () => ({
    getParams: (newParams) => {
      return treeData;
    },
  }));
  return (
    <div>
      {
        treeData.tree.length > 0 && 
        <Tree autoExpandParent={true} key={key} defaultExpandAll={true} treeData={treeData.tree} />
      }
    </div>
  );
});
export default ParamSet;