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

如何在Blazor表单中维护多行列级联下拉框的独立状态?

Blazor级联下拉列表筛选异常问题

背景信息

  • 有一个锻炼列表
  • 两个枚举类型WorkoutType(LowerBody/UpperBody)和ExerciseType(Main/Secondary/Accessory),用于逐层筛选锻炼列表
  • 一个EditForm表单,支持用户:
    • 先选择WorkoutType,筛选出对应类型的锻炼列表
    • 再选择ExerciseType,进一步缩小筛选范围

说明:针对不熟悉健身领域的用户补充:一次锻炼包含大类型(如上半身/下半身训练),每个大类型下又分不同训练层级(主要训练、次要训练、辅助训练)

预期操作流程

  1. 选择WorkoutType,锻炼列表完成初次筛选
  2. 在表单第一行选择ExerciseType,列表二次筛选
  3. 从两次筛选后的列表中选择锻炼名称(第一行)
  4. 切换到表单第二行
  5. 选择第二行的ExerciseType
  6. 从对应筛选后的列表中选择新的锻炼名称

问题现象

第二行的锻炼名称列表无法更新,即使选择了不同的ExerciseType,显示的列表始终和第一行完全一致。

当前实现的AddWorkout.razor组件代码

@using stateManagementMinimumReproducibleExample.Repositories
@using stateManagementMinimumReproducibleExample.Models
@page "/addworkout"
@rendermode InteractiveServer
@inject UserExerciseRepository userExerciseRepository

<EditForm Model="userExercise">
    <label for="workoutType"> Workout Type: </label>
    <InputSelect id="workoutType" @bind-Value="userExercise.WorkoutType" @bind-Value:after="GetExercisesByWorkoutType">
        @foreach (var type in Enum.GetValues(typeof(WorkoutType)))
        {
            <option value="@type">@type</option>
        }
    </InputSelect>

    @for (int i = 0; i < numberOfExercisesInWorkout; i++)
    {
        var j = i;
        if (j >= exerciseListForSelectedDay.Count)
        {
            exerciseListForSelectedDay.Add(new UserExercise());
        }

        <div>
            <label for="exerciseType"> Exercise Type: </label>
            <InputSelect id="exerciseType" @bind-Value="exerciseListForSelectedDay[j].ExerciseType" @bind-Value:after="GetExercisesByExerciseType">
                @foreach (var type in Enum.GetValues(typeof(ExerciseType)))
                {
                    <option value="@type">@type</option>
                }
            </InputSelect>

            <label for="exerciseName"> Exercise: </label>
            <InputSelect id="exerciseName" @bind-Value="exerciseListForSelectedDay[j].ExerciseId">
                @foreach (var exercise in listOfExerciseByExerciseType)
                {
                    <option value="@exercise.Id">@exercise.Name</option>
                }
            </InputSelect>
        </div>
    }
</EditForm>


@code {
    private int numberOfExercisesInWorkout = 2;

    private UserExercise userExercise = new();

    private List<UserExercise> exerciseListForSelectedDay = new();

    public List<Exercise> listOfExercisesByWorkoutType = new();

    public List<Exercise> listOfExerciseByExerciseType = new();

    public async Task GetExercisesByWorkoutType()
    {
            listOfExercisesByWorkoutType = await userExerciseRepository.GetExercisesBasedOnWorkoutType(userExercise.WorkoutType);
            StateHasChanged();
    }

    public void GetExercisesByExerciseType()
    {
            listOfExerciseByExerciseType = userExerciseRepository.GetExercisesFromWorkoutListBasedOnExerciseType(listOfExercisesByWorkoutType, userExercise.ExerciseType);
    }
}

问题原因

所有行的锻炼名称下拉列表都绑定了同一个全局列表listOfExerciseByExerciseType,无论哪一行选择ExerciseType,都会覆盖这个列表的内容,导致所有行显示相同的筛选结果。

解决方案

为每一行单独存储对应的筛选后锻炼列表,这里采用字典存储行索引与对应锻炼列表的方案,具体修改如下:

1. 替换全局列表为字典

在@code块中,将listOfExerciseByExerciseType替换为字典:

// 原代码:public List<Exercise> listOfExerciseByExerciseType = new();
public Dictionary<int, List<Exercise>> filteredExercisesByRow = new();

2. 修改ExerciseType选择后的回调方法

修改GetExercisesByExerciseType方法,接收行索引参数,针对当前行筛选并存储结果:

public void GetExercisesByExerciseType(int rowIndex)
{
    // 获取当前行选择的ExerciseType
    var selectedExerciseType = exerciseListForSelectedDay[rowIndex].ExerciseType;
    // 筛选对应锻炼列表并存储到字典中
    filteredExercisesByRow[rowIndex] = userExerciseRepository.GetExercisesFromWorkoutListBasedOnExerciseType(listOfExercisesByWorkoutType, selectedExerciseType);
    // 通知组件更新UI
    StateHasChanged();
}

3. 更新InputSelect的回调绑定

在循环渲染的InputSelect中,传递当前行的索引j给回调方法:

// 原代码:@bind-Value:after="GetExercisesByExerciseType"
<InputSelect id="exerciseType" @bind-Value="exerciseListForSelectedDay[j].ExerciseType" @bind-Value:after="() => GetExercisesByExerciseType(j)">

4. 修改锻炼名称下拉列表的数据源

让每一行的下拉列表读取对应索引的筛选结果:

<InputSelect id="exerciseName" @bind-Value="exerciseListForSelectedDay[j].ExerciseId">
    @if (filteredExercisesByRow.TryGetValue(j, out var exercises))
    {
        @foreach (var exercise in exercises)
        {
            <option value="@exercise.Id">@exercise.Name</option>
        }
    }
</InputSelect>

5. 处理WorkoutType切换时的清空逻辑

当WorkoutType改变时,清空所有行的筛选结果,避免旧数据残留:

public async Task GetExercisesByWorkoutType()
{
    listOfExercisesByWorkoutType = await userExerciseRepository.GetExercisesBasedOnWorkoutType(userExercise.WorkoutType);
    // 清空所有行的筛选结果
    filteredExercisesByRow.Clear();
    StateHasChanged();
}

可选方案:扩展UserExercise类

如果不想使用字典,也可以给UserExercise类添加一个FilteredExercises属性,直接存储当前项对应的筛选列表:

public class UserExercise
{
    // 原有属性...
    public WorkoutType WorkoutType { get; set; }
    public ExerciseType ExerciseType { get; set; }
    public int ExerciseId { get; set; }
    
    // 添加新属性
    public List<Exercise> FilteredExercises { get; set; } = new();
}

之后在筛选时直接赋值给当前行的FilteredExercises,下拉列表绑定该属性即可,逻辑与字典方案类似。

复现步骤

  1. 启动应用
  2. 导航到/addworkout页面
  3. 选择WorkoutType(全局生效)
  4. 选择第一行的ExerciseType,并选择对应锻炼项
  5. 选择第二行的ExerciseType,此时即可看到对应筛选后的锻炼列表

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 23:42:33