- 浏览: 162877 次
- 性别:
- 来自: 西安
-
文章分类
最新评论
EXT TREE CHECKBOX 用法
在Faceye基础版(开源)中,使用了大量的树结构,比如台台管理树,用户RSS订阅及分类树,用户博客分类对,网址导航分类树,开源项目分类树,博客管理树等大量的树形结构,甚至在用户授权,用户分组,资料分类等也都使用了树结构,树结构的处理,在Faceye中相比比较成熟,这些,是基于前期项目中对Struts-menu,Dtree等处理
关于Struts-Menu,Dtree的处理,在我早期的博客中都有描述.
带有复选框的树,在实际的项目中,特别是授权结构中,有大量的使用.我们来看一下Faceye中如何处理这种带复选框的树形结构.
首先,Faceye使用了Ext的树解决方案,所以,所有的数据结构,均转化为Ext中树解决方案的数据结构.在这里,我们可以很方便的把这一结构转化为其它树支持的数据结构,比如Strus-menu,dtreee等.
下面是对树完整的脚本处理代码.
[code]
Ext.BLANK_IMAGE_URL = 'scripts/ext/resources/images/vista/s.gif';
com.faceye.compoents.core.security.RoleVisiteModelPermission={
init:function(roleId){
Ext.QuickTips.init();
if(Ext.getCmp('tree')){
Ext.get('tree-viewer').remove(true);
// Ext.getCmp('tree').destroy(this);
Ext.getBody().createChild({tag:'div',id:'tree-viewer'});
}
var tree;
var root;
// if(!tree){
var Tree = Ext.tree;
tree = new Ext.tree.TreePanel(
{
el:'tree-viewer',
id:'tree',
autoScroll:true,
animate:true,
border:false,
enableDD:true,
containerScroll: true,
rootVisible:false,
loader: new Ext.tree.TreeLoader({
dataUrl:'/faceye/treeAction.do?method=treeWithCheckBoxForPermission&roleId='+roleId
// uiProviders:{
// 'col': Ext.ux.CheckColumnNodeUI
// }
})
// columns:[{
//// header:'node',
// dataIndex:'name'
// }]
//
}
);
//Defint node click event,when node is clicked,send data to inner 'div' and show data in
// set the root node
root = new Tree.AsyncTreeNode({
text: 'Common Platform',
draggable:false,
id:'source',
checked:true
// uiProvider: Ext.tree.CheckboxNodeUI
});
tree.setRootNode(root);
//全部展开
tree.expandAll();
tree.on('checkchange',function(node){
if(!node.isLeaf()){
node.toggle();
}
fireCheckChange(node);
// node.cascade(function(n){
// n.getUI().toggleCheck();
// });
});
function fireCheckChange(node){
if(node.getUI().isChecked()){
checkedChildrenNodes(node);
checkedParentNodes(node);
}else{
//取得当前节点的所有子节点,包括当前节点
var allChildrenNodes=getAllChildrenNodes(node);
//如果当前节点的所有子节点中,不存在checked=true的节点,那么将当前节点置为checked=false.
//
//如果当前节点有子节点,同时,当前节点checked=false,那么将其所有子节点置为checked=false
for(var i=0;i<allChildrenNodes.length;i++){
if(allChildrenNodes[i].getUI().isChecked()){
allChildrenNodes[i].getUI().toggleCheck();
}
}
unCheckedParentNode(node);
}
}
function checkedChildrenNodes(node){
//取得本节点的所有子节点,子节点中包括其自己
var allChildrenNodes=getAllChildrenNodes(node);
if(allChildrenNodes.length>1){
for(var i=0;i<allChildrenNodes.length;i++){
if(!allChildrenNodes[i].getUI().isChecked()){
allChildrenNodes[i].getUI().toggleCheck();
}
}
}
}
function unCheckedParentNode(currentChildNode){
if(currentChildNode.parentNode){
var parentNode=currentChildNode.parentNode;
//取得本父节点下所有被选中的子节点
//包括本父节点本身
var allCheckedChildrenNodes=getCheckedNodes(parentNode);
if(allCheckedChildrenNodes.length === 1){
parentNode.getUI().toggleCheck();
parentNode.attributes.checked=false;
}
if(parentNode.parentNode){
unCheckedParentNode(parentNode);
}
}
}
function checkedParentNodes(node){
//取得本节点的所有父节点,父节点中包括其自己
var allParentNodes=getAllParentNodes(node);
if(allParentNodes.length>1){
for(var i=0;i<allParentNodes.length;i++){
if(!allParentNodes[i].getUI().isChecked()){
allParentNodes[i].getUI().toggleCheck();
}
}
}
}
function getCheckedNodesId(node){
var checked = [];
if( node.getUI().isChecked() || node.attributes.checked ) {
// alert('dfdf'+node.childNodes.length);
checked.push(node.id);
if( !node.isLeaf() ) {
for(var i = 0; i < node.childNodes.length; i++ ) {
checked = checked.concat( getCheckedNodesId(node.childNodes[i]) );
}
}
}
return checked;
};
function getCheckedNodes(node){
var checked = [];
if( node.getUI().isChecked() ) {
checked.push(node);
if( !node.isLeaf() ) {
for(var i = 0; i < node.childNodes.length; i++ ) {
checked = checked.concat( getCheckedNodes(node.childNodes[i]) );
}
}
}
return checked;
};
function getAllChildrenNodes(node){
var children = [];
children.push(node);
if(!node.isLeaf()){
for(var i=0;i<node.childNodes.length;i++){
children = children.concat(getAllChildrenNodes(node.childNodes[i]));
}
}
return children;
};
function getAllParentNodes(node){
var parentNodes=[];
parentNodes.push(node);
if(node.parentNode){
parentNodes = parentNodes.concat(getAllParentNodes(node.parentNode));
}
return parentNodes;
};
function getAllChecked(){
return getCheckedNodesId(root);
}
tree.on('click', function (node){
if(node.isLeaf()){
// Ext.get('content-iframe').dom.src = node.attributes.link+'&node='+node.id;
//define grid;
return true;
}else{
node.toggle();
}
});
tree.on('dblclick',function(node){
if(node.isLeaf()){
return true;
}else{
node.toggle();
fireCheckChange(node);
// this.fireEvent('checkchange', this.node, true);
}
});
// render the tree
// }
tree.render();
root.expand();
var win=new Ext.Window({
layout:'fit',
//模式窗口
modal:true,
width:450,
height:300,
closeAction:'hide',
plain: true,
// margins:'5 5 5 5',
title:'为角色授权',
buttonAlign:'center',
buttons: [{
text:'确定',
scope:com.faceye.compoents.core.security.SelectRoles,
type:'submit',
disabled:false,
handler:function(){
var checked=getAllChecked().join(',');
Ext.Ajax.request({
url:'/faceye/roleAction.do?method=permission',
failure:function(){
Ext.Msg.alert('角色授权','角色授权失败!');
win.hide();
},
success:function(){
Ext.Msg.alert('角色授权','角色授权成功!');
win.hide();
},
params:{
treeIds:checked,
roleId:roleId
}
});
}
},{
text: '放弃',
handler: function(){
// formItemSelector.getForm().reset();
if(win.getComponent('tree')){
// Ext.get('tree-viewer').remove();
// win.destroy();
// win.remove(tree);
}
win.hide();
// win.disable();
// tree.disable();
}
}
]
});
win.add(tree);
win.show();
}
};
在带有复选框的树形结构处理中,主要需要解决这么几个问题:
1.我们的树,是无限级别的
2.当选中父节点时,其所有子节点,包括直接子节点和间接子节点,一并选中
3.当一个节点的所有子节点被取消选中状态时,其父节点要处于未被选中的状态.
因为树向来是子子孙孙无穷尽的一个东西,所以在这里,需要用到比较多的递归处理,不管是在脚本方面,还是在后台代码里,都需要用到一些递归处理,比如我们所提供的方法中:
可以看到,我们在这里,加入了:
关于Struts-Menu,Dtree的处理,在我早期的博客中都有描述.
带有复选框的树,在实际的项目中,特别是授权结构中,有大量的使用.我们来看一下Faceye中如何处理这种带复选框的树形结构.
首先,Faceye使用了Ext的树解决方案,所以,所有的数据结构,均转化为Ext中树解决方案的数据结构.在这里,我们可以很方便的把这一结构转化为其它树支持的数据结构,比如Strus-menu,dtreee等.
下面是对树完整的脚本处理代码.
[code]
Ext.BLANK_IMAGE_URL = 'scripts/ext/resources/images/vista/s.gif';
com.faceye.compoents.core.security.RoleVisiteModelPermission={
init:function(roleId){
Ext.QuickTips.init();
if(Ext.getCmp('tree')){
Ext.get('tree-viewer').remove(true);
// Ext.getCmp('tree').destroy(this);
Ext.getBody().createChild({tag:'div',id:'tree-viewer'});
}
var tree;
var root;
// if(!tree){
var Tree = Ext.tree;
tree = new Ext.tree.TreePanel(
{
el:'tree-viewer',
id:'tree',
autoScroll:true,
animate:true,
border:false,
enableDD:true,
containerScroll: true,
rootVisible:false,
loader: new Ext.tree.TreeLoader({
dataUrl:'/faceye/treeAction.do?method=treeWithCheckBoxForPermission&roleId='+roleId
// uiProviders:{
// 'col': Ext.ux.CheckColumnNodeUI
// }
})
// columns:[{
//// header:'node',
// dataIndex:'name'
// }]
//
}
);
//Defint node click event,when node is clicked,send data to inner 'div' and show data in
// set the root node
root = new Tree.AsyncTreeNode({
text: 'Common Platform',
draggable:false,
id:'source',
checked:true
// uiProvider: Ext.tree.CheckboxNodeUI
});
tree.setRootNode(root);
//全部展开
tree.expandAll();
tree.on('checkchange',function(node){
if(!node.isLeaf()){
node.toggle();
}
fireCheckChange(node);
// node.cascade(function(n){
// n.getUI().toggleCheck();
// });
});
function fireCheckChange(node){
if(node.getUI().isChecked()){
checkedChildrenNodes(node);
checkedParentNodes(node);
}else{
//取得当前节点的所有子节点,包括当前节点
var allChildrenNodes=getAllChildrenNodes(node);
//如果当前节点的所有子节点中,不存在checked=true的节点,那么将当前节点置为checked=false.
//
//如果当前节点有子节点,同时,当前节点checked=false,那么将其所有子节点置为checked=false
for(var i=0;i<allChildrenNodes.length;i++){
if(allChildrenNodes[i].getUI().isChecked()){
allChildrenNodes[i].getUI().toggleCheck();
}
}
unCheckedParentNode(node);
}
}
function checkedChildrenNodes(node){
//取得本节点的所有子节点,子节点中包括其自己
var allChildrenNodes=getAllChildrenNodes(node);
if(allChildrenNodes.length>1){
for(var i=0;i<allChildrenNodes.length;i++){
if(!allChildrenNodes[i].getUI().isChecked()){
allChildrenNodes[i].getUI().toggleCheck();
}
}
}
}
function unCheckedParentNode(currentChildNode){
if(currentChildNode.parentNode){
var parentNode=currentChildNode.parentNode;
//取得本父节点下所有被选中的子节点
//包括本父节点本身
var allCheckedChildrenNodes=getCheckedNodes(parentNode);
if(allCheckedChildrenNodes.length === 1){
parentNode.getUI().toggleCheck();
parentNode.attributes.checked=false;
}
if(parentNode.parentNode){
unCheckedParentNode(parentNode);
}
}
}
function checkedParentNodes(node){
//取得本节点的所有父节点,父节点中包括其自己
var allParentNodes=getAllParentNodes(node);
if(allParentNodes.length>1){
for(var i=0;i<allParentNodes.length;i++){
if(!allParentNodes[i].getUI().isChecked()){
allParentNodes[i].getUI().toggleCheck();
}
}
}
}
function getCheckedNodesId(node){
var checked = [];
if( node.getUI().isChecked() || node.attributes.checked ) {
// alert('dfdf'+node.childNodes.length);
checked.push(node.id);
if( !node.isLeaf() ) {
for(var i = 0; i < node.childNodes.length; i++ ) {
checked = checked.concat( getCheckedNodesId(node.childNodes[i]) );
}
}
}
return checked;
};
function getCheckedNodes(node){
var checked = [];
if( node.getUI().isChecked() ) {
checked.push(node);
if( !node.isLeaf() ) {
for(var i = 0; i < node.childNodes.length; i++ ) {
checked = checked.concat( getCheckedNodes(node.childNodes[i]) );
}
}
}
return checked;
};
function getAllChildrenNodes(node){
var children = [];
children.push(node);
if(!node.isLeaf()){
for(var i=0;i<node.childNodes.length;i++){
children = children.concat(getAllChildrenNodes(node.childNodes[i]));
}
}
return children;
};
function getAllParentNodes(node){
var parentNodes=[];
parentNodes.push(node);
if(node.parentNode){
parentNodes = parentNodes.concat(getAllParentNodes(node.parentNode));
}
return parentNodes;
};
function getAllChecked(){
return getCheckedNodesId(root);
}
tree.on('click', function (node){
if(node.isLeaf()){
// Ext.get('content-iframe').dom.src = node.attributes.link+'&node='+node.id;
//define grid;
return true;
}else{
node.toggle();
}
});
tree.on('dblclick',function(node){
if(node.isLeaf()){
return true;
}else{
node.toggle();
fireCheckChange(node);
// this.fireEvent('checkchange', this.node, true);
}
});
// render the tree
// }
tree.render();
root.expand();
var win=new Ext.Window({
layout:'fit',
//模式窗口
modal:true,
width:450,
height:300,
closeAction:'hide',
plain: true,
// margins:'5 5 5 5',
title:'为角色授权',
buttonAlign:'center',
buttons: [{
text:'确定',
scope:com.faceye.compoents.core.security.SelectRoles,
type:'submit',
disabled:false,
handler:function(){
var checked=getAllChecked().join(',');
Ext.Ajax.request({
url:'/faceye/roleAction.do?method=permission',
failure:function(){
Ext.Msg.alert('角色授权','角色授权失败!');
win.hide();
},
success:function(){
Ext.Msg.alert('角色授权','角色授权成功!');
win.hide();
},
params:{
treeIds:checked,
roleId:roleId
}
});
}
},{
text: '放弃',
handler: function(){
// formItemSelector.getForm().reset();
if(win.getComponent('tree')){
// Ext.get('tree-viewer').remove();
// win.destroy();
// win.remove(tree);
}
win.hide();
// win.disable();
// tree.disable();
}
}
]
});
win.add(tree);
win.show();
}
};
在带有复选框的树形结构处理中,主要需要解决这么几个问题:
1.我们的树,是无限级别的
2.当选中父节点时,其所有子节点,包括直接子节点和间接子节点,一并选中
3.当一个节点的所有子节点被取消选中状态时,其父节点要处于未被选中的状态.
因为树向来是子子孙孙无穷尽的一个东西,所以在这里,需要用到比较多的递归处理,不管是在脚本方面,还是在后台代码里,都需要用到一些递归处理,比如我们所提供的方法中:
function getAllParentNodes(node){ var parentNodes=[]; parentNodes.push(node); if(node.parentNode){ parentNodes = parentNodes.concat(getAllParentNodes(node.parentNode)); } return parentNodes; }; 在这里,提供了这样一样功能,既:取得当前点击(选中)节点的所有父节点,这些父节点中,包括直接父节点和间接父节点,也就是说,在这里,会取得当前选中节点的父亲节点,父亲节点的父亲节点....一直到根节点为止. 类似的方法有:function getAllChildrenNodes(node){ var children = []; children.push(node); if(!node.isLeaf()){ for(var i=0;i<node.childNodes.length;i++){ children = children.concat(getAllChildrenNodes(node.childNodes[i])); } } return children; }; 这是取得一个节点所有子节点的方法.处理跟上面类似. 通过以上这些处理,我们就可以很方便的处理带复选框的树形结构. 而在这里,我们最终真正关心的,是那些被我们选中的节点,于是,我们需要取得所有被选中的节点,在这里我们需要使用另外一个方法: [code] function getCheckedNodes(node){ var checked = []; if( node.getUI().isChecked() ) { checked.push(node); if( !node.isLeaf() ) { for(var i = 0; i < node.childNodes.length; i++ ) { checked = checked.concat( getCheckedNodes(node.childNodes[i]) ); } } } return checked; }; 在这里,我们同样使用到了递归. 同时,我们可以看到,在生成树的时候,我们使用了这样一个loader: [code] loader: new Ext.tree.TreeLoader({ dataUrl:'/faceye/treeAction.do?method=treeWithCheckBoxForPermission&roleId='+roleId // uiProviders:{ // 'col': Ext.ux.CheckColumnNodeUI // } }) 这是我们从后台取数据的方法,同时,要在前台加入check column 要加入check column,我们需要看以下代码: 在方法:treeWithCheckBoxForPermission中,我们做了以下操作: [code] public ActionForward treeWithCheckBoxForPermission(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { String json = ""; // JSONArray ja=new JSONArray(); String roleId=this.getHttp().getParameter(request, "roleId"); if (super.getHttp().getParameter(request, "node").equals("source")) { json = this.getTreeService().treeJSONWithCheckBox(roleId); } else { String currentNode = super.getHttp().getParameter(request, "node"); json = this.getTreeService().treeJSONWithCheckBox( this.getTreeService().getTransedTrees(), currentNode,roleId); } super.jsonPrint(response, json); // return mapping.findForward("system.admin.face"); return null; } 在这里,我们主要对原始的树形结构进行了处理,在每个节点上面,打上了checkbox标识:public String treeJSONWithCheckBox(Serializable roleId) { // TODO Auto-generated method stub try { List trees = this.getTransedTrees(); String result = this.treeJSONWithCheckBox(trees, roleId); return result; } catch (Exception e) { log.info(">>>>faceye error in method:treeJSON() is" + e.toString()); return null; } }
这里最主要的一个方法是:public String treeJSONWithCheckBox(List source, Serializable roleId) { // TODO Auto-generated method stub if (source == null || source.isEmpty()) { return null; } List roots = this.getRoots(source); Iterator it = roots.iterator(); StringBuffer json = new StringBuffer(); json.append("["); while (it.hasNext()) { Map item = (Map) it.next(); json.append(this.transTree2JSONWithCheckBox(source, item, roleId)); json.append(","); } json.deleteCharAt(json.lastIndexOf(",")); json.append("]"); return json.toString(); }
在这里,我们调用了最终方法:
[code]
transTree2JSONWithCheckBox
对树结构进行最终处理
[code]
private String transTree2JSONWithCheckBox(List source, Map tree,
Serializable roleId) {
StringBuffer json = new StringBuffer();
json.append("{");
json.append(""text":");
json.append(""");
json.append(tree.get(StringPool.TREE_NAME).toString());
json.append(""");
json.append(",");
json.append(""id":");
json.append(""");
json.append(tree.get(StringPool.TREE_ID).toString());
json.append(""");
json.append(",");
json.append(""leaf":");
if (this
.isHaveChildren(source, tree.get(StringPool.TREE_ID).toString())) {
json.append("false");
} else {
json.append("true");
}
json.append(",");
json.append(""cls":");
json.append(""file"");
if (tree.containsKey(StringPool.TREE_ACTION)
&& null != tree.get(StringPool.TREE_ACTION)) {
json.append(",");
json.append(""link":");
json.append(""");
json.append(tree.get(StringPool.TREE_ACTION).toString());
json.append(""");
} else {
json.append(",");
json.append(""link":");
json.append(""");
json.append("#");
json.append(""");
}
json.append(",");
json.append(""checked":");
// json.append("true");
json.append(this.isNodeChecked(tree.get(StringPool.TREE_ID).toString(),
roleId));
json.append("}");
return json.toString();
}
可以看到,我们在这里,加入了:
checked:true这样的属性.
相关推荐
EXT TREE扩展CHECKBOX JS是一种在EXT JS框架下对树形组件(Tree Panel)进行增强,实现复选框功能的技术。EXT JS是一个强大的JavaScript GUI库,它提供了丰富的组件和功能,用于构建复杂的Web应用程序。在EXT JS中,...
EXT Tree 是一个强大的JavaScript组件,它是EXT JS框架的一部分,用于构建可交互的树形数据结构。EXT Tree在网页中常用于展示...通过学习和实践这些实例,你可以更好地掌握EXT Tree的用法,并根据项目需求灵活应用。
url: 'tree_data.json', // 或者使用本地数据 reader: { type: 'json', root: 'nodes' } }, root: { text: 'Root Node', expanded: true }, fields: ['text', 'checked', {name: 'children', type: '...
例如,TreeField和CheckBoxTree等控件都是扩展自标准组件的功能。此外,Ext JS还可以与其他技术(如dwr、localXHR等)整合,增加更多的功能和灵活性。 #### 9. 常见问题解答 在Ext使用过程中,可能遇到各种问题,...
复选框的添加通常通过配置`checkbox`属性来完成。例如: ```javascript { text: '父节点', checked: false, children: [ { text: '子节点1', checked: false }, { text: '子节点2', checked: true } ] } ``` ...
Ext 3.0 是一个广泛使用的JavaScript框架,用于构建富客户端Web应用程序。它以其强大的组件化和数据绑定功能而闻名,使得开发复杂的交互式界面变得简单。这份"Ext3.0中文文档"提供了全面的指南,帮助开发者深入理解...
EXT树的核心类是`Ext.tree.TreePanel`,它提供了丰富的配置项和方法来定制和操作树形结构。控制选择意味着用户可以通过交互(如点击节点)来选择一个或多个树节点,而撤销功能则允许用户撤消他们的最后一步操作,...
使用`Ext.data.TreeStore`的`load`方法,可以实现按需加载子节点,提高页面性能。配置`proxy`为`Ext.data.proxy.Ajax`或`Ext.data.proxy.Rest`,配合服务器端接口实现异步数据获取。 6. **模板与渲染**: 可以...
掌握不同控件的使用方法对于构建有效的用户界面至关重要。 2. **布局管理(Layout Management)**:EXT支持多种布局方式,如卡片式布局、表格式布局等,合理的布局设计可以使用户界面更加美观和易于使用。 3. **...
var treePanel = Ext.create('Ext.tree.Panel', { title: 'Tree with Checkboxes', width: 200, height: 300, store: store, rootVisible: false, checkboxModel: { threeState: true }, viewConfig: { ...
对于子节点,我们使用`cascade`方法遍历其所有子节点,将它们的`attributes.checked`和`ui.checkbox.checked`属性设置为当前事件的状态(state)。这会确保每个子节点的复选框状态与父节点同步。 然后,我们处理父...
在处理全选和清空时,我们需要遍历TreePanel的节点,并使用`toggleCheck`方法改变CheckBox的视觉状态,同时更新节点的`checked`属性,以保持数据和视图的一致性。这样的设计允许用户在复杂的数据结构中方便地进行...
在ExtJS中,使用`Ext.tree.Panel`创建树。`rootVisible`属性控制是否显示根节点,`store`配置项与网格类似,用于绑定数据。树节点可以通过`children`属性定义子节点,或者通过异步加载数据。树视图还支持节点的展开/...
var tree = Ext.create('Ext.tree.Panel', { selModel: { type: 'checkbox', cascadeChanges: true // 这将使父节点的选中状态影响子节点 }, ... }); ``` 2. 加载数据源,可能使用了`check-nodes.json`这个...
在实际的开发过程中,还可以将这两种方法结合起来使用,比如在某些情况下,即使复选框存在,但通过`beforeselect`函数依然可以禁止选中,这样可以更好地满足复杂的业务逻辑需求。 需要注意的是,在实际开发中,一定...
1. **配置CheckboxTree**: 首先,我们需要创建一个Ext.tree.TreePanel,并为其配置CheckboxTree插件,这样每个节点都会显示复选框。通过设置`checkboxModel`属性,我们可以控制选中父节点时子节点的选中行为。 2. *...
本篇文章将聚焦于Ext JS中的一个特定功能——级联选择的树形控件,并探讨其扩展插件的使用方法。 在Ext JS中,树形控件(Tree)是一种常见的数据展示形式,它允许用户以层级结构查看和操作数据。级联选择...
- **概述**:`Ext` 是 ExtJS 的核心命名空间,包含了全局的方法和属性。 - **用途**:提供了一个统一的入口来访问 ExtJS 库的功能,如创建组件、管理事件等。 - **常用方法**: - `Ext.create()`: 创建一个组件实例...
4. **树形控件(Tree)配置项与方法**:`Ext tree配置项与方法.docx`会讲解如何创建和操作树形结构。内容可能包括节点的添加、删除、展开/折叠,以及自定义节点图标和行为。 5. **xtype列表**:`Extjs xtype列表....
这可以通过在`loader`配置中定义`load`方法实现,该方法会在节点展开时被调用,向服务器发送请求获取子节点数据,然后使用返回的数据动态添加子节点。 7. **优化性能**:为了提高性能,可以使用分页或懒加载技术,...