根据上一篇目标一,进一步实现树的穿梭框
头部搜索功能有点问题,处理bug 在文末,咨询可私
基于ant-design树的穿梭框,实现穿梭后右侧是已选树,(当前antd右侧只有一个层级)
理想的树的穿梭框:
左边是完整的树,右边是已选的树,左边已选穿梭到右边,左边已选的消失,右边增加已选,右边也可以勾选然后穿梭回去左边,左边出现右边消失。
目标1:右边是已选的树,左边已选穿梭到右边,左边树不变可以继续操作,右边只可以看结果
目标2:右边是已选的树,左边已选穿梭到右边,左边已选的消失掉,右边可以选择穿梭回去
主要核心办法:左边不展示已被穿梭的数据(不改变左边的原数组,采用隐藏的方式,已经被穿梭的或者子集全部被穿梭的不展示)
改动代码:
onSearch={(dir, val) => {
if (dir === 'left') { // 左边搜索
if (val !== '') {
const newDeptList = this.onsearchDataSource(dataSource, val);
this.setState({ newDataSource: newDeptList });
} else {
this.setState({ newDataSource: dataSource });// 左边没搜索时候的原始数据
}
} else { //右边搜索
if (val !== '') {
const newDeptList = this.onsearchDataSource(rightDataSource, val);
this.setState({ newRightDataSource: newDeptList });
} else {
this.setState({ newRightDataSource: rightDataSource }); // 右边没搜索时候的原始数据
}
}
}}
// 树搜索函数
onsearchDataSource(dataSource, val) {
// 递归查找存在的父级
const deepFilter = (i, val) => {
return (
i.children.filter((o) => {
if (o.label.indexOf(val) > -1) {
return true;
}
if (o.children && o.children.length > 0) {
return deepFilter(o, val);
}
}).length > 0
);
};
const filterMenu = (list, val) => {
return list
.filter((item) => {
if (item.label.indexOf(val) > -1) {
return true;
}
if (item.children && item.children.length > 0) {
return deepFilter(item, val);
}
return false;
})
.map((item) => {
item = Object.assign({}, item);
if (item.children) {
item.children = item.children.filter((res) => res.label.indexOf(val) > -1);
filterMenu(item.children, val);
}
return item;
});
};
return filterMenu(dataSource, val);
}
onCheck={(_, even) => {
const {
checkedNodes,
node: {
props: { eventKey },
},
} = even;
// 筛选出最底层的子集 集合
const checkedChildKeys = checkedNodes.reduce((arr, e) => {
if (e.props.children.length <= 0) {
arr.push(e.key);
}
return arr;
}, []);
if (_.length > 0) {
this.setState({ currTargetKeys: lodash.union(targetKeys, checkedChildKeys) });
onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
// 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
onItemSelect('DEL' + Math.random(), true);
} else {
// 加上如果勾来勾去又没勾的情况
this.setState({ currTargetKeys: targetKeys });
}
}}
左边的tree :
if (direction === 'left') {
const checkedKeys = [...selectedKeys, ...currTargetKeys];
return newDataSource.length > 0 ? (
{
const {
checkedNodes,
node: {
props: { eventKey },
},
} = even;
// 筛选出最底层的子集 集合
const checkedChildKeys = checkedNodes.reduce((arr, e) => {
if (e.props.children.length <= 0) {
arr.push(e.key);
}
return arr;
}, []);
if (_.length > 0) {
this.setState({ currTargetKeys: lodash.union(targetKeys, checkedChildKeys) });
onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
// 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
onItemSelect('DEL' + Math.random(), true);
} else {
this.setState({ currTargetKeys: targetKeys });
}
}}>
{generateTree(newDataSource, targetKeys, searchValue, disabled, 'left')}
) : (
);
}
右边的tree :
// 右边从纯展示变成了可更改 所以增加已选的操作
if (direction === 'right') {
const checkedKeys = [...rightCurrTargetKeys];
return newRightDataSource.length > 0 ? (
{
const {
checkedNodes,
node: {
props: { eventKey },
},
} = even;
// 筛选出最底层的子集 集合
const checkedChildKeys = checkedNodes.reduce((arr, e) => {
if (e.props.children.length <= 0) {
arr.push(e.key);
}
return arr;
}, []);
// 勾选后更改左侧已选定内容
this.setState({ currTargetKeys: lodash.xor(checkedChildKeys, targetKeys) });
// 设置右侧已勾选框
this.setState({ rightCurrTargetKeys: checkedChildKeys });
onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
}}>
{generateTree(newRightDataSource, targetKeys, searchValue, false, 'right')}
) : (
);
}
目标一:左边没变
目标二:左边减少上次已穿梭的内容,右边增加新穿梭的内容,
// 不改变左边的原数组,采用隐藏的方式,已经被穿梭的或者子集全部被穿梭的不展示label
const generateTree = (treeNodes = [], checkedKeys = [], searchValue, disabled = false, type = 'left') => {
return treeNodes.map(({ children, ...props }) => {
if (type === 'left') {
// 是否子元素都被选择了 如果是则父不展示了
const isAll =
children.length > 0
? lodash.every(children, (e) => {
return checkedKeys.includes(e.key);
})
: false;
// 自己是否被穿梭了 穿梭了则不展示了 !checkedKeys.includes(props.key)
return !isAll && !checkedKeys.includes(props.key) ? (
{props.label}
}>
{generateTree(children, checkedKeys, searchValue, disabled, type)}
) : (
''
);
} else {
// 右边的展示内容
return (
{props.label}
}>
{generateTree(children, checkedKeys, searchValue, disabled, type)}
);
}
});
};
TreeTransfer 整个组件代码
import React, { Component } from 'react';
import './index.less';
import { Transfer, Tree, Tooltip } from 'antd';
import lodash from 'lodash';
const { TreeNode } = Tree;
const isChecked = (selectedKeys, eventKey) => {
return selectedKeys.indexOf(eventKey) !== -1;
};
const generateTree = (treeNodes = [], checkedKeys = [], searchValue, disabled = false, type = 'left') => {
return treeNodes.map(({ children, ...props }) => {
if (type === 'left') {
// 是否子元素都被选择了
const isAll =
children.length > 0
? lodash.every(children, (e) => {
return checkedKeys.includes(e.key);
})
: false;
return !isAll && !checkedKeys.includes(props.key) ? (
{props.label}
}>
{generateTree(children, checkedKeys, searchValue, disabled, type)}
) : (
''
);
} else {
return (
= 0 ? 'red' : '' }}>
{props.label}
}>
{generateTree(children, checkedKeys, searchValue, disabled, type)}
);
}
});
};
class TreeTransfer extends Component {
constructor(props, context) {
super(props, context);
this.stores = this.props.UserMgtMod;
this.state = {
searchValue: null,
transferDataSource: [],
currTargetKeys: [],
rightCurrTargetKeys: [],
defaultExpandAll: true,
newDataSource: [],
newRightDataSource: [],
};
}
componentDidMount() {
const { dataSource, targetKeys, rightDataSource } = this.props;
this.flatten(dataSource);
this.setState({
currTargetKeys: targetKeys,
newDataSource: dataSource,
newRightDataSource: rightDataSource,
});
}
UNSAFE_componentWillReceiveProps(nextprops) {
const { dataSource, targetKeys, rightDataSource } = nextprops;
this.setState({
currTargetKeys: targetKeys,
newDataSource: dataSource,
newRightDataSource: rightDataSource,
});
}
flatten(list = []) {
const dataSource = this.state.transferDataSource;
list.forEach((item) => {
dataSource.push(item);
this.setState({ transferDataSource: dataSource });
this.flatten(item.children);
});
}
onChange(e) {
this.setState({
searchValue: e.target.value,
});
}
onsearchDataSource(dataSource, val) {
// 递归查找存在的父级
const deepFilter = (i, val) => {
return (
i.children.filter((o) => {
if (o.label.indexOf(val) > -1) {
return true;
}
if (o.children && o.children.length > 0) {
return deepFilter(o, val);
}
}).length > 0
);
};
const filterMenu = (list, val) => {
return list
.filter((item) => {
if (item.label.indexOf(val) > -1) {
return true;
}
if (item.children && item.children.length > 0) {
return deepFilter(item, val);
}
return false;
})
.map((item) => {
item = Object.assign({}, item);
if (item.children) {
item.children = item.children.filter((res) => res.label.indexOf(val) > -1);
filterMenu(item.children, val);
}
return item;
});
};
return filterMenu(dataSource, val);
}
render() {
const { dataSource, targetKeys, rightDataSource, disabled, ...restProps } = this.props;
const { transferDataSource, searchValue, currTargetKeys, rightCurrTargetKeys, newDataSource, newRightDataSource } = this.state;
return (
item.label}
showSearch
showSelectAll={false}
onSearch={(dir, val) => {
if (dir === 'left') {
if (val !== '') {
const newDeptList = this.onsearchDataSource(dataSource, val);
this.setState({ newDataSource: newDeptList });
} else {
this.setState({ newDataSource: dataSource });
}
} else {
if (val !== '') {
const newDeptList = this.onsearchDataSource(rightDataSource, val);
this.setState({ newRightDataSource: newDeptList });
} else {
this.setState({ newRightDataSource: rightDataSource });
}
}
}}>
{({ direction, onItemSelect, selectedKeys }) => {
if (direction === 'left') {
const checkedKeys = [...selectedKeys, ...currTargetKeys];
return newDataSource.length > 0 ? (
{
const {
checkedNodes,
node: {
props: { eventKey },
},
} = even;
// 筛选出最底层的子集 集合
const checkedChildKeys = checkedNodes.reduce((arr, e) => {
if (e.props.children.length <= 0) {
arr.push(e.key);
}
return arr;
}, []);
if (_.length > 0) {
this.setState({ currTargetKeys: lodash.union(targetKeys, checkedChildKeys) });
onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
// 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
onItemSelect('DEL' + Math.random(), true);
} else {
this.setState({ currTargetKeys: targetKeys });
}
}}>
{generateTree(newDataSource, targetKeys, searchValue, disabled, 'left')}
) : (
);
}
if (direction === 'right') {
const checkedKeys = [...rightCurrTargetKeys];
return newRightDataSource.length > 0 ? (
{
const {
checkedNodes,
node: {
props: { eventKey },
},
} = even;
// 筛选出最底层的子集 集合
const checkedChildKeys = checkedNodes.reduce((arr, e) => {
if (e.props.children.length <= 0) {
arr.push(e.key);
}
return arr;
}, []);
// 勾选后更改左侧已选定内容
this.setState({ currTargetKeys: lodash.xor(checkedChildKeys, targetKeys) });
// 设置右侧已勾选框
this.setState({ rightCurrTargetKeys: checkedChildKeys });
onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
}}>
{generateTree(newRightDataSource, targetKeys, searchValue, false, 'right')}
) : (
);
}
}}
);
}
}
export default TreeTransfer;
组件引用案例:
{
this.setState({ roleModal: false, rightDataSource: [] });
}}
onCancel={() => {
this.setState({ roleModal: false, rightDataSource: [] });
}}>
{this.state.roleModal && (
)}
关键函数
handleChange = (newTargetKeys) => {
const targetKeys = newTargetKeys.reduce((arr, e) => {
// 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
if (e.indexOf('DEL') < 0) {
arr.push(e);
}
return arr;
}, []);
// 递归查找是否存在包含选中key的父级
const deepFilter = (i, arr) => {
return (
i.children.filter((o) => {
if (arr.includes(o.key)) {
return true;
}
if (o.children && o.children.length > 0) {
return deepFilter(o, arr);
}
}).length > 0
);
};
// 找到一个存在key的父级 然后遍历获取层级内容
const filterMenu = (list, arr) => {
return list
.filter((item) => {
if (arr.includes(item.key)) {
return true;
}
if (item.children && item.children.length > 0) {
return deepFilter(item, arr);
}
return false;
})
.map((item) => {
item = Object.assign({}, item);
if (item.children) {
item.children = filterMenu(item.children, arr);
}
return item;
});
};
if (this.state.roleModal) {
const { roleList } = toJS(this.stores.state);
this.setState({ roleTargetKeys: [...new Set(targetKeys)] }); // 去重
const rightDataSource = filterMenu(roleList, targetKeys); // 查询组装已选树
this.stores.saveInfoModal({ roleIds: targetKeys });
this.props.form.setFieldsValue({ roleIds: targetKeys });
this.setState({ rightDataSource: rightDataSource || [] });
}
};
触发方法:
{
this.setState({ roleModal: true });
setTimeout(() => {
this.handleChange(this.state.roleTargetKeys);
}, 100);
}}>
{'' + title === 'detail' ? '查看' : '请选择'}
已选{this.state.roleTargetKeys.length}项
目标二就完成了
处理已勾选的显示值bug
初始化
this.state = {
rightCheckLength: 0, // 记录右边已勾
leftCheckLength: 0, // 记录左边已勾
};
}
重置
UNSAFE_componentWillReceiveProps(nextprops) {
// 穿梭到右边更新后重置
this.setState({
leftCheckLength: 0,
rightCheckLength: 0,
});
}
自行定义title
设置勾选
// 左边
const checkedChildKeys = checkedNodes.reduce((arr, e) => {
if (e.props.children.length <= 0) {
arr.push(e.key);
}
return arr;
}, []);
// 设置勾选
this.setState({ leftCheckLength: checkedChildKeys.length });
// 右边
// 筛选出最底层的子集 集合
const checkedChildKeys = checkedNodes.reduce((arr, e) => {
if (e.props.children.length <= 0) {
arr.push(e.key);
}
return arr;
}, []);
this.setState({ currTargetKeys: lodash.xor(checkedChildKeys, targetKeys) });
完整代码:
import React, { Component } from 'react';
import './index.less';
import { Transfer, Tree, Tooltip } from 'antd';
import lodash from 'lodash';
const { TreeNode } = Tree;
const isChecked = (selectedKeys, eventKey) => {
return selectedKeys.indexOf(eventKey) !== -1;
};
const generateTree = (treeNodes = [], checkedKeys = [], searchValue, disabled = false, type = 'left') => {
return treeNodes.map(({ children, ...props }) => {
if (type === 'left') {
// 是否子元素都被选择了
const isAll =
children && children.length > 0
? lodash.every(children, (e) => {
return checkedKeys.includes(e.key);
})
: false;
return !isAll && !checkedKeys.includes(props.key) ? (
{props.label}
}>
{generateTree(children, checkedKeys, searchValue, disabled, type)}
) : (
''
);
} else {
return (
= 0 ? 'red' : '' }}>
{props.label}
}>
{generateTree(children, checkedKeys, searchValue, disabled, type)}
);
}
});
};
class TreeTransfer extends Component {
constructor(props, context) {
super(props, context);
this.stores = this.props.UserMgtMod;
this.state = {
searchValue: null,
transferDataSource: [],
currTargetKeys: [],
rightCurrTargetKeys: [],
defaultExpandAll: true,
newDataSource: [],
newRightDataSource: [],
rightCheckLength: 0,
leftCheckLength: 0,
dataSourceLength: 0,
};
}
componentDidMount() {
const { dataSource, targetKeys, rightDataSource } = this.props;
this.flatten(dataSource);
this.setState({
currTargetKeys: targetKeys,
newDataSource: dataSource,
newRightDataSource: rightDataSource,
});
this.getChildrenLength(dataSource);
}
UNSAFE_componentWillReceiveProps(nextprops) {
const { dataSource, targetKeys, rightDataSource } = nextprops;
this.setState({
leftCheckLength: 0,
rightCheckLength: 0,
});
// this.flatten(nextprops.dataSource);
this.setState({
currTargetKeys: targetKeys,
newDataSource: dataSource,
newRightDataSource: rightDataSource,
});
}
// 获取子数量 用在勾选是左上角的显示
getChildrenLength(dataSource) {
const getTail = (item) => (item.children && item.children.length > 0 ? item.children.map((m) => getTail(m)) : [item]);
const result = lodash.flattenDeep(dataSource.map((m) => getTail(m)));
this.setState({ dataSourceLength: result.length });
}
flatten(list = []) {
const dataSource = this.state.transferDataSource;
list.forEach((item) => {
dataSource.push(item);
this.setState({ transferDataSource: dataSource });
this.flatten(item.children);
});
}
onChange(e) {
this.setState({
searchValue: e.target.value,
});
}
onsearchDataSource(dataSource, val) {
// 递归查找存在的父级
const deepFilter = (i, val) => {
return (
i.children.filter((o) => {
if (o.label.indexOf(val) > -1) {
return true;
}
if (o.children && o.children.length > 0) {
return deepFilter(o, val);
}
}).length > 0
);
};
const filterMenu = (list, val) => {
return list
.filter((item) => {
if (item.label.indexOf(val) > -1) {
return true;
}
if (item.children && item.children.length > 0) {
return deepFilter(item, val);
}
return false;
})
.map((item) => {
item = Object.assign({}, item);
if (item.children) {
item.children = item.children.filter((res) => res.label.indexOf(val) > -1);
filterMenu(item.children, val);
}
return item;
});
};
return filterMenu(dataSource, val);
}
render() {
const { dataSource, targetKeys, rightDataSource, disabled, ...restProps } = this.props;
const { leftCheckLength, rightCheckLength, transferDataSource, searchValue, currTargetKeys, rightCurrTargetKeys, newDataSource, newRightDataSource, dataSourceLength } = this.state;
return (
item.label}
showSearch
titles={[`${leftCheckLength}/${dataSourceLength - targetKeys.length}项`, `${rightCheckLength}/${targetKeys.length}项`]}
showSelectAll={false}
onSearch={(dir, val) => {
if (dir === 'left') {
if (val !== '') {
const newDeptList = this.onsearchDataSource(dataSource, val);
this.setState({ newDataSource: newDeptList });
} else {
this.setState({ newDataSource: dataSource });
}
} else {
if (val !== '') {
const newDeptList = this.onsearchDataSource(rightDataSource, val);
this.setState({ newRightDataSource: newDeptList });
} else {
this.setState({ newRightDataSource: rightDataSource });
}
}
}}>
{({ direction, onItemSelect, selectedKeys }) => {
if (direction === 'left') {
const checkedKeys = [...selectedKeys, ...currTargetKeys];
return newDataSource.length > 0 ? (
{
const {
checkedNodes,
node: {
props: { eventKey },
},
} = even;
// 筛选出最底层的子集 集合
const checkedChildKeys = checkedNodes.reduce((arr, e) => {
if (e.props.children.length <= 0) {
arr.push(e.key);
}
return arr;
}, []);
this.setState({ leftCheckLength: checkedChildKeys.length });
if (_.length > 0) {
this.setState({ currTargetKeys: lodash.union(targetKeys, checkedChildKeys) });
onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
// 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
onItemSelect('DEL' + Math.random(), true);
} else {
this.setState({ currTargetKeys: targetKeys });
}
}}>
{generateTree(newDataSource, targetKeys, searchValue, disabled, 'left')}
) : (
);
}
if (direction === 'right') {
const checkedKeys = [...rightCurrTargetKeys];
return newRightDataSource.length > 0 ? (
{
const {
checkedNodes,
node: {
props: { eventKey },
},
} = even;
// 筛选出最底层的子集 集合
const checkedChildKeys = checkedNodes.reduce((arr, e) => {
if (e.props.children.length <= 0) {
arr.push(e.key);
}
return arr;
}, []);
this.setState({ currTargetKeys: lodash.xor(checkedChildKeys, targetKeys) });
this.setState({ rightCheckLength: checkedChildKeys.length, rightCurrTargetKeys: checkedChildKeys });
onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
}}>
{generateTree(newRightDataSource, targetKeys, searchValue, false, 'right')}
) : (
);
}
}}
);
}
}
export default TreeTransfer;