antd 有一个可拖拽的树组件,很好用,但是展开只能点击前面的小图标,而且样式固定。
为了满足需求,拓展了一个点击列表展开收缩树,可拖拽,有功能列表的树组件。
const { Tree, Icon } = antd;
const { TreeNode } = Tree;
const x = 3;
const y = 2;
const z = 1;
const gData = [];
const generateData = (_level, _preKey, _tns) => {
const preKey = _preKey || '0';
const tns = _tns || gData;
const children = [];
for (let i = 0; i < x; i++) {
const key = `${preKey}-${i}`;
tns.push({ title: key, key });
if (i < y) {
children.push(key);
}
}
if (_level < 0) {
return tns;
}
const level = _level - 1;
children.forEach((key, index) => {
tns[index].children = [];
return generateData(level, key, tns[index].children);
});
};
generateData(z);
/*
*@sourceTree Array<{title, key, children}> 数据源
*@setList Array<{title, handleFunc}>
*/
class Demo extends React.Component {
state = {
gData: [],
expandedKeys: [],
autoExpandParent: true,
selectedKeys: [],
activeFocusKeys: '', //鼠标悬浮展示图标
activeListFocusKeys: '', //鼠标悬浮展示列表
treeDialog: []
};
componentDidMount() {
const { sourceTree, expandedKeys } = this.props;
this.setState({
gData: sourceTree || gData,
expandedKeys: [] || expandedKeys,
treeDialog: [{title: '增加子货架', handleFunc: ()=>{}}, {title: '增加', handleFunc: ()=>{}}, {title: '删除', handleFunc: ()=>{}}, {title: '修改', handleFunc: ()=>{}}]
})
}
onDragEnter = info => {
console.log(info);
// expandedKeys 需要受控时设置
// this.setState({
// expandedKeys: info.expandedKeys,
// });
};
onDrop = info => {
const dropKey = info.node.props.eventKey;
const dragKey = info.dragNode.props.eventKey;
const dropPos = info.node.props.pos.split('-');
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
const loop = (data, key, callback) => {
data.forEach((item, index, arr) => {
if (item.key === key) {
return callback(item, index, arr);
}
if (item.children) {
return loop(item.children, key, callback);
}
});
};
const data = [...this.state.gData];
// Find dragObject
let dragObj;
loop(data, dragKey, (item, index, arr) => {
arr.splice(index, 1);
dragObj = item;
});
if (!info.dropToGap) {
// Drop on the content
loop(data, dropKey, item => {
item.children = item.children || [];
// where to insert 示例添加到尾部,可以是随意位置
item.children.push(dragObj);
});
} else if (
(info.node.props.children || []).length > 0 && // Has children
info.node.props.expanded && // Is expanded
dropPosition === 1 // On the bottom gap
) {
loop(data, dropKey, item => {
item.children = item.children || [];
// where to insert 示例添加到头部,可以是随意位置
item.children.unshift(dragObj);
});
} else {
let ar;
let i;
loop(data, dropKey, (item, index, arr) => {
ar = arr;
i = index;
});
if (dropPosition === -1) {
ar.splice(i, 0, dragObj);
} else {
ar.splice(i + 1, 0, dragObj);
}
}
console.log(data)
this.setState({
gData: data,
});
};
//选中的回调
onSelect = (selectedKeys, obj) => {
let {expandedKeys} = this.state;
let selectedKey = this.state.selectedKeys;
//选中的状态
if (obj.selected) {
// 没有 children 代表当前已没有下一级目录
if (obj.event && obj.selectedNodes.length === 1 && !obj.selectedNodes[0].props.children) {
//do something
return;
}
//判断是否已经展开,未展开就添加到 expandedKeys 中
//已经展开就不用管
let index = expandedKeys.indexOf(selectedKeys[0])
if (index === -1) {
this.setState({
expandedKeys: [...expandedKeys, selectedKeys[0]],
selectedKeys: selectedKeys
})
} else {
const expanedeNews = expandedKeys.filter(t=>t!==selectedKeys[0])
this.setState({
expandedKeys: expanedeNews,
selectedKeys: selectedKeys
})
}
} else {
//selectedKey 是上次选中的元素 在 expandedKeys 肯定是存在的
//找到下标后需要过滤掉子类目录 例如上次选中的元素为 /a ,
//子类 /a/a_1 已经展开就需要从 expandedKeys 中移除这个元素
let index = expandedKeys.indexOf(selectedKey[0])
if (index !== -1) {
//过渡掉子类元素
expandedKeys = expandedKeys.filter((ele) => {
return !ele.includes(selectedKey[0])
})
this.setState({
expandedKeys: expandedKeys,
selectedKeys: selectedKey
})
} else {
this.setState({
selectedKeys: selectedKey,
expandedKeys: [...expandedKeys, ...selectedKey]
})
}
}
}
//比较出2个数组中不一样的元素
diffArray = (arr1, arr2) => {
let arr3 = [];
for (let i = 0; i < arr1.length; i++) {
if (arr2.indexOf(arr1[i]) === -1)
arr3.push(arr1[i]);
}
for (let j = 0; j < arr2.length; j++) {
if (arr1.indexOf(arr2[j]) === -1)
arr3.push(arr2[j]);
}
return arr3;
}
// 悬浮展示图表
iconLFocus= (item, tag) => {
this.setState({activeFocusKeys: tag ? item : ""})
}
// 悬浮鼠标展示列表
listFocus = (item, tag) => {
this.setState({activeListFocusKeys: tag ? item : ""})
}
render() {
const { activeFocusKeys, activeListFocusKeys, treeDialog } = this.state;
const {listTag = true, listStyle={}, listItemStyle={}} = this.props;
const loop = data =>
data.map(item => {
if (item.children && item.children.length) {
return (
<TreeNode key={item.key} title={<div className="title-box"><div><Icon type="desktop" style={{marginRight: 5}}/>{item.title}</div><Icon type="caret-down" /></div>}>
{loop(item.children)}
</TreeNode>
);
}
return <TreeNode key={item.key}
title={<div className="title-box title-box-item"
onMouseOver={() => this.iconLFocus(item.key, true)}
onMouseLeave={() => this.iconLFocus(item.key)}>
{item.title}
{(activeFocusKeys === item.key && listTag) ?
<Icon type="more" className="title-box-icon" onMouseOver={()=>this.listFocus(item.key, true)}/>
: null}
{(activeListFocusKeys === item.key && listTag) ?
<TreeList list={treeDialog} key={item.key} listFocus={this.listFocus} listStyle={listStyle} listItemStyle={listItemStyle}/>
: null}
</div>} />
});
return (
<Tree
className="draggable-tree"
showIcon={true}
expandedKeys={this.state.expandedKeys}
onSelect={this.onSelect}
onExpand={this.onExpend}
draggable
blockNode
onDragEnter={this.onDragEnter}
onDrop={this.onDrop}
>
{loop(this.state.gData)}
</Tree>
);
}
}
function TreeList(props) {
const {list, key, listFocus, listStyle, listItemStyle} = props;
const mouseOverFunc = function(event) {
event.stopPropagation();
}
const treeItemClick = function(event, func, key) {
event.stopPropagation();
event.preventDefault();
func(key);
}
return (
<div className="tree-list-dialog-mask" onMouseOver={mouseOverFunc} onMouseLeave={() => listFocus(key, false)}>
<div className="tree-list-dialog" style={listStyle} >
{list.map(t=>(
<div className="tree-list-dialog-list" style={listItemStyle} onClick={(event) => treeItemClick(event, t.handleFunc, key)}>{t.title}</div>
))}
</div>
</div>
)
}
ReactDOM.render(<Demo />, mountNode);