业务场景:
树和表格联动,当选中树的某个节点时,动态添加表格展示.
难点:
1. 树的层级不固定,当我选中某个子节点的时候,表格中始终展示第二个子节点的数据.
2.表格中带有删除按钮,可以动态取消树的勾选状态
实际应用
反参结构:
[
{
"dspId": "0-0",
"dspName": "第一级数据",
"bsLists": [
{
"bsId": "0-0-1",
"bsName": "要展示在表格中的第二级数据1",
"bsNumber": "30",
"bsUnit": "天",
"outputParamList": [
{
"paramName": "第三级数据1-1",
"paramId": "0-0-1-1",
"checkable": 1,
"childParams": [
{
"paramName": "第四级数据1-1-1",
"paramId": "0-0-1-1-1",
"checkable": 1,
"childParams": []
}
]
}
]
},
{
"bsId": "0-0-2",
"bsName": "要展示在表格中的第二级数据2",
"bsNumber": "30",
"bsUnit": "天",
"outputParamList": [
{
"paramName": "第三级数据2-1",
"paramId": "0-0-2-1",
"checkable": 1,
"childParams": [
{
"paramName": "第四级数据2-1-1",
"paramId": "0-0-2-1-1",
"checkable": 1,
"childParams": []
},
{
"paramName": "第四级数据2-1-2",
"paramId": "0-0-2-1-2",
"checkable": 1,
"childParams": []
}
]
}
]
}
]
}
]
结合antd的数据结构,发现后台反参并不足以直接实现tree的结构,首先第一步就是处理数据结构,注意,重点来了,无论是勾选0-0-2-1-1
还是0-0-2-1-2
亦或者是 0-0-2-1
,表格中数据都是动态渲染0-0-2
中的参数.此处为了减少循环,采用的方法是把需要的参数次第下传.
数据处理成符合tree结构的数据
// apiTreeData 指的是在接口中直接获取到的数据
// 初始值
apiTreeData.map((item, index) => {
const treeChild = [];
// 一级数据处理
item.checkable = item.checkable * 1 === 1; // 复选框
item.tier = 0; // 层级
item.className = tierClass(0);
// 一级的children
item.bsLists.map((it, i) => {
// 二级数据处理
it.checkable = it.checkable * 1 === 1;
it.tier = 1; // 层级
it.className = tierClass(1);
const params = {
// 传递的参数
}
let child = it.outputParamList || [];
console.log('i', i);
// 二级的children
if (it.outputParamList) {
child = loopChild(it.outputParamList, params, 2);
}
treeChild.push({
...params,
title: it.bsName,
key: it.bsId,
children: child,
...item,
});
expandedKey.push(item.dspId);
})
filterTreeData.push({
title: item.dspName,
key: item.dspId,
children: treeChild,
...item
});
})
// 三级及三级以下
function loopChild(child, params, tier) {
const filterData = [];
child.map(children => {
children.title = children.paramName;
children.key = children.paramId;
children.checkable = children.checkable * 1 === 1
children.children = children.childParams;
children.tier = tier;
children.className = tierClass(tier);
console.log('tier', tier)
// 包含下级时
if (children.childParams && JSON.stringify(children.childParams) !== '[]') {
children.children = loopChild(children.children, params, tier + 1);
}
filterData.push({ ...children, ...params });
})
return filterData;
}
展示如下:
数据处理之后
现在我们如愿的实现了tree的展示,接下来就是表格,表格为了区分是否可编辑,在可编辑的tr上添加了icon[不要疑惑 删除按钮对应的函数为什么是props,因为这块的数据处理起来较为麻烦,特意分成了俩个组件来编写]
{
title: '有效期',
dataIndex: 'bsNumber',
key: 'bsNumber',
editable: true,
filterDropdown: true, // 自定义的列筛选功能,我们占位为信息提示Icon的位置
filterIcon: <Tooltip placement="top" title="输入日期" >
<EditOutlined />
</Tooltip>,
// type: 'input',
},
{
title: '有效期',
dataIndex: 'bsUnit',
key: 'bsUnit',
editable: true,
filterDropdown: true, // 自定义的列筛选功能,我们占位为信息提示Icon的位置
filterIcon: <Tooltip placement="top" title="选择单位" >
<EditOutlined />
</Tooltip>,
type: {
kind: 'select',
option: ['年', '月', '周', '天']
},
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
render: (text, record) =>
this.state.dataSource.length >= 1 ? (
<spanonClick={() => { this.props.tableDelete && this.props.tableDelete(record) }}>删除</span>
) : null,
}
效果
表格.png接下来的任务就是将俩个组件联动
- 勾选联动
// --------------------------------tree组件中------------------------------------------------
// 树-选中
const [checkedKeys, setCheckedKeys] = useState([]);
// 监听props传值
useEffect(() => {
setCheckedKeys(props.checkedKeys);
}, [props.checkedKeys])
const onCheck = (checkedKeys, info) => {
setCheckedKeys(checkedKeys);
let parInfo = [];
if (info.checked) {
info.checkedNodes.map(item => {
parInfo.push({
bsId: item.bsId, // 基础服务id
bsNumber: item.bsNumber,
bsUnit: item.bsUnit,
})
})
}
parInfo = deWeight(parInfo, 'bsId'); // 去重
props.onCheck && props.onCheck(checkedKeys, info, parInfo);
};
// 根据数组对象的某一个key去重
function deWeight(arr, key) {
let map = new Map();
for (let item of arr) {
if (!map.has(item[key])) {
map.set(item[key], item);
}
}
return [...map.values()];
}
// -----------------------------------------公众组件------------------------------------------
const [checkedKeys, setCheckedKeys] = useState([]);
const [dataSource, setDataSource] = useState([]);
<TreeMenu
checkedKeys={checkedKeys}
onCheck={(checkedKeys, info, parInfo) => {
setCheckedKeys(checkedKeys);
setDataSource(parInfo);
}}
/>
//-----------------------------------表格组件------------------------------------------------
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props.dataSource !== nextProps.dataSource) {
this.setState({
dataSource: nextProps.dataSource,
count: nextProps.dataSource.length,
})
}
}
- 删除联动
// ----------------------------表格组件--------------------------------------------
<span className={'global-btn-table rc-linkColor '} onClick={() => { this.props.tableDelete && this.props.tableDelete(record) }}>删除</span>
// -----------------------------------------公众组件------------------------------------------
const [checkedKeys, setCheckedKeys] = useState([]);
const [dataSource, setDataSource] = useState([]);
const tableDelete = (record) => {
const filterDataSource = [...dataSource], checked = [...checkedKeys];
const filterChecked = deleteTreeLoop(filterTreeData, checked, record.bsId);
const filterSource = filterDataSource.filter(item => item.bsId !== record.bsId)
setCheckedKeys(filterChecked);
setDataSource(filterSource);
}
// 1.循环树
// 2.循环keys
// 3.树的key=keys[n]&&bsId相同时,delete keys[n];
// 4.得到keys
const deleteTreeLoop = (treeData, checkedKeys, bsId, ck = []) => {
treeData.forEach(item => {
// 筛掉基础服务id相同的数据,checkkey中删除;
checkedKeys.forEach((it, i) => {
if (it === item.key && item.bsId !== bsId) {
item.checked = false;
ck.push(item.key);
}
})
if (item.children && JSON.stringify(item.children) !== '[]') {
deleteTreeLoop(item.children, checkedKeys, bsId, ck);
}
})
return ck; // 筛选之后的数组
}
<EditableTable
dataSource={dataSource || []}
tableDelete={(data) => tableDelete(data)}
/>
最终效果图
总结
后期主要遇到的问题在deleteTreeLoop
函数中,第三个参数的问题
PS: 时间有点赶了,写的有点不知所谓,目前还在联调中,继续研究保存,待我联调通之后在整理下
网友评论