You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取Razor递归组件中所有选中的复选框列表?

递归复选框组件:获取所有选中项解决方案

已实现从服务获取递归结构对象列表,生成带展开/折叠功能的递归复选框组件,当前需要完成收集所有选中项列表的功能,用于后续服务调用或表单提交。以下是原代码及修改方案:


原代码

YourComponent.razor

@page "/yourcomponent"

<div style="margin-left: 20px;">
    @foreach (var item in RootItems2())
    {
        <RecursiveExpandableWithCheckbox Title="@item.Title" ChildItems="@item.ChildItems" />
    }
</div>

@code {
    
    Dashboard.Utility.DashboardExtras dashboardExtras = new DashboardExtras();

    private List<ExpandableItemModel> RootItems2()
    {
        try
        {
            string theRootObjectId = dashboardExtras.GetTheRootObjectId();

            Task<List<Item>> items = dashboardExtras.GetItemChildrenByObjectId(theRootObjectId);
            
            List<ExpandableItemModel> rootItems = new List<ExpandableItemModel>();
            foreach (var i0 in items.Result)
            {
                rootItems.Add(new ExpandableItemModel
                    {
                        Title = i0.Name + " - " + i0.FQID.ObjectId,
                        ChildItems = RootItems666(i0.FQID.ObjectId.ToString())
                    });
            }
            return rootItems;
        }
        catch (Exception ex)
        {
            return null;
        }

    }

    private List<ExpandableItemModel> RootItems666(string s)
    {
        Task<List<Item>> items = dashboardExtras.GetItemChildrenByObjectId(s);
        List<ExpandableItemModel> rootItems = new List<ExpandableItemModel>();
        if (items.Result != null)
        {
            foreach (var i0 in items.Result)
            {
                rootItems.Add(new ExpandableItemModel
                    {
                        Title = i0.Name + " - " + i0.FQID.ObjectId,
                        ChildItems = RootItems666(i0.FQID.ObjectId.ToString())
                    });
            }
            return rootItems;
        }
        else
        {
            return null;
        }
    }
}

RecursiveExpandableWithCheckbox.razor

<div>
    <span @onclick="ToggleCollapse">@GetExpandIcon()</span>
    <input type="checkbox" @bind="IsSelected" @oninput="OnCheckboxValueChanged" /> <!-- Checkbox -->
    <span>@Title</span>
</div>

@if (!IsCollapsed && ChildItems != null)
{
    <div style="margin-left: 20px;">
        @foreach (var item in ChildItems)
        {
            <RecursiveExpandableWithCheckbox Title="@item.Title" ChildItems="@item.ChildItems" @bind-IsSelected="@item.IsSelected"  />
        }
    </div>
}

@code {
    [Parameter] public string Title { get; set; }
    [Parameter] public List<ExpandableItemModel> ChildItems { get; set; }
    [Parameter] public bool IsSelected { get; set; } = false;
    [Parameter] public EventCallback<bool> IsSelectedChanged { get; set; }
    [Parameter] public EventCallback<string> StringList { get; set; }
    

    private List<string> SelectedOnes = new List<string>();
    private bool IsCollapsed = true;

    private void ToggleCollapse()
    {
        IsCollapsed = !IsCollapsed;
    }

    private string GetExpandIcon()
    {
        return IsCollapsed ? "+" : "-";
    }

    private async Task OnCheckboxValueChanged(ChangeEventArgs e)
    {
        IsSelected = (bool)e.Value;
        
        if (ChildItems != null)
        {
            foreach (var item in ChildItems)
            {
                item.IsSelected = IsSelected;
            }
        }

        await IsSelectedChanged.InvokeAsync(IsSelected);
    }
}

修改方案

1. 完善数据模型

给ExpandableItemModel添加唯一标识字段,方便后续精准获取选中项:

public class ExpandableItemModel
{
    public string Title { get; set; }
    public string ObjectId { get; set; } // 存储唯一ID,从原Title中提取
    public List<ExpandableItemModel> ChildItems { get; set; }
    public bool IsSelected { get; set; } = false;
}

2. 修正父组件异步数据加载

原代码用Task.Result会导致死锁,改为异步初始化加载数据,同时添加选中项收集逻辑:

@page "/yourcomponent"
@inject Dashboard.Utility.DashboardExtras dashboardExtras

<div style="margin-left: 20px;">
    @if (_rootItems == null)
    {
        <p>加载中...</p>
    }
    else
    {
        @foreach (var item in _rootItems)
        {
            <RecursiveExpandableWithCheckbox Model="@item" />
        }
    }
</div>

<button @onclick="GetSelectedItems">获取选中项</button>
<p>选中项ID:@string.Join(", ", _selectedObjectIds)</p>

@code {
    private List<ExpandableItemModel> _rootItems;
    private List<string> _selectedObjectIds = new List<string>();

    protected override async Task OnInitializedAsync()
    {
        try
        {
            string theRootObjectId = dashboardExtras.GetTheRootObjectId();
            var items = await dashboardExtras.GetItemChildrenByObjectId(theRootObjectId);
            _rootItems = await BuildRecursiveItems(items);
        }
        catch (Exception ex)
        {
            // 处理异常,比如显示错误信息
        }
    }

    private async Task<List<ExpandableItemModel>> BuildRecursiveItems(List<Item> items)
    {
        var result = new List<ExpandableItemModel>();
        if (items != null)
        {
            foreach (var i0 in items)
            {
                var childItems = await dashboardExtras.GetItemChildrenByObjectId(i0.FQID.ObjectId.ToString());
                result.Add(new ExpandableItemModel
                {
                    Title = $"{i0.Name} - {i0.FQID.ObjectId}",
                    ObjectId = i0.FQID.ObjectId.ToString(),
                    ChildItems = await BuildRecursiveItems(childItems)
                });
            }
        }
        return result;
    }

    private void GetSelectedItems()
    {
        _selectedObjectIds.Clear();
        CollectSelectedItems(_rootItems, _selectedObjectIds);
    }

    private void CollectSelectedItems(List<ExpandableItemModel> items, List<string> selectedIds)
    {
        if (items == null) return;
        foreach (var item in items)
        {
            if (item.IsSelected)
            {
                selectedIds.Add(item.ObjectId);
            }
            CollectSelectedItems(item.ChildItems, selectedIds);
        }
    }
}

3. 优化递归组件逻辑

修改组件直接接收ExpandableItemModel,简化参数传递,同时完善选中联动(包括子项选中时自动更新父项半选状态):

<div>
    <span @onclick="ToggleCollapse">@GetExpandIcon()</span>
    <input type="checkbox" 
           @bind="Model.IsSelected" 
           @onchange="OnCheckboxChanged"
           indeterminate="@IsIndeterminate" />
    <span>@Model.Title</span>
</div>

@if (!IsCollapsed && Model.ChildItems != null && Model.ChildItems.Any())
{
    <div style="margin-left: 20px;">
        @foreach (var child in Model.ChildItems)
        {
            <RecursiveExpandableWithCheckbox Model="@child" />
        }
    </div>
}

@code {
    [Parameter, Required] public ExpandableItemModel Model { get; set; }

    private bool IsCollapsed = true;
    private bool IsIndeterminate => Model.ChildItems?.Any(c => c.IsSelected) == true && !Model.IsSelected;

    private void ToggleCollapse()
    {
        IsCollapsed = !IsCollapsed;
    }

    private string GetExpandIcon()
    {
        return IsCollapsed ? "+" : "-";
    }

    private void OnCheckboxChanged(ChangeEventArgs e)
    {
        var isSelected = (bool)e.Value;
        Model.IsSelected = isSelected;
        // 子项同步选中状态
        if (Model.ChildItems != null)
        {
            foreach (var child in Model.ChildItems)
            {
                SetChildSelection(child, isSelected);
            }
        }
        StateHasChanged();
    }

    private void SetChildSelection(ExpandableItemModel item, bool isSelected)
    {
        item.IsSelected = isSelected;
        if (item.ChildItems != null)
        {
            foreach (var child in item.ChildItems)
            {
                SetChildSelection(child, isSelected);
            }
        }
    }

    protected override void OnParametersSet()
    {
        // 监听子项变化,更新当前项的半选状态
        if (Model.ChildItems != null)
        {
            var hasSelectedChild = Model.ChildItems.Any(c => c.IsSelected);
            var allSelected = Model.ChildItems.All(c => c.IsSelected);
            if (hasSelectedChild && !allSelected)
            {
                StateHasChanged();
            }
        }
    }
}

核心说明

  • 用异步加载数据替代同步Task.Result,避免UI线程死锁
  • 通过递归遍历数据模型收集所有选中项的ObjectId,无需依赖组件回调传递
  • 新增半选状态(indeterminate),当部分子项选中时父项显示半选,提升用户体验
  • 组件直接绑定数据模型,简化参数传递,状态变化实时同步到模型中

内容的提问来源于stack exchange,提问作者RichardCooke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 21:28:10