cs
public class ListToTree<T> where T : new()
{
/// <summary>
/// 将平铺列表转换为树形结构(标准版本)
/// </summary>
/// <param name="lists">原始平铺数据列表</param>
/// <param name="code">节点唯一标识字段名(如 "id")</param>
/// <param name="parentCode">父节点标识字段名(如 "parent")</param>
/// <param name="value">根节点的父节点值(通常为 "0" 或空字符串)</param>
/// <param name="childNodeName">子节点集合的字段名,默认为 "children"</param>
/// <returns>树形结构的列表</returns>
public static List<T> ListToTrees(List<T> lists, string code, string parentCode, string value, string childNodeName = "children")
{
if (lists.Count > 0)
{
var tempLists = lists
.Where(m => (string?)m?.GetType().GetProperty(parentCode)?.GetValue(m, null) == value).ToList();
if (tempLists.Count > 0)
{
var treeDatas = new List<T>();
T t;
Type? type;
foreach (var obj in tempLists)
{
t = obj;
type = obj?.GetType();
var childs = ListToTrees(lists, code, parentCode,
(string)(type?.GetProperty(code)?.GetValue(t, null) ?? ""), childNodeName);
if (childs.Count > 0)
{
type?.GetProperty(childNodeName)?.SetValue(t, childs, null);
}
treeDatas.Add(t);
}
return treeDatas;
}
}
return new List<T>();
}
/// <summary>
/// 将平铺列表转换为树形结构(支持孤儿节点作为独立根节点)
/// 当某个节点的父节点在列表中不存在时,该节点自身作为根节点保留在结果中
/// </summary>
/// <param name="lists">原始平铺数据列表</param>
/// <param name="code">节点唯一标识字段名(如 "id")</param>
/// <param name="parentCode">父节点标识字段名(如 "parent")</param>
/// <param name="value">根节点的父节点值(通常为 "0" 或空字符串)</param>
/// <param name="childNodeName">子节点集合的字段名,默认为 "children"</param>
/// <returns>树形结构的列表,包含所有节点(包括孤儿节点)</returns>
public static List<T> ListToTreesWithOrphan(List<T> lists, string code, string parentCode, string value, string childNodeName = "children")
{
var result = new List<T>();
var processedIds = new HashSet<string>();
var rootNodes = lists
.Where(m => (string?)m?.GetType().GetProperty(parentCode)?.GetValue(m, null) == value)
.ToList();
foreach (var root in rootNodes)
{
BuildTree(lists, root, code, parentCode, childNodeName, processedIds, result);
}
var orphanNodes = lists
.Where(m => !processedIds.Contains((string)(m?.GetType().GetProperty(code)?.GetValue(m, null) ?? "")))
.ToList();
foreach (var orphan in orphanNodes)
{
BuildTree(lists, orphan, code, parentCode, childNodeName, processedIds, result);
}
return result;
}
/// <summary>
/// 递归构建子树(内部方法)
/// </summary>
/// <param name="lists">原始平铺数据列表</param>
/// <param name="node">当前节点</param>
/// <param name="code">节点唯一标识字段名</param>
/// <param name="parentCode">父节点标识字段名</param>
/// <param name="childNodeName">子节点集合的字段名</param>
/// <param name="processedIds">已处理过的节点ID集合(用于去重)</param>
/// <param name="result">最终结果列表</param>
private static void BuildTree(List<T> lists, T node, string code, string parentCode, string childNodeName, HashSet<string> processedIds, List<T> result)
{
var type = node?.GetType();
var nodeId = (string)(type?.GetProperty(code)?.GetValue(node, null) ?? "");
if (processedIds.Contains(nodeId))
return;
processedIds.Add(nodeId);
var children = lists
.Where(m => (string?)m?.GetType().GetProperty(parentCode)?.GetValue(m, null) == nodeId)
.ToList();
if (children.Count > 0)
{
var childList = new List<T>();
foreach (var child in children)
{
BuildTree(lists, child, code, parentCode, childNodeName, processedIds, childList);
}
type?.GetProperty(childNodeName)?.SetValue(node, childList, null);
}
result.Add(node);
}
}
使用方法 :
cspublic async Task<List<MenuListVO>> GetMenuList(MenuListDTO menuListDTO) { List<IConditionalModel> conditionalModels = new List<IConditionalModel>(); var query = db.Queryable<h_bd_menu>().AS("h_bd_menu") .WhereIF(!StringExtension.IsNullOrEmpty(menuListDTO.title), v => v.title.Contains(menuListDTO.title)); if (!StringExtension.IsNullOrEmpty(menuListDTO.query)) { conditionalModels = db.Utilities.JsonToConditionalModels(menuListDTO.query.ToJson()); } var list = await query.Where(conditionalModels).ToListAsync(); ConsoleHelper.WriteLine(list.ToJson()); var rootvalue = list.Any() ? list.Min(v => v.parent) : 0; var menuTree = ListToTree<MenuListVO>.ListToTreesWithOrphan(App.Mapper.MapTo<List<MenuListVO>>(list), "id", "parent", rootvalue.ToString()); ConsoleHelper.WriteLine(menuTree.ToJson()); return App.Mapper.MapTo<List<MenuListVO>>(menuTree); }
转换前数据
javascript
[
{
"title": "测试表单",
"number": "test",
"component": "layouts/DetailLayout.vue",
"desc": "",
"name": "AppTest",
"icon": "canyinkafei",
"sortid": 1,
"parent": 0,
"islock": false,
"id": 2039617586328956928,
"created_by": 1913844754068672512,
"created_time": "2026-04-02 16:15:01",
"IsDeleted": false
},
{
"title": "表单",
"number": "test-pending",
"component": "",
"desc": "二次封装表单",
"name": "AppTestTestPending",
"icon": "jiaoseguanli1",
"sortid": 1,
"parent": 2039617586328956928,
"islock": false,
"id": 2039629889204056064,
"created_by": 1913844754068672512,
"created_time": "2026-04-02 17:03:54",
"IsDeleted": false
},
{
"title": "表单原版",
"number": "form",
"component": "views/template/Form.vue",
"desc": "",
"name": "AppTestTestPendingForm",
"icon": "geren-wodeweituo",
"sortid": 1,
"parent": 2039629889204056064,
"islock": true,
"id": 2039642027431751680,
"created_by": 1913844754068672512,
"created_time": "2026-04-02 17:52:08",
"IsDeleted": false
},
{
"title": "配置表单",
"number": "config-form",
"component": "views/template/ConfigFormExample.vue",
"desc": "",
"name": "AppTestTestPendingConfigForm",
"icon": "canyinkafei",
"sortid": 3,
"parent": 2039629889204056064,
"islock": false,
"id": 2039642271640907776,
"created_by": 1913844754068672512,
"created_time": "2026-04-02 17:53:06",
"IsDeleted": false
},
{
"title": "菜单管理",
"number": "menumanage",
"component": "views/menu/index.vue",
"desc": "",
"name": "AppTestTestPendingMenuManage",
"icon": "geren-wodeweituo",
"sortid": 1,
"parent": 2039642863855661056,
"islock": false,
"id": 2039642995829436416,
"created_by": 1913844754068672512,
"created_time": "2026-04-02 17:55:59",
"IsDeleted": false
}
]
转化后数据
javascript
[
{
"id": "2039617586328956928",
"title": "测试表单",
"number": "test",
"component": "layouts/DetailLayout.vue",
"desc": "",
"name": "AppTest",
"icon": "canyinkafei",
"sortid": "1",
"parent": "0",
"islock": false,
"children": [
{
"id": "2039629889204056064",
"title": "表单",
"number": "test-pending",
"component": "",
"desc": "二次封装表单",
"name": "AppTestTestPending",
"icon": "jiaoseguanli1",
"sortid": "1",
"parent": "2039617586328956928",
"islock": false,
"children": [
{
"id": "2039642027431751680",
"title": "表单原版",
"number": "form",
"component": "views/template/Form.vue",
"desc": "",
"name": "AppTestTestPendingForm",
"icon": "geren-wodeweituo",
"sortid": "1",
"parent": "2039629889204056064",
"islock": true
},
{
"id": "2039642271640907776",
"title": "配置表单",
"number": "config-form",
"component": "views/template/ConfigFormExample.vue",
"desc": "",
"name": "AppTestTestPendingConfigForm",
"icon": "canyinkafei",
"sortid": "3",
"parent": "2039629889204056064",
"islock": false
}
]
}
]
},
{
"id": "2039642995829436416",
"title": "菜单管理",
"number": "menumanage",
"component": "views/menu/index.vue",
"desc": "",
"name": "AppTestTestPendingMenuManage",
"icon": "geren-wodeweituo",
"sortid": "1",
"parent": "2039642863855661056",
"islock": false
}
]