|
|
// ============================================================================
|
|
|
// 【文件说明】HwWebMenu1Service.cs - 网站菜单服务类(变体版本)
|
|
|
// ============================================================================
|
|
|
// 这个服务类负责处理网站菜单的业务逻辑(变体版本),包括:
|
|
|
// - 菜单的 CRUD 操作
|
|
|
// - 树形菜单结构构建
|
|
|
// - 软删除支持
|
|
|
//
|
|
|
// 【业务背景】
|
|
|
// 网站菜单模块用于管理网站的前端导航菜单,支持多级树形结构。
|
|
|
// 这个服务是 HwWebMenuService 的变体版本,可能用于不同的菜单场景。
|
|
|
//
|
|
|
// 【与 Java Spring Boot 的对比】
|
|
|
// Java Spring Boot:
|
|
|
// @Service
|
|
|
// public class HwWebMenu1ServiceImpl implements HwWebMenu1Service { ... }
|
|
|
//
|
|
|
// ASP.NET Core + Furion:
|
|
|
// public class HwWebMenu1Service : ITransient { ... }
|
|
|
// ============================================================================
|
|
|
|
|
|
namespace Admin.NET.Plugin.HwPortal;
|
|
|
|
|
|
/// <summary>
|
|
|
/// 网站菜单服务类(变体版本)。
|
|
|
/// <para>
|
|
|
/// 【服务职责】
|
|
|
/// 1. 管理网站菜单的增删改查
|
|
|
/// 2. 构建树形菜单结构
|
|
|
/// 3. 支持软删除(IsDelete 字段标记)
|
|
|
/// </para>
|
|
|
/// <para>
|
|
|
/// 【树形结构说明】
|
|
|
/// - Parent:父菜单ID,null或0表示顶级菜单
|
|
|
/// - Ancestors:祖先路径,格式为"0,1,2"
|
|
|
/// - 支持多级嵌套菜单结构
|
|
|
/// </para>
|
|
|
/// <para>
|
|
|
/// 【与 HwWebMenuService 的区别】
|
|
|
/// 这个服务是菜单服务的变体版本,可能用于:
|
|
|
/// - 不同的菜单类型(如前台菜单 vs 后台菜单)
|
|
|
/// - 不同的租户或站点
|
|
|
/// - 不同的业务场景
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
public class HwWebMenu1Service : ITransient
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// MyBatis 映射器名称(保留用于回滚)。
|
|
|
/// <para>
|
|
|
/// 【C# 语法知识点 - const 常量】
|
|
|
/// const 是"编译期常量",值在编译时就确定了。
|
|
|
///
|
|
|
/// 对比 Java:
|
|
|
/// Java: private static final String MAPPER = "HwWebMenuMapper1";
|
|
|
/// C#: private const string Mapper = "HwWebMenuMapper1";
|
|
|
///
|
|
|
/// C# 的命名约定:
|
|
|
/// - const 通常用 PascalCase(首字母大写)
|
|
|
/// - Java 的 static final 通常用 UPPER_SNAKE_CASE
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
private const string Mapper = "HwWebMenuMapper1";
|
|
|
|
|
|
/// <summary>
|
|
|
/// MyBatis 执行器(保留用于回滚)。
|
|
|
/// <para>
|
|
|
/// 【依赖注入】
|
|
|
/// 通过构造函数注入,和控制器注入服务的方式一样。
|
|
|
///
|
|
|
/// HwPortalMyBatisExecutor 是自定义的执行器,封装了 MyBatis 风格的 SQL 执行逻辑。
|
|
|
/// 当前代码中已注释掉使用,但保留以备回滚。
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
private readonly HwPortalMyBatisExecutor _executor;
|
|
|
|
|
|
/// <summary>
|
|
|
/// SqlSugar 数据访问对象。
|
|
|
/// <para>
|
|
|
/// 【ISqlSugarClient 接口】
|
|
|
/// 这是 SqlSugar 的核心接口,提供数据库访问能力。
|
|
|
///
|
|
|
/// 对比 Java:
|
|
|
/// Java MyBatis: SqlSession 或 Mapper 接口
|
|
|
/// C# SqlSugar: ISqlSugarClient
|
|
|
///
|
|
|
/// 通过依赖注入获取,由框架在启动时配置并注册到 DI 容器。
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
private readonly ISqlSugarClient _db;
|
|
|
|
|
|
/// <summary>
|
|
|
/// 构造函数(依赖注入)。
|
|
|
/// <para>
|
|
|
/// 【C# 语法知识点 - 构造函数】
|
|
|
/// public HwWebMenu1Service(HwPortalMyBatisExecutor executor, ISqlSugarClient db)
|
|
|
///
|
|
|
/// 对比 Java:
|
|
|
/// Java:
|
|
|
/// @Autowired
|
|
|
/// public HwWebMenu1Service(HwPortalMyBatisExecutor executor, ISqlSugarClient db) {
|
|
|
/// this.executor = executor;
|
|
|
/// this.db = db;
|
|
|
/// }
|
|
|
///
|
|
|
/// C#:
|
|
|
/// public HwWebMenu1Service(HwPortalMyBatisExecutor executor, ISqlSugarClient db)
|
|
|
/// {
|
|
|
/// _executor = executor;
|
|
|
/// _db = db;
|
|
|
/// }
|
|
|
///
|
|
|
/// C# 的改进:
|
|
|
/// 1. 不需要 @Autowired 注解,框架自动识别构造函数
|
|
|
/// 2. 使用 _executor 命名约定表示私有字段
|
|
|
/// 3. 可以使用主构造函数(C# 12+)进一步简化
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="executor">MyBatis 执行器(保留用于回滚)</param>
|
|
|
/// <param name="db">SqlSugar 数据访问对象</param>
|
|
|
public HwWebMenu1Service(HwPortalMyBatisExecutor executor, ISqlSugarClient db)
|
|
|
{
|
|
|
_executor = executor;
|
|
|
_db = db;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 根据菜单ID查询菜单信息。
|
|
|
/// <para>
|
|
|
/// 【方法命名约定】
|
|
|
/// Select + 实体名 + By + 主键名:根据主键查询单条记录
|
|
|
///
|
|
|
/// 对比 Java 若依:
|
|
|
/// 若依通常使用:selectXxxById、getXxxById
|
|
|
/// 这里使用:SelectXxxByWebMenuId,更符合 C# 的 PascalCase 命名规范
|
|
|
/// </para>
|
|
|
/// <para>
|
|
|
/// 【软删除过滤】
|
|
|
/// 查询时自动过滤已删除的记录(IsDelete == "0")。
|
|
|
/// 这是软删除的标准做法。
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="webMenuId">菜单ID</param>
|
|
|
/// <returns>菜单实体</returns>
|
|
|
public async Task<HwWebMenu1> SelectHwWebMenuByWebMenuId(long webMenuId)
|
|
|
{
|
|
|
// 回滚到 XML 方案时可直接恢复:
|
|
|
// return await _executor.QuerySingleAsync<HwWebMenu1>(Mapper, "selectHwWebMenuByWebMenuId", new { webMenuId });
|
|
|
|
|
|
// 【SqlSugar 查询语法】
|
|
|
// _db.Queryable<HwWebMenu1>():创建一个针对 HwWebMenu1 表的查询
|
|
|
// .Where(item => item.IsDelete == "0" && item.WebMenuId == webMenuId):添加 WHERE 条件
|
|
|
// .FirstAsync():执行查询,返回第一条记录,如果没有则返回 null
|
|
|
//
|
|
|
// 生成的 SQL 类似于:
|
|
|
// SELECT * FROM hw_web_menu1 WHERE IsDelete = '0' AND WebMenuId = @webMenuId LIMIT 1
|
|
|
//
|
|
|
// 对比 Java MyBatis:
|
|
|
// Java: mapper.selectOne(new QueryWrapper<HwWebMenu1>().eq("IsDelete", "0").eq("WebMenuId", webMenuId));
|
|
|
// C#: _db.Queryable<HwWebMenu1>().Where(item => item.IsDelete == "0" && item.WebMenuId == webMenuId).FirstAsync();
|
|
|
//
|
|
|
// C# 的优势:Lambda 表达式是强类型的,拼写错误在编译期就能发现
|
|
|
return await _db.Queryable<HwWebMenu1>()
|
|
|
.Where(item => item.IsDelete == "0" && item.WebMenuId == webMenuId)
|
|
|
.FirstAsync();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 查询菜单列表。
|
|
|
/// <para>
|
|
|
/// 【动态查询条件】
|
|
|
/// 支持按父ID、祖先路径、状态、菜单名、租户ID、图片、类型、值、英文名等条件筛选。
|
|
|
/// 所有条件都是可选的,有值时才添加到 WHERE 子句。
|
|
|
/// </para>
|
|
|
/// <para>
|
|
|
/// 【软删除过滤】
|
|
|
/// 默认只查询未删除的记录(IsDelete == "0")。
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="input">查询条件</param>
|
|
|
/// <returns>菜单列表</returns>
|
|
|
public async Task<List<HwWebMenu1>> SelectHwWebMenuList(HwWebMenu1 input)
|
|
|
{
|
|
|
// 【防御性编程】
|
|
|
// 确保 query 不为 null,避免后续空引用异常
|
|
|
HwWebMenu1 query = input ?? new HwWebMenu1();
|
|
|
|
|
|
// 回滚到 XML 方案时可直接恢复:
|
|
|
// return await _executor.QueryListAsync<HwWebMenu1>(Mapper, "selectHwWebMenuList", query);
|
|
|
|
|
|
// 【SqlSugar WhereIF 动态条件】
|
|
|
// .WhereIF(condition, expression):只有当 condition 为 true 时,才添加 WHERE 条件
|
|
|
//
|
|
|
// 对比 Java MyBatis 的 XML:
|
|
|
// <if test="parent != null">
|
|
|
// AND Parent = #{parent}
|
|
|
// </if>
|
|
|
//
|
|
|
// C# SqlSugar 的 WhereIF 更直观,而且类型安全
|
|
|
//
|
|
|
// 【string.IsNullOrWhiteSpace】
|
|
|
// 检查字符串是否为 null、空字符串 "" 或仅包含空白字符
|
|
|
//
|
|
|
// 对比 Java:
|
|
|
// Java: StringUtils.isBlank(str)(需要 Apache Commons Lang)
|
|
|
// C#: string.IsNullOrWhiteSpace(str)(内置)
|
|
|
//
|
|
|
// 【Contains 模糊查询】
|
|
|
// item.WebMenuName.Contains(query.WebMenuName)
|
|
|
// 生成 SQL:WHERE WebMenuName LIKE '%xxx%'
|
|
|
//
|
|
|
// 【HasValue 可空类型检查】
|
|
|
// query.Parent.HasValue 检查可空 long? 是否有值
|
|
|
//
|
|
|
// 对比 Java:
|
|
|
// Java: query.getParent() != null
|
|
|
// C#: query.Parent.HasValue
|
|
|
//
|
|
|
// 【ToListAsync】
|
|
|
// 执行查询并返回列表,异步版本
|
|
|
return await _db.Queryable<HwWebMenu1>()
|
|
|
.Where(item => item.IsDelete == "0")
|
|
|
.WhereIF(query.Parent.HasValue, item => item.Parent == query.Parent)
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(query.Ancestors), item => item.Ancestors == query.Ancestors)
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(query.Status), item => item.Status == query.Status)
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(query.WebMenuName), item => item.WebMenuName.Contains(query.WebMenuName))
|
|
|
.WhereIF(query.TenantId.HasValue, item => item.TenantId == query.TenantId)
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(query.WebMenuPic), item => item.WebMenuPic == query.WebMenuPic)
|
|
|
.WhereIF(query.WebMenuType.HasValue, item => item.WebMenuType == query.WebMenuType)
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(query.Value), item => item.Value.Contains(query.Value))
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(query.WebMenuNameEnglish), item => item.WebMenuNameEnglish.Contains(query.WebMenuNameEnglish))
|
|
|
.ToListAsync();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 查询菜单树。
|
|
|
/// <para>
|
|
|
/// 【树形结构构建】
|
|
|
/// 先查询菜单列表,然后调用 BuildWebMenuTree 构建树形结构。
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="input">查询条件</param>
|
|
|
/// <returns>树形菜单列表</returns>
|
|
|
public async Task<List<HwWebMenu1>> SelectMenuTree(HwWebMenu1 input)
|
|
|
{
|
|
|
// 查询菜单列表
|
|
|
List<HwWebMenu1> menus = await SelectHwWebMenuList(input);
|
|
|
|
|
|
// 构建并返回树形结构
|
|
|
return BuildWebMenuTree(menus);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 新增菜单。
|
|
|
/// <para>
|
|
|
/// 【软删除标记初始化】
|
|
|
/// 如果未指定删除标记,默认为"0"(未删除)。
|
|
|
/// 这是软删除的标准做法。
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="input">菜单数据</param>
|
|
|
/// <returns>影响行数</returns>
|
|
|
public async Task<int> InsertHwWebMenu(HwWebMenu1 input)
|
|
|
{
|
|
|
// 【软删除标记初始化】
|
|
|
// string.IsNullOrWhiteSpace 检查是否为 null、空字符串或仅包含空白字符
|
|
|
// ?? 是空合并运算符:如果左边为 null,返回右边
|
|
|
input.IsDelete = string.IsNullOrWhiteSpace(input.IsDelete) ? "0" : input.IsDelete;
|
|
|
|
|
|
// 回滚到 XML 方案时可直接恢复:
|
|
|
// return await _executor.ExecuteAsync(Mapper, "insertHwWebMenu", input);
|
|
|
|
|
|
// 【SqlSugar 插入语法】
|
|
|
// _db.Insertable(input):创建一个插入操作
|
|
|
// .ExecuteCommandAsync():执行插入,返回影响行数
|
|
|
//
|
|
|
// 生成的 SQL 类似于:
|
|
|
// INSERT INTO hw_web_menu1 (Parent, Ancestors, Status, WebMenuName, ...) VALUES (@Parent, @Ancestors, ...)
|
|
|
return await _db.Insertable(input).ExecuteCommandAsync();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 更新菜单信息。
|
|
|
/// <para>
|
|
|
/// 【字段级更新策略】
|
|
|
/// 1. 先查询现有记录
|
|
|
/// 2. 对非空字段进行更新
|
|
|
/// 3. null 值表示"不更新此字段"
|
|
|
///
|
|
|
/// 这种设计支持部分更新(PATCH 语义)。
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="input">更新的数据</param>
|
|
|
/// <returns>影响行数</returns>
|
|
|
public async Task<int> UpdateHwWebMenu(HwWebMenu1 input)
|
|
|
{
|
|
|
// 回滚到 XML 方案时可直接恢复:
|
|
|
// return await _executor.ExecuteAsync(Mapper, "updateHwWebMenu", input);
|
|
|
|
|
|
// 查询现有记录
|
|
|
HwWebMenu1 current = await SelectHwWebMenuByWebMenuId(input.WebMenuId ?? 0);
|
|
|
if (current == null)
|
|
|
{
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
// 【父菜单ID】
|
|
|
// HasValue 检查可空类型是否有值
|
|
|
if (input.Parent.HasValue)
|
|
|
{
|
|
|
current.Parent = input.Parent;
|
|
|
}
|
|
|
|
|
|
// 【祖先路径】
|
|
|
// != null 检查引用类型是否为 null
|
|
|
if (input.Ancestors != null)
|
|
|
{
|
|
|
current.Ancestors = input.Ancestors;
|
|
|
}
|
|
|
|
|
|
// 【状态】
|
|
|
if (input.Status != null)
|
|
|
{
|
|
|
current.Status = input.Status;
|
|
|
}
|
|
|
|
|
|
// 【菜单名】
|
|
|
if (input.WebMenuName != null)
|
|
|
{
|
|
|
current.WebMenuName = input.WebMenuName;
|
|
|
}
|
|
|
|
|
|
// 【租户ID】
|
|
|
if (input.TenantId.HasValue)
|
|
|
{
|
|
|
current.TenantId = input.TenantId;
|
|
|
}
|
|
|
|
|
|
// 【菜单图片】
|
|
|
if (input.WebMenuPic != null)
|
|
|
{
|
|
|
current.WebMenuPic = input.WebMenuPic;
|
|
|
}
|
|
|
|
|
|
// 【菜单类型】
|
|
|
if (input.WebMenuType.HasValue)
|
|
|
{
|
|
|
current.WebMenuType = input.WebMenuType;
|
|
|
}
|
|
|
|
|
|
// 【值】
|
|
|
if (input.Value != null)
|
|
|
{
|
|
|
current.Value = input.Value;
|
|
|
}
|
|
|
|
|
|
// 【英文名】
|
|
|
if (input.WebMenuNameEnglish != null)
|
|
|
{
|
|
|
current.WebMenuNameEnglish = input.WebMenuNameEnglish;
|
|
|
}
|
|
|
|
|
|
// 【执行更新】
|
|
|
// _db.Updateable(current):创建更新操作
|
|
|
// .ExecuteCommandAsync():执行更新,返回影响行数
|
|
|
return await _db.Updateable(current).ExecuteCommandAsync();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 批量删除菜单(软删除)。
|
|
|
/// <para>
|
|
|
/// 【软删除实现】
|
|
|
/// 不是物理删除,而是将 IsDelete 字段更新为"1"。
|
|
|
/// 这样可以保留数据用于审计,也支持恢复操作。
|
|
|
///
|
|
|
/// 对比 Java MyBatis:
|
|
|
/// Java 通常也是 UPDATE table SET is_delete = '1' WHERE id IN (...)
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="webMenuIds">菜单ID数组</param>
|
|
|
/// <returns>影响行数</returns>
|
|
|
public async Task<int> DeleteHwWebMenuByWebMenuIds(long[] webMenuIds)
|
|
|
{
|
|
|
// 回滚到 XML 方案时可直接恢复:
|
|
|
// return await _executor.ExecuteAsync(Mapper, "deleteHwWebMenuByWebMenuIds", new { array = webMenuIds });
|
|
|
|
|
|
// 【查询待删除的菜单】
|
|
|
// .Where(item => item.WebMenuId.HasValue && webMenuIds.Contains(item.WebMenuId.Value))
|
|
|
// 条件:WebMenuId 有值,且在传入的 ID 数组中
|
|
|
List<HwWebMenu1> menus = await _db.Queryable<HwWebMenu1>()
|
|
|
.Where(item => item.WebMenuId.HasValue && webMenuIds.Contains(item.WebMenuId.Value))
|
|
|
.ToListAsync();
|
|
|
|
|
|
// 【软删除标记】
|
|
|
// 将 IsDelete 字段设置为 "1",表示已删除
|
|
|
foreach (HwWebMenu1 menu in menus)
|
|
|
{
|
|
|
menu.IsDelete = "1";
|
|
|
}
|
|
|
|
|
|
// 【批量更新】
|
|
|
// 只更新 IsDelete 字段,使用 UpdateColumns 指定更新字段
|
|
|
// 如果 menus 为空,返回 0;否则执行批量更新
|
|
|
return menus.Count == 0 ? 0 : await _db.Updateable(menus)
|
|
|
.UpdateColumns(item => new { item.IsDelete })
|
|
|
.ExecuteCommandAsync();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 构建菜单树形结构。
|
|
|
/// <para>
|
|
|
/// 【树形结构构建算法】
|
|
|
/// 这是一个经典的"扁平列表转树形结构"算法:
|
|
|
///
|
|
|
/// 输入:扁平的节点列表,每个节点有 parentId 指向父节点
|
|
|
/// 输出:树形结构,顶级节点包含 children 子节点
|
|
|
///
|
|
|
/// 算法步骤:
|
|
|
/// 1. 收集所有节点ID到 tempList
|
|
|
/// 2. 遍历节点列表,找出所有顶级节点(parentId 为 null、0 或不在列表中)
|
|
|
/// 3. 对每个顶级节点,调用 RecursionFn 递归构建子树
|
|
|
/// 4. 返回树形结构列表
|
|
|
///
|
|
|
/// 对比 Java 若依:
|
|
|
/// 若依的 TreeBuildUtils.buildTree() 做同样的事情。
|
|
|
/// 这是若依框架的标准树构建算法。
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="menus">扁平菜单列表</param>
|
|
|
/// <returns>树形菜单列表</returns>
|
|
|
public List<HwWebMenu1> BuildWebMenuTree(List<HwWebMenu1> menus)
|
|
|
{
|
|
|
// 结果列表
|
|
|
List<HwWebMenu1> returnList = new();
|
|
|
|
|
|
// 【收集所有节点ID】
|
|
|
// .Select(item => item.WebMenuId) 提取所有 ID
|
|
|
// .ToList() 转换为列表
|
|
|
List<long?> tempList = menus.Select(item => item.WebMenuId).ToList();
|
|
|
|
|
|
// 【遍历查找顶级节点】
|
|
|
foreach (HwWebMenu1 menu in menus)
|
|
|
{
|
|
|
// 【顶级节点判定】
|
|
|
// 满足以下任一条件即为顶级节点:
|
|
|
// 1. !menu.Parent.HasValue:parent 为 null
|
|
|
// 2. menu.Parent == 0:parent 为 0
|
|
|
// 3. !tempList.Contains(menu.Parent):parent 不在列表中(父节点不存在)
|
|
|
if (!menu.Parent.HasValue || menu.Parent == 0 || !tempList.Contains(menu.Parent))
|
|
|
{
|
|
|
// 递归构建子树
|
|
|
RecursionFn(menus, menu);
|
|
|
returnList.Add(menu);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果没找到顶级节点,返回原列表(兜底处理)
|
|
|
return returnList.Count == 0 ? menus : returnList;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 递归构建子树。
|
|
|
/// <para>
|
|
|
/// 【递归算法说明】
|
|
|
/// 递归是一种"自己调用自己"的编程技巧。
|
|
|
///
|
|
|
/// 这里的递归逻辑:
|
|
|
/// 1. 找出当前节点的所有直接子节点
|
|
|
/// 2. 把子节点挂到当前节点的 Children 属性
|
|
|
/// 3. 对每个子节点,递归执行步骤 1-2
|
|
|
///
|
|
|
/// 终止条件:节点没有子节点(HasChild 返回 false)
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="list">所有节点列表</param>
|
|
|
/// <param name="current">当前节点</param>
|
|
|
private static void RecursionFn(List<HwWebMenu1> list, HwWebMenu1 current)
|
|
|
{
|
|
|
// 找出当前节点的所有直接子节点
|
|
|
List<HwWebMenu1> childList = GetChildList(list, current);
|
|
|
|
|
|
// 把子节点挂到当前节点
|
|
|
current.Children = childList;
|
|
|
|
|
|
// 【递归调用】
|
|
|
// 对每个有子节点的子节点,继续递归构建子树
|
|
|
// .Where(child => HasChild(list, child)) 筛选有子节点的节点
|
|
|
foreach (HwWebMenu1 child in childList.Where(child => HasChild(list, child)))
|
|
|
{
|
|
|
RecursionFn(list, child);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 获取当前节点的直接子节点列表。
|
|
|
/// </summary>
|
|
|
/// <param name="list">所有节点列表</param>
|
|
|
/// <param name="current">当前节点</param>
|
|
|
/// <returns>子节点列表</returns>
|
|
|
private static List<HwWebMenu1> GetChildList(List<HwWebMenu1> list, HwWebMenu1 current)
|
|
|
{
|
|
|
// 【LINQ Where 过滤】
|
|
|
// item.Parent.Value == current.WebMenuId.Value
|
|
|
// 条件:item 的 parent 等于 current 的 id
|
|
|
//
|
|
|
// .HasValue 检查可空类型是否有值(不为 null)
|
|
|
// .Value 获取可空类型的实际值
|
|
|
//
|
|
|
// 对比 Java:
|
|
|
// Java: list.stream().filter(item -> item.getParent() != null && current.getWebMenuId() != null && item.getParent().equals(current.getWebMenuId())).collect(Collectors.toList());
|
|
|
//
|
|
|
// C# 的可空类型处理更优雅
|
|
|
return list.Where(item => item.Parent.HasValue && current.WebMenuId.HasValue && item.Parent.Value == current.WebMenuId.Value).ToList();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 判断当前节点是否有子节点。
|
|
|
/// </summary>
|
|
|
/// <param name="list">所有节点列表</param>
|
|
|
/// <param name="current">当前节点</param>
|
|
|
/// <returns>是否有子节点</returns>
|
|
|
private static bool HasChild(List<HwWebMenu1> list, HwWebMenu1 current)
|
|
|
{
|
|
|
return GetChildList(list, current).Count > 0;
|
|
|
}
|
|
|
}
|