我想构建从服务器获取的数据,因此我可以使用
TreeView
中的 Material UI
组件:https://material-ui.com/api/tree-view/
我正在获取大量数据,因此我想在用户单击展开按钮时从服务器获取子节点。所以 当第一个节点展开时,HTTP 请求将发送到服务器,该服务器返回该节点的所有子节点。当另一个节点展开时,会获取该节点的子节点等。
在页面启动时,我想获取根节点及其子节点。返回的 JSON 看起来像这样:
{
"division": {
"id": "1234",
"name": "Teest",
"address": "Oslo"
},
"children": [
{
"id": "3321",
"parentId": "1234",
"name": "Marketing",
"address": "homestreet"
},
{
"id": "3323",
"parentId": "1234",
"name": "Development",
"address": "homestreet"
}
]
}
展开
Marketing
节点时,我想进行 HTTP 调用来获取该节点的子节点。所以我会得到这样的 JSON:
{
"children": [
{
"id": "2212",
"parentId": "3321",
"name": "R&D",
"address": "homestreet"
},
{
"id": "4212",
"parentId": "3321",
"name": "Testing",
"address": "homestreet"
}
]
}
但是我对如何创建这样一个数据结构感到困惑,该数据结构稍后可以在我的
TreeView
组件中使用。我怎样才能创建这样的结构?
对于仍在寻找此问题解决方案的人,我最近使用
TreeView API中的
selected
和 expanded
属性的组合解决了这个问题。请参阅此 Code Sandbox demo,了解如何异步加载新子项并在加载后展开其父项的示例。
import React from "react";
import TreeView from "@material-ui/lab/TreeView";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import TreeItem from "@material-ui/lab/TreeItem";
import TreeNode from "./TreeNode";
const mockApiCall = async () => {
return new Promise((resolve) => {
setTimeout(() => {
const nextId = Math.ceil(Math.random() * 100);
resolve([
{
id: `${nextId}`,
name: `child-${nextId}`,
children: []
},
{
id: `${nextId + 1}`,
name: `child-${nextId + 1}`,
children: []
}
]);
}, Math.ceil(Math.random() * 1000));
});
};
export default class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: [],
selected: "1",
tree: new TreeNode({
id: "1",
name: "src",
children: []
})
};
}
handleChange = async (event, nodeId) => {
const node = this.state.tree.search(nodeId);
if (node && !node.children.length) {
mockApiCall()
.then((result) => {
this.setState({ tree: this.state.tree.addChildren(result, nodeId) });
})
.catch((err) => console.error(err))
.finally(() => {
this.setState({
selected: nodeId,
expanded: [...this.state.expanded, nodeId]
});
});
}
};
createItemsFromTree = (tree) => {
if (tree.children.length) {
return (
<TreeItem key={tree.id} nodeId={tree.id} label={tree.name}>
{tree.children.length > 0 &&
tree.children.map((child) => this.createItemsFromTree(child))}
</TreeItem>
);
}
return <TreeItem key={tree.id} nodeId={tree.id} label={tree.name} />;
};
render() {
return (
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
selected={this.state.selected}
onNodeSelect={this.handleChange}
expanded={this.state.expanded}
>
{this.createItemsFromTree(this.state.tree)}
</TreeView>
);
}
}
<div class="container-fluid">
<div class="row mt-3">
<div class="col-lg-8">
<div class="card">
<div class="card-body">
<div id="tree">
<div class="branch">
<div class="entry main-entry"><span class="drop"><p-dropdown [options]="logicGates"
optionLabel="name" placeholder="Select" /></span>
<div class="branch">
<div class="entry">
<span class="drop">
<div class="card sub-card p-2">
<div class="row align-items-center">
<div class="col-lg-4">
<select name="" id="" class="form-select">
<option value="">Select Field</option>
</select>
</div>
<div class="col-lg-4">
<select name="" id="" class="form-select">
<option value="">Select Condition</option>
</select>
</div>
<div class="col-lg-3">
<input type="text" class="form-control"
placeholder="Field Text">
</div>
<div class="col-lg-1">
<button class="btn border-0"><i
class='bx bx-trash'></i></button>
</div>
</div>
</div>
</span>
</div>
<div class="entry">
<span class="drop">
<div class="card sub-card p-2">
<div class="row align-items-center">
<div class="col-lg-4">
<select name="" id="" class="form-select">
<option value="">Select Field</option>
</select>
</div>
<div class="col-lg-4">
<select name="" id="" class="form-select">
<option value="">Select Condition</option>
</select>
</div>
<div class="col-lg-3">
<input type="text" class="form-control"
placeholder="Field Text">
</div>
<div class="col-lg-1">
<button class="btn border-0"><i
class='bx bx-trash'></i></button>
</div>
</div>
</div>
</span>
</div>
</div>
<div class="margin">
<a href="">+ Add Condition</a>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer">
<button class="btn border-0 btn_foot me-3">
<i class='bx bx-plus-circle'></i> Add Rule
</button>
<button class="btn border-0 btn_foot">
<i class='bx bx-folder-plus'></i> Add Inner Group
</button>
</div>
</div>
</div>
</div>
</div>
#tree {
display: inline-block;
padding: 10px;
width: 100%;
}
#tree * {
box-sizing: border-box;
}
#tree .branch {
padding: 20px 0 5px 20px;
}
#tree .branch:not(:first-child) {
margin-left: 170px;
}
#tree .branch:not(:first-child):after {
content: "";
width: 20px;
border-top: 1px solid #ccc;
position: absolute;
left: 150px;
top: 50%;
margin-top: 1px;
}
.entry {
position: relative;
min-height: 100px;
display: block;
}
.entry:before {
content: "";
height: 100%;
border-left: 1px solid #ccc;
position: absolute;
left: -20px;
}
.entry:first-child:after {
height: 10px;
border-radius: 10px 0 0 0;
}
.entry:first-child:before {
width: 10px;
height: 50%;
top: 50%;
margin-top: 1px;
border-radius: 10px 0 0 0;
}
.entry:after {
content: "";
width: 20px;
transition: border 0.5s;
border-top: 1px solid #ccc;
position: absolute;
left: -20px;
top: 50%;
margin-top: 1px;
}
.entry:last-child:before {
width: 10px;
height: 50%;
border-radius: 0 0 0 10px;
}
.entry:last-child:after {
height: 10px;
border-top: none;
transition: border 0.5s;
border-bottom: 1px solid #ccc;
border-radius: 0 0 0 10px;
margin-top: -9px;
}
.entry:only-child:after {
width: 10px;
height: 0px;
margin-top: 1px;
border-radius: 0px;
}
.entry:only-child:before {
display: none;
}
.entry span {
border: 1px solid #ccc;
display: block;
min-width: 150px;
padding: 5px 10px;
line-height: 20px;
text-align: center;
position: absolute;
left: 0;
top: 50%;
margin-top: -15px;
color: #666;
font-family: arial, verdana, tahoma;
font-size: 14px;
display: inline-block;
border-radius: 5px;
transition: all 0.5s;
}
// #tree .entry span:hover,
// #tree .entry span:hover + .branch .entry span {
// background: #e6e6e6;
// color: #000;
// border-color: #a6a6a6;
// }
#tree .entry span:hover + .branch .entry::after,
#tree .entry span:hover + .branch .entry::before,
#tree .entry span:hover + .branch::before,
#tree .entry span:hover + .branch .branch::before {
border-color: #a6a6a6;
}
::ng-deep {
.p-dropdown {
border-radius: 20px;
width: 150px;
}
.p-inputtext {
padding: 0.35rem 0.45rem;
font-size: 14px;
}
.p-icon {
width: 0.5rem;
height: 0.5rem;
}
.p-dropdown-panel .p-dropdown-items {
padding-left: 0rem;
margin-bottom: 0rem;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-item {
padding: 0.55rem 0.75rem;
text-align: start;
}
}
div.main-entry {
.drop {
border: 0px;
padding: 0px;
}
}
.margin {
margin-left: 180px;
a {
text-decoration: none;
}
}
button.btn_foot {
background: #0367a51f;
color: #0367a5;
}