如何在Blazor表单中维护多行列级联下拉框的独立状态?
Blazor级联下拉列表筛选异常问题
背景信息
- 有一个锻炼列表
- 两个枚举类型
WorkoutType(LowerBody/UpperBody)和ExerciseType(Main/Secondary/Accessory),用于逐层筛选锻炼列表 - 一个EditForm表单,支持用户:
- 先选择
WorkoutType,筛选出对应类型的锻炼列表 - 再选择
ExerciseType,进一步缩小筛选范围
- 先选择
说明:针对不熟悉健身领域的用户补充:一次锻炼包含大类型(如上半身/下半身训练),每个大类型下又分不同训练层级(主要训练、次要训练、辅助训练)
预期操作流程
- 选择
WorkoutType,锻炼列表完成初次筛选 - 在表单第一行选择
ExerciseType,列表二次筛选 - 从两次筛选后的列表中选择锻炼名称(第一行)
- 切换到表单第二行
- 选择第二行的
ExerciseType - 从对应筛选后的列表中选择新的锻炼名称
问题现象
第二行的锻炼名称列表无法更新,即使选择了不同的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,下拉列表绑定该属性即可,逻辑与字典方案类似。
复现步骤
- 启动应用
- 导航到
/addworkout页面 - 选择
WorkoutType(全局生效) - 选择第一行的
ExerciseType,并选择对应锻炼项 - 选择第二行的
ExerciseType,此时即可看到对应筛选后的锻炼列表
内容的提问来源于stack exchange,提问作者user22874144
相关产品推荐
相关产品推荐

