- 前端
- UI组件
- 设计系统
【免费下载链接】ant-design-blazor
基于 Ant Design 与 Blazor 的前端组件库。让开发者解放生产力,实现更大价值。
导读
在实际业务中,经常需要在表格单元格中直接编辑数据,并让表单校验覆盖到每个单元格,例如录入班级学生名单时逐行填写姓名、地址并校验必填。ant-design-blazor 的 Form 组件提供了ValidateMode="FormValidateMode.Complex"模式,可以把Table直接作为表单的一部分嵌进FormItem中,让每一列单元格里的输入控件都参与到整个EditContext的验证流程。读完本文,你将掌握"表格 + 表单"的组合写法、ValidateMode三种模式的差异、单元格校验失败时的错误样式处理,以及动态增删行与提交回调的完整实现方案。
场景概述:为什么需要"表格作为表单组件"
官方演示文档 nest-table.md 的主题是"表格编辑与验证(Table Edit & Validate)",其核心诉求一句话概括:
支持使用表格作为表单组件,并验证单元格编辑。
即:当一张表格的数据本身就是表单模型的一部分时(例如一个班级Model含若干Student行),我们希望:
- 表格单元格内的
Input等控件直接绑定到每行数据对象的属性; - 单元格内的输入参与表单整体校验,未通过校验时能像普通
FormItem一样呈现错误状态; - 提交时校验整张表的所有单元格,
OnFinish/OnFinishFailed正确触发。
这个场景的完整可运行示例位于 NestTable.razor,下文会以它为骨架逐步拆解。
先看完整示例:班级学生名单编辑表
示例的整体结构是:外层Form管理一个Model,其中Students是学生列表;FormItem内嵌一个Table,每一列用PropertyColumn包裹FormItem和Input实现单元格编辑。
@using System.ComponentModel.DataAnnotations; <Form Model="@model" ValidateMode="FormValidateMode.Complex" OnFinish="OnFinish" OnFinishFailed="OnFinishFailed" LabelColSpan="8" WrapperColSpan="16"> <FormItem Label="Class Name"> <Input @bind-Value="@context.ClassName" /> </FormItem> <FormItem Label="Students" Name="@nameof(context.Students)" Rules="[new(){ Min=1 }]"> <Button OnClick="addRow" Type="ButtonType.Primary" Style="margin-bottom:16px" Size="ButtonSize.Small"> Add a row </Button> <Table DataSource="context.Students" TItem="Student" Context="row" Size="TableSize.Small" HidePagination Bordered> <PropertyColumn Width="30%" Property="c=>c.Name"> <FormItem Required> <Input @bind-Value="@row.Name" /> </FormItem> </PropertyColumn> <PropertyColumn Property="c=>c.Address"> <FormItem Required> <Input @bind-Value="@row.Address" /> </FormItem> </PropertyColumn> </Table> </FormItem> <FormItem WrapperColOffset="8" WrapperColSpan="16"> <Button Type="ButtonType.Primary" HtmlType="submit"> Submit </Button> </FormItem> </Form> <style> .ant-form-item-has-error .ant-table { border: 1px solid red; } </style> @code { private Model model = new Model(); public class Model { [Required] public string ClassName { get; set; } public List<Student> Students { get; set; } = []; } public record Student() { public string Id { get; set; } public string Age { get; set; } public string Name { get; set; } public string Address { get; set; } } void addRow() { model.Students.Add(new()); } private void OnFinish(EditContext editContext) { Console.WriteLine($"Success:{JsonSerializer.Serialize(model)}"); } private void OnFinishFailed(EditContext editContext) { Console.WriteLine($"Failed:{JsonSerializer.Serialize(model)}"); } }下面逐段说明每部分的设计意图与底层实现。
Form 容器:ValidateMode 决定校验规则来源
外层Form的ValidateMode="FormValidateMode.Complex"是整个方案的关键开关。查看枚举定义 FormValidateMode.cs,三种模式语义如下:
| 模式 | 值 | 校验规则来源 |
|---|---|---|
Default | 0 | 仅来自绑定模型上的数据注解特性(Data Annotations,如[Required]) |
Rules | 1 | 仅来自各FormItem上的Rules参数 |
Complex | 3 | 同时使用数据注解与Rules规则 |
Form<TModel>的默认值正是FormValidateMode.Complex(见 Form.razor.cs)。规则合并逻辑在FormItem.SetRules()中实现(FormItem.razor.cs):
_rules = Form.ValidateMode switch { FormValidateMode.Default => GetRulesFromAttributes(), FormValidateMode.Rules => Rules ?? [], _ => [.. GetRulesFromAttributes(), .. Rules ?? []] }; if (Required && !_rules.Any(rule => rule.Required == true || rule.ValidationAttribute is RequiredAttribute)) { _rules = [.. _rules, new FormValidationRule { Required = true }]; }从源码可见Complex模式会拼接两套规则:由PropertyReflector反射模型属性得到的验证特性,以及FormItem.Rules显式声明的规则。这就是示例中 Class Name 依赖模型的[Required]注解、Students 却用Rules声明"至少 1 行"的原因——两者可以在同一表单中共存。
Form.razor内部实际渲染的是 Blazor 的EditForm,并根据模式选择验证器(Form.razor):
- 未指定
Validate时默认挂载<DataAnnotationsValidator />,用于处理数据注解; - 通过
UseRulesValidator挂载<FormRulesValidator />,用于处理Rules规则。
FormItem通过[CascadingParameter(Name = "Form")]拿到表单上下文(FormItem.razor.cs),并通过级联参数获取EditContext,从而与表单的验证生命周期挂钩。
让表格成为表单的一部分:Name 与 Member Path 绑定
示例中第二个FormItem的写法非常关键:
<FormItem Label="Students" Name="@nameof(context.Students)" Rules="[new(){ Min=1 }]">Name参数的作用在 FormItem.razor.cs 的注释中说明:它指定表单项的名称,同时可作为**成员路径(Member Path)**用于绑定模型属性。FormItem.BuildPropertyWithName()(FormItem.razor.cs)会通过PathHelper.GetLambda<object>(dataIndex, type)把"Students"解析成对Model.Students的访问表达式,构建出PropertyReflector:
- 有了
PropertyReflector,FormItem才能读取属性的验证特性(ValidationAttributes)、取值委托(GetValueDelegate)与类型信息; - 即使表格内部没有直接绑定
Model.Students的输入控件,FormItem也能基于Name独立完成对Students集合的校验。
因此Rules="[new(){ Min=1 }]"表达的是:Students集合至少要有 1 个元素,否则该表单项标记为校验失败。这是"表格行数校验"的核心手段——不校验某一行,而是校验整个数据源集合的长度。
表格作为编辑器:PropertyColumn 自定义单元格内容
示例中的Table并没有像常规表格那样通过PropertyColumn自动渲染文本,而是在列内嵌入了编辑控件:
<Table DataSource="context.Students" TItem="Student" Context="row" Size="TableSize.Small" HidePagination Bordered> <PropertyColumn Width="30%" Property="c=>c.Name"> <FormItem Required> <Input @bind-Value="@row.Name" /> </FormItem> </PropertyColumn> <PropertyColumn Property="c=>c.Address"> <FormItem Required> <Input @bind-Value="@row.Address" /> </FormItem> </PropertyColumn> </Table>Table通过Context="row"为每一行暴露当前行数据对象,列内的<Input @bind-Value="@row.Name" />因此绑定到每一行各自的Student.Name。PropertyColumn的CellContent渲染逻辑(Column.razor)中,ChildContent优先于默认的FormattedValue文本输出:
else if (ChildContent != null) { return ChildContent; }所以写在列内部的内容会被原样渲染为单元格内容,这正是把FormItem放进单元格的基础。
而列内的FormItem虽然没有显式Name,但它包裹的Input通过@bind-Value提供了ValueExpression,FormItem在控件初始化时(FormItem.razor.cs)会从控件的ValueExpression构建PropertyReflector并注册FieldIdentifier,从而把每一行的Name、Address属性纳入表单校验。Required参数(FormItem.razor.cs)则会强制注入一条Required = true的规则,见前文SetRules()的末尾逻辑。
表格参数要点
Size="TableSize.Small":紧凑行高,适合批量录入场景;Table的Size参数默认值为TableSize.Default(Table.razor.cs)。HidePagination:隐藏分页。从 Table.razor.Pagination.cs 的注释可知,设置后应避免同时设置PageSize;内部InternalReload()会把每页大小自动调整为数据源总行数(Table.razor.cs),即"一页展示全部行",保证新增行后立即完整显示。Bordered:带边框的表格,默认false(Table.razor.cs)。配合下述错误样式,边框线可以清晰勾勒出校验失败区域。
单元格校验失败的视觉反馈
表格内的FormItem校验失败时,会获得ant-form-item-has-error类(由 FormItem.razor.cs 的SetClass()根据_isValid状态添加),单元格内的Input会像普通表单项一样呈现红色边框。示例末尾还额外追加了一段样式,让整个表格在所属FormItem校验失败时整体描红:
.ant-form-item-has-error .ant-table { border: 1px solid red; }这段样式的作用是:当 "Students" 这个FormItem因Min=1不满足(例如用户删光了所有行)而失败时,视觉上整个表格被红框包围,用户能立即感知问题出在表格区域,而不只是细小的单元格内。同理,FormItem的Help、ValidateStatus等能力同样适用于表格单元格内的FormItem,可进一步自定义错误提示文案与状态图标。
动态增删行:表格数据源就是表单模型集合
示例中"Add a row"按钮直接向模型集合追加元素:
void addRow() { model.Students.Add(new()); }因为Table.DataSource="context.Students"引用的是Model.Students的同一引用,List<Student>变更后表格会自动重渲染出新的空行。Student使用record定义(NestTable.razor),属性默认初始化,新增的行各字段为空字符串,随即触发Required校验。若要实现删除行,只需类似地从集合中Remove对应行并刷新。
这里有一个值得注意的细节:Table依赖自身数据源重渲染,而FormItem对Students的校验走的是FormItem自身的验证管线,两者由EditContext统一协调。用户新增空行后如果直接提交,OnFinishFailed会被触发,因为新行单元格的Required校验与Students的Min=1校验都未通过。
提交与校验结果处理
提交按钮使用HtmlType="submit",触发EditForm的原生提交事件:
<FormItem WrapperColOffset="8" WrapperColSpan="16"> <Button Type="ButtonType.Primary" HtmlType="submit">Submit</Button> </FormItem>WrapperColOffset="8"让按钮与上方的输入控件左对齐(与LabelColSpan="8"呼应)。提交后由Form分发到两个回调:
- 全部校验通过 →
OnFinish(EditContext); - 存在任一校验失败 →
OnFinishFailed(EditContext)。
示例中两者都把整个model序列化输出到控制台,方便在调试中确认表格数据是否完整写回了模型。OnFinish与OnFinishFailed的参数类型是Microsoft.AspNetCore.Components.Forms.EditContext,这也印证了Form底层基于 BlazorEditForm的事实。
表格内编辑的验证流程小结
把整个链路串起来看:
Form创建EditContext,并以级联参数下发到所有FormItem(Form.razor);- 外层
FormItem通过Name定位Model.Students属性,Rules提供Min=1集合长度校验; - 内层表格单元格的
FormItem通过Input的ValueExpression定位每行Student的属性,Required提供单元格必填校验; Complex模式下数据注解与Rules规则被合并执行;- 校验结果通过
EditContext汇总,决定OnFinish还是OnFinishFailed; - 失败时各
FormItem挂上ant-form-item-has-error等状态类,自定义 CSS 可将整个表格描红提示。
延伸阅读(仓库源码路径)
- 表单组件主体:Form.razor、Form.razor.cs
- 校验模式枚举:FormValidateMode.cs
- 表单项规则合并与状态管理:FormItem.razor.cs
- 表格单元格内容渲染:Column.razor
- 表格分页与尺寸参数:Table.razor.cs、Table.razor.Pagination.cs
- 官方演示文档与示例代码:nest-table.md、NestTable.razor
相关表单能力还可参考同目录下的其他演示:dynamic-field(动态字段)、validate-onchange(变更时校验)、customized-form-validator(自定义验证器)等,它们与表格编辑场景结合可覆盖更复杂的录入需求。
- 前端
- UI组件
- 设计系统
【免费下载链接】ant-design-blazor
基于 Ant Design 与 Blazor 的前端组件库。让开发者解放生产力,实现更大价值。
相关推荐
ant-design-blazor 实战:将 Table 嵌入 Form 实现表格单元格编辑与验证
ant design blazor 实战:将 Table 嵌入 Form 实现表格单元格编辑与验证 本篇指南基于 ant design blazor 官方示例“
UI组件前端Ant Design表格编辑功能:单元格编辑与行内编辑实现
Ant Design表格编辑功能:单元格编辑与行内编辑实现 在日常数据管理工作中,你是否经常遇到需要快速修改表格数据的场景?比如编辑订单信息、更新用户资料或调整
前端UI组件设计系统Ant Design Table 单元格编辑实战:基于 Form 与 components 定制实现可编辑表格
Ant Design Table 单元格编辑实战:基于 Form 与 components 定制实现可编辑表格 本指南围绕 ant design 仓库中 edi
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考