基于react antd设计的树状参数需求,之前遇到过这个层级比较深的参数需求,类似请求后端时设计参数,类型可以选择数组对象数字字符串等
代码这里需要注意的是 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;