You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
14 KiB
JavaScript
447 lines
14 KiB
JavaScript
|
3 months ago
|
import React, {PureComponent} from 'react';
|
||
|
|
import {Input, message, Tree, Modal} from 'antd';
|
||
|
|
import {DeleteOutlined, EditOutlined, FileOutlined, PlusCircleOutlined, ExclamationCircleOutlined} from '@ant-design/icons';
|
||
|
|
import {contextMenu, Item, Menu} from 'react-contexify';
|
||
|
|
import 'react-contexify/dist/ReactContexify.min.css';
|
||
|
|
import {getNodeByKeyAndCallbackProcess} from '@/components/_utils/algorithmTools';
|
||
|
|
import {connect} from '@umijs/max';
|
||
|
|
|
||
|
|
const {Search} = Input;
|
||
|
|
|
||
|
|
@connect(({globaldata, loading}) => ({
|
||
|
|
globaldata,
|
||
|
|
loading: loading.models.globaldata,
|
||
|
|
}))
|
||
|
|
class AsyncTree extends PureComponent {
|
||
|
|
state = {
|
||
|
|
expandedKeys: ['000000000000'],
|
||
|
|
autoExpandParent: true,
|
||
|
|
selectedKeys: [],
|
||
|
|
treeData: [],
|
||
|
|
selectedTreeByRightClick: "",
|
||
|
|
searchValue: '',
|
||
|
|
dataList: [],
|
||
|
|
display: "block",
|
||
|
|
};
|
||
|
|
|
||
|
|
constructor(props) {
|
||
|
|
super(props);
|
||
|
|
this.state.treeData = [
|
||
|
|
{title: props.rootNodeName, id: '000000000000', key: '000000000000', value: '000000000000', levelcode: "000000000000"},
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
componentDidMount() {
|
||
|
|
const {dispatch, getTreeMethod, reQueryParent} = this.props;
|
||
|
|
if(!reQueryParent) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
dispatch({
|
||
|
|
type: 'globaldata/' + getTreeMethod,
|
||
|
|
payload: {
|
||
|
|
parentid: "#"
|
||
|
|
},
|
||
|
|
callback: (res) => {
|
||
|
|
console.log(res)
|
||
|
|
if (res.success == true && res?.list.length > 0) {
|
||
|
|
const temList = res.list?.map((item) => {
|
||
|
|
return {
|
||
|
|
key: item.id,
|
||
|
|
...item
|
||
|
|
}
|
||
|
|
})
|
||
|
|
this.setState({
|
||
|
|
treeData: temList
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
//展开收起
|
||
|
|
onExpand = expandedKeys => {
|
||
|
|
this.setState({
|
||
|
|
expandedKeys,
|
||
|
|
autoExpandParent: false,
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
// 选中的树节点
|
||
|
|
onSelect = (selectedKeys, {node}) => {
|
||
|
|
const {checkedTreeChild} = this.props;
|
||
|
|
this.setState({selectedKeys});
|
||
|
|
if(checkedTreeChild) {
|
||
|
|
checkedTreeChild(node);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
addMenuTreeNode = () => {
|
||
|
|
const {handleModalVisible} = this.props;
|
||
|
|
handleModalVisible(true, this.state.selectedTreeByRightClick, this.reLoadCurrentTreeNode);
|
||
|
|
};
|
||
|
|
|
||
|
|
updateMenuTreeNode = () => {
|
||
|
|
const {handleUpdateModalVisible} = this.props;
|
||
|
|
handleUpdateModalVisible(true, this.state.selectedTreeByRightClick, this.reLoadCurrentTreeNodeUpdate);
|
||
|
|
};
|
||
|
|
|
||
|
|
viewMenuTreeNode = () => {
|
||
|
|
const {handleViewModalVisible} = this.props;
|
||
|
|
handleViewModalVisible(true, this.state.selectedTreeByRightClick);
|
||
|
|
};
|
||
|
|
|
||
|
|
deleteMenuTreeNode = () => {
|
||
|
|
const {handleDeleteRecord} = this.props;
|
||
|
|
Modal.confirm({
|
||
|
|
title: '你确定要删除吗?',
|
||
|
|
icon: <ExclamationCircleOutlined />,
|
||
|
|
content: '删除该节点后其子节点也会删除!这并不会删除该节点及其子节点的的关联数据',
|
||
|
|
okText: '确认',
|
||
|
|
cancelText: '取消',
|
||
|
|
onOk: () => {
|
||
|
|
handleDeleteRecord(this.state.selectedTreeByRightClick, this.reLoadCurrentTreeNodeDelete)
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
// 响应右键点击
|
||
|
|
rightClickTreeNode = ({event, node}) => {
|
||
|
|
event.preventDefault();
|
||
|
|
contextMenu.show({
|
||
|
|
id: 'menu_id',
|
||
|
|
event: event,
|
||
|
|
props: {
|
||
|
|
foo: 'bar'
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
if (node.key != '000000000000') {
|
||
|
|
this.setState({
|
||
|
|
selectedTreeByRightClick: node,
|
||
|
|
display: "block",
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
this.setState({
|
||
|
|
selectedTreeByRightClick: node,
|
||
|
|
display: "none",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 右键菜单
|
||
|
|
MyAwesomeMenu = () => (
|
||
|
|
<Menu id='menu_id' theme="light" style={{zIndex: 1000}}>
|
||
|
|
<Item onClick={this.addMenuTreeNode}>
|
||
|
|
<PlusCircleOutlined style={{fontSize: 16, paddingTop: 3}}/>
|
||
|
|
<span style={{paddingLeft: 10}}>添加</span>
|
||
|
|
</Item>
|
||
|
|
<Item onClick={this.updateMenuTreeNode} style={{display: this.state.display}}>
|
||
|
|
<EditOutlined style={{fontSize: 16, paddingTop: 3}}/>
|
||
|
|
<span style={{paddingLeft: 10}}>修改</span>
|
||
|
|
</Item>
|
||
|
|
<Item onClick={this.viewMenuTreeNode} style={{display: this.state.display}}>
|
||
|
|
<FileOutlined style={{fontSize: 16, paddingTop: 3}}/>
|
||
|
|
<span style={{paddingLeft: 10}}>查看</span>
|
||
|
|
</Item>
|
||
|
|
<Item onClick={this.deleteMenuTreeNode} style={{display: this.state.display}}>
|
||
|
|
<DeleteOutlined style={{fontSize: 16, paddingTop: 3}}/>
|
||
|
|
<span style={{paddingLeft: 10}}>删除</span>
|
||
|
|
</Item>
|
||
|
|
</Menu>
|
||
|
|
);
|
||
|
|
|
||
|
|
// 组装树数据
|
||
|
|
updateTreeData(list, key, children) {
|
||
|
|
return list.map(node => {
|
||
|
|
if (node.key === key) {
|
||
|
|
return {
|
||
|
|
...node,
|
||
|
|
children,
|
||
|
|
};
|
||
|
|
} else if (node.children) {
|
||
|
|
return {
|
||
|
|
...node,
|
||
|
|
children: this.updateTreeData(node.children, key, children),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return node;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 异步加载树数据
|
||
|
|
* @param key
|
||
|
|
* @param children
|
||
|
|
* @returns {Promise<unknown>}
|
||
|
|
*/
|
||
|
|
onLoadData = ({key, children}) => {
|
||
|
|
const {dispatch, getTreeMethod} = this.props;
|
||
|
|
|
||
|
|
const params = {
|
||
|
|
parentid: key
|
||
|
|
};
|
||
|
|
|
||
|
|
return new Promise((resolve) => {
|
||
|
|
// if (children) {
|
||
|
|
// resolve();
|
||
|
|
// return;
|
||
|
|
// }
|
||
|
|
dispatch({
|
||
|
|
type: 'globaldata/' + getTreeMethod,
|
||
|
|
payload: params,
|
||
|
|
callback: (res) => {
|
||
|
|
if (res.success == true) {
|
||
|
|
const temList = res.list?.map((item) => {
|
||
|
|
return {
|
||
|
|
key: item.id,
|
||
|
|
...item
|
||
|
|
}
|
||
|
|
})
|
||
|
|
this.setState({
|
||
|
|
treeData: this.updateTreeData(this.state.treeData, key, temList)
|
||
|
|
});
|
||
|
|
}
|
||
|
|
resolve();
|
||
|
|
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
reLoadCurrentTreeNode = (treeNode, dropKey) => {
|
||
|
|
const data = [...this.state.treeData];
|
||
|
|
treeNode.key = treeNode.id;
|
||
|
|
getNodeByKeyAndCallbackProcess(data, dropKey, (item) => {
|
||
|
|
item.children = item.children || [];
|
||
|
|
// where to insert 示例添加到尾部,可以是随意位置
|
||
|
|
item.children.push(treeNode);
|
||
|
|
});
|
||
|
|
|
||
|
|
this.setState({
|
||
|
|
treeData: data,
|
||
|
|
});
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
reLoadCurrentTreeNodeUpdate = (treeNode, currentTreeNodeKey, parentTreeNodeKey) => {
|
||
|
|
const data = [...this.state.treeData];
|
||
|
|
|
||
|
|
getNodeByKeyAndCallbackProcess(data, parentTreeNodeKey, (item) => {
|
||
|
|
item.children = item.children || {};
|
||
|
|
item.children = item.children.map((item, key) => item.key == currentTreeNodeKey ? {...item, ...treeNode} : item)
|
||
|
|
// item.children.push(treeNode);
|
||
|
|
});
|
||
|
|
this.setState({
|
||
|
|
treeData: data,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
reLoadCurrentTreeNodeDelete = (currentTreeNodeKey) => {
|
||
|
|
|
||
|
|
const data = [...this.state.treeData];
|
||
|
|
getNodeByKeyAndCallbackProcess(data, currentTreeNodeKey, (item, index, arr) => {
|
||
|
|
arr.splice(index, 1);
|
||
|
|
});
|
||
|
|
|
||
|
|
this.setState({
|
||
|
|
treeData: data,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 搜索事件
|
||
|
|
onChange = e => {
|
||
|
|
const {treeData, dataList} = this.state;
|
||
|
|
const {value} = e.target;
|
||
|
|
const expandedKeys = dataList.map(item => {
|
||
|
|
if (item.title.indexOf(value) > -1) {
|
||
|
|
return this.getParentKey(item.title, treeData);
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}).filter((item, i, self) => item && self.indexOf(item) === i);
|
||
|
|
this.setState({
|
||
|
|
expandedKeys,
|
||
|
|
searchValue: value,
|
||
|
|
autoExpandParent: true,
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
getParentKey = (title, tree) => {
|
||
|
|
let parentKey;
|
||
|
|
for (let i = 0; i < tree.length; i++) {
|
||
|
|
const node = tree[i];
|
||
|
|
if (node.children) {
|
||
|
|
if (node.children.some(item => item.title === title)) {
|
||
|
|
parentKey = node.key;
|
||
|
|
} else if (this.getParentKey(title, node.children)) {
|
||
|
|
parentKey = this.getParentKey(title, node.children);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return parentKey;
|
||
|
|
};
|
||
|
|
|
||
|
|
generateList = data => {
|
||
|
|
|
||
|
|
if (undefined == data) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (let i = 0; i < data.length; i++) {
|
||
|
|
const node = data[i];
|
||
|
|
const {key, title} = node;
|
||
|
|
this.state.dataList.push({key, title: title});
|
||
|
|
if (node.children) {
|
||
|
|
this.generateList(node.children);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
onDrop = async info => {
|
||
|
|
const dropKey = info.node.key;
|
||
|
|
const dragKey = info.dragNode.key;
|
||
|
|
const dropPos = info.node.pos.split('-');
|
||
|
|
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
|
||
|
|
|
||
|
|
const source_parent_id = info.dragNode.parentid;
|
||
|
|
const source_levelcode = info.dragNode.levelcode;
|
||
|
|
let new_levelcode = "";
|
||
|
|
let dragKey_parent_id = "";
|
||
|
|
let sequences = "";
|
||
|
|
let sequencesArray = [];
|
||
|
|
|
||
|
|
const data = [...this.state.treeData];
|
||
|
|
|
||
|
|
// Find dragObject
|
||
|
|
let dragObj;
|
||
|
|
getNodeByKeyAndCallbackProcess(data, dragKey, (item, index, arr) => {
|
||
|
|
arr.splice(index, 1);
|
||
|
|
dragObj = item;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!info.dropToGap) {
|
||
|
|
let ar;
|
||
|
|
// Drop on the content 移动到未展开的节点里
|
||
|
|
getNodeByKeyAndCallbackProcess(data, dropKey, (item, index) => {
|
||
|
|
new_levelcode = item.levelcode + "/" + dragKey;
|
||
|
|
dragKey_parent_id = item.key;
|
||
|
|
ar = item.children || [];
|
||
|
|
|
||
|
|
dragObj.levelcode = new_levelcode;
|
||
|
|
dragObj.parentid = dragKey_parent_id;
|
||
|
|
item.children = item.children || [];
|
||
|
|
sequences = item.children.length;
|
||
|
|
// where to insert 示例添加到尾部,可以是随意位置 push
|
||
|
|
item.children.unshift(dragObj);
|
||
|
|
});
|
||
|
|
ar.forEach((item, index) => {
|
||
|
|
let arrs = {
|
||
|
|
id: item.key,
|
||
|
|
sequences: index
|
||
|
|
}
|
||
|
|
sequencesArray.push(arrs);
|
||
|
|
})
|
||
|
|
} else if (
|
||
|
|
(info.node.props.children || []).length > 0 && // Has children
|
||
|
|
info.node.props.expanded && // Is expanded
|
||
|
|
dropPosition === 1 // On the bottom gap
|
||
|
|
) {
|
||
|
|
getNodeByKeyAndCallbackProcess(data, dropKey, (item) => {
|
||
|
|
new_levelcode = item.levelcode + "/" + dragKey;
|
||
|
|
dragKey_parent_id = item.key
|
||
|
|
|
||
|
|
dragObj.levelcode = new_levelcode;
|
||
|
|
dragObj.parentid = dragKey_parent_id;
|
||
|
|
item.children = item.children || [];
|
||
|
|
item.children.unshift(dragObj);
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
let ar;
|
||
|
|
let i;
|
||
|
|
getNodeByKeyAndCallbackProcess(data, dropKey, (item, index, arr) => {
|
||
|
|
new_levelcode = item.levelcode.replace(item.key, "") + dragKey;
|
||
|
|
dragKey_parent_id = item.parentid
|
||
|
|
|
||
|
|
dragObj.levelcode = new_levelcode;
|
||
|
|
dragObj.parentid = dragKey_parent_id;
|
||
|
|
ar = arr;
|
||
|
|
i = index;
|
||
|
|
});
|
||
|
|
if (dropPosition === -1) {
|
||
|
|
sequences = i;
|
||
|
|
ar.splice(i, 0, dragObj);
|
||
|
|
} else {
|
||
|
|
sequences = i + 1;
|
||
|
|
ar.splice(i + 1, 0, dragObj);
|
||
|
|
}
|
||
|
|
ar.forEach((item, index, arr) => {
|
||
|
|
if(sequences <= index) {
|
||
|
|
let arrs = {
|
||
|
|
id: item.key,
|
||
|
|
sequences: index
|
||
|
|
}
|
||
|
|
sequencesArray.push(arrs);
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const {dispatch, updateTreeNodeByDragUrl} = this.props;
|
||
|
|
|
||
|
|
await dispatch({
|
||
|
|
type: 'globaldata/' + updateTreeNodeByDragUrl,
|
||
|
|
payload: {
|
||
|
|
source_id: dragKey,
|
||
|
|
source_parentid: source_parent_id,
|
||
|
|
source_levelcode: source_levelcode,
|
||
|
|
target_id: dragKey_parent_id,
|
||
|
|
levelcode: new_levelcode,
|
||
|
|
sequences: sequences,
|
||
|
|
sequencesArray: sequencesArray
|
||
|
|
},
|
||
|
|
callback: async (res) => {
|
||
|
|
if(res.success == true) {
|
||
|
|
message.success('修改成功');
|
||
|
|
await this.setState({
|
||
|
|
treeData: data,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
render() {
|
||
|
|
const { height, modify } = this.props;
|
||
|
|
const {autoExpandParent, expandedKeys, selectedKeys, treeData} = this.state;
|
||
|
|
this.generateList(treeData);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div style={{float: 'left', width: '30%'}}>
|
||
|
|
<Search style={{marginBottom: 8, width: '100%'}} placeholder={'请输入'}
|
||
|
|
onChange={this.onChange}/>
|
||
|
|
<Tree
|
||
|
|
loadData={this.onLoadData}
|
||
|
|
onExpand={this.onExpand}
|
||
|
|
expandedKeys={expandedKeys}
|
||
|
|
autoExpandParent={autoExpandParent}
|
||
|
|
onSelect={this.onSelect}
|
||
|
|
selectedKeys={selectedKeys}
|
||
|
|
onRightClick={this.rightClickTreeNode}
|
||
|
|
draggable={modify}
|
||
|
|
onDrop={this.onDrop}
|
||
|
|
treeData={treeData}
|
||
|
|
height={height || 700}
|
||
|
|
>
|
||
|
|
</Tree>
|
||
|
|
{modify ? this.MyAwesomeMenu() : null}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export default AsyncTree;
|