SxDataGrid (数据表格)
- Implemented
工业级高容量数据展示表格。支持虚拟化滚动、排序、过滤、列宽调整、行选择和远程数据加载。对齐 Microsoft Fluent UI Blazor DataGrid 的 API 结构。
使用场景
- 大数据量表格展示(虚拟化滚动)
- 可排序/可筛选的业务数据
- 远程 API 数据分页加载
- 自定义列渲染与行选择
数据加载模式
SxDataGrid 支持两种数据加载模式:
1. 本地数据模式 (Items)
适用于数据量较小、已在内存中的场景。
<SxDataGrid TGridItem="Person" Items="@_people">
<SxPropertyColumn Property="@(p => p.Name)" Title="姓名" Sortable="true" />
<SxPropertyColumn Property="@(p => p.Age)" Title="年龄" Sortable="true" />
</SxDataGrid>
@code {
private IQueryable<Person> _people = GetPeople().AsQueryable();
}
2. 远程数据模式 (ItemsProvider)
适用于大数据量、需要服务端分页/排序/过滤的场景。
<SxDataGrid TGridItem="Person"
ItemsProvider="LoadDataAsync"
Virtualize="true"
ShowColumnOptions="true">
<SxPropertyColumn Property="@(p => p.Name)" Title="姓名" Sortable="true" />
<SxPropertyColumn Property="@(p => p.Age)" Title="年龄" Sortable="true" />
</SxDataGrid>
@code {
private async ValueTask<SxGridItemsProviderResult<Person>> LoadDataAsync(
SxGridItemsProviderRequest<Person> request)
{
// 从 API 或数据库加载数据
var query = _allData.AsQueryable();
// 应用过滤和排序
query = request.ApplyFilters(query);
query = request.ApplySorting(query);
var totalCount = query.Count();
var items = query.Skip(request.StartIndex)
.Take(request.Count ?? 20)
.ToList();
return SxGridItemsProviderResult<Person>.From(items, totalCount);
}
}
排序
启用排序
在列上设置 Sortable="true" 启用排序:
<SxPropertyColumn Property="@(p => p.Name)" Title="姓名" Sortable="true" />
默认排序列
设置 IsDefaultSortColumn="true" 和 InitialSortDirection 指定默认排序:
<SxPropertyColumn Property="@(p => p.CreatedAt)" Title="创建时间"
Sortable="true"
IsDefaultSortColumn="true"
InitialSortDirection="SortDirection.Descending" />
排序事件
<SxDataGrid TGridItem="Person" Items="@_people" OnSortChanged="HandleSortChanged">
...
</SxDataGrid>
@code {
private void HandleSortChanged(DataGridSortChangedEventArgs<Person> args)
{
Console.WriteLine($"排序列: {args.Column?.Title}, 升序: {args.Ascending}");
}
}
远程数据排序
使用 ItemsProvider 时,排序信息通过 request 参数传递:
private async ValueTask<SxGridItemsProviderResult<Person>> LoadDataAsync(
SxGridItemsProviderRequest<Person> request)
{
// 方式一:使用内置辅助方法(客户端排序)
var query = request.ApplySorting(_data.AsQueryable());
// 方式二:获取排序信息传给 API(服务端排序)
if (request.SortByColumn != null)
{
var sortField = request.SortByColumn.Title;
var sortDir = request.SortByAscending ? "asc" : "desc";
// 调用 API: /api/people?sortBy=Name&sortDir=asc
}
// 方式三:获取排序属性列表
var sortProperties = request.GetSortProperties();
// 返回 [{ PropertyName: "Name", Direction: Ascending }]
}
过滤
启用列过滤
设置 ShowColumnOptions="true" 在表头显示列选项菜单:
<SxDataGrid TGridItem="Person" Items="@_people" ShowColumnOptions="true">
<SxPropertyColumn Property="@(p => p.Name)" Title="姓名" />
<SxPropertyColumn Property="@(p => p.Age)" Title="年龄" />
</SxDataGrid>
过滤语法
用户在过滤输入框中可以使用以下语法:
| 类型 | 语法 | 示例 | 说明 |
|---|---|---|---|
| 文本 | value |
alice |
包含 |
| 文本 | =value |
=Alice |
精确匹配 |
| 文本 | value* |
ali* |
开头匹配 |
| 文本 | *value |
*son |
结尾匹配 |
| 数字 | n |
30 |
等于 |
| 数字 | >n |
>30 |
大于 |
| 数字 | >=n |
>=30 |
大于等于 |
| 数字 | <n |
<30 |
小于 |
| 数字 | <=n |
<=30 |
小于等于 |
| 数字 | n-m |
20-40 |
范围(包含边界) |
| 布尔 | true/yes/1 |
true |
真 |
| 布尔 | false/no/0 |
false |
假 |
过滤状态管理
使用 SxFilterState 管理过滤状态:
<SxDataGrid TGridItem="Person" Items="@_people"
FilterState="@_filterState"
OnFilterChanged="HandleFilterChanged">
...
</SxDataGrid>
<SxButton OnClick="ClearFilters">清除所有过滤</SxButton>
@code {
private SxFilterState _filterState = new();
private void HandleFilterChanged(SxFilterState state)
{
Console.WriteLine($"活跃过滤器数量: {state.Count}");
}
private void ClearFilters()
{
_filterState.ClearAll();
}
}
远程数据过滤
使用 ItemsProvider 时,过滤信息通过 request.Filters 传递:
private async ValueTask<SxGridItemsProviderResult<Person>> LoadDataAsync(
SxGridItemsProviderRequest<Person> request)
{
// 方式一:使用内置辅助方法(客户端过滤)
var query = request.ApplyFilters(_data.AsQueryable());
// 方式二:获取过滤信息传给 API(服务端过滤)
if (request.Filters?.Count > 0)
{
foreach (var filter in request.Filters.Where(f => f.IsEnabled))
{
// filter.PropertyName - 属性名,如 "Name", "Age"
// filter.FilterType - 过滤类型:String, Numeric, Boolean, Enum
if (filter is SxStringFilterDescriptor strFilter)
{
// strFilter.Value - 过滤值
// strFilter.Mode - Contains, Equals, StartsWith, EndsWith
}
else if (filter is SxNumericFilterDescriptor numFilter)
{
// numFilter.Value - 过滤值
// numFilter.Operator - Equals, GreaterThan, LessThan, Range 等
// numFilter.LowerBound, numFilter.UpperBound - 范围值
}
}
}
}
虚拟化滚动
对于大数据量,启用虚拟化只渲染可见行:
<SxDataGrid TGridItem="Person"
ItemsProvider="LoadDataAsync"
Virtualize="true"
OverscanCount="5">
...
</SxDataGrid>
| 参数 | 说明 |
|---|---|
Virtualize |
启用虚拟化滚动 |
OverscanCount |
预渲染的额外行数,提高滚动流畅度 |
列宽调整
<SxDataGrid TGridItem="Person" Items="@_people"
ResizableColumns="true"
ResizeOnAllRows="true"
ResizeType="DataGridResizeType.Discrete">
...
</SxDataGrid>
| 参数 | 说明 |
|---|---|
ResizableColumns |
启用列宽调整 |
ResizeOnAllRows |
在所有行显示调整手柄(默认仅表头) |
ResizeType |
Discrete(10px 步进)或 Exact(精确像素) |
行选择
使用 SxSelectColumn 添加选择列,支持单选和多选模式。
基本用法
<SxDataGrid TGridItem="Person" Items="@_people">
<SxSelectColumn TGridItem="Person"
SelectMode="DataGridSelectMode.Multiple"
@bind-SelectedItems="_selectedPeople" />
<SxPropertyColumn Property="@(p => p.Name)" Title="姓名" />
</SxDataGrid>
@code {
private ICollection<Person> _selectedPeople = new List<Person>();
}
选择模式
| SelectMode | 说明 |
|---|---|
Single |
单选,点击已选行取消选择 |
SingleSticky |
单选,点击已选行保持选择 |
Multiple |
多选,显示复选框 |
点击整行选择
设置 SelectFromEntireRow="true" 允许点击行的任意位置进行选择:
<SxDataGrid TGridItem="Person" Items="@_people">
<SxSelectColumn TGridItem="Person"
SelectMode="DataGridSelectMode.Multiple"
SelectFromEntireRow="true"
@bind-SelectedItems="_selectedPeople" />
...
</SxDataGrid>
远程数据模式下的行选择
SxSelectColumn 完全支持远程数据模式(ItemsProvider)和虚拟化滚动。选择操作不会触发数据重新加载,确保流畅的用户体验。
<SxDataGrid TGridItem="Person"
ItemsProvider="LoadDataAsync"
Virtualize="true">
<SxSelectColumn TGridItem="Person"
SelectMode="DataGridSelectMode.Multiple"
@bind-SelectedItems="_selectedPeople" />
<SxPropertyColumn Property="@(p => p.Name)" Title="姓名" Sortable="true" />
</SxDataGrid>
@code {
private ICollection<Person> _selectedPeople = new List<Person>();
private async ValueTask<SxGridItemsProviderResult<Person>> LoadDataAsync(
SxGridItemsProviderRequest<Person> request)
{
// 选择状态与数据加载完全独立
// 用户选择/取消选择不会触发此方法
var result = await _api.GetPeopleAsync(request.StartIndex, request.Count ?? 50);
return SxGridItemsProviderResult<Person>.From(result.Items, result.TotalCount);
}
}
SxSelectColumn 参数
| 参数名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
SelectMode |
DataGridSelectMode |
Multiple |
选择模式 |
SelectedItems |
ICollection<TGridItem> |
new List<T>() |
已选择的项目集合 |
SelectedItemsChanged |
EventCallback<ICollection<TGridItem>> |
- | 选择变化回调 |
SelectFromEntireRow |
bool |
false |
是否允许点击整行选择 |
Width |
string? |
"40px" |
选择列宽度 |
Title |
string? |
null |
列标题(多选时显示全选复选框) |
监听选择变化
<SxDataGrid TGridItem="Person" Items="@_people">
<SxSelectColumn TGridItem="Person"
SelectMode="DataGridSelectMode.Multiple"
@bind-SelectedItems="_selectedPeople"
SelectedItemsChanged="HandleSelectionChanged" />
...
</SxDataGrid>
@code {
private ICollection<Person> _selectedPeople = new List<Person>();
private void HandleSelectionChanged(ICollection<Person> selected)
{
Console.WriteLine($"已选择 {selected.Count} 项");
// 可以在这里执行批量操作逻辑
}
}
编程式控制选择
<SxButton OnClick="SelectAll">全选</SxButton>
<SxButton OnClick="ClearSelection">清空选择</SxButton>
@code {
private ICollection<Person> _selectedPeople = new List<Person>();
private List<Person> _allPeople = new();
private void SelectAll()
{
_selectedPeople = new List<Person>(_allPeople);
}
private void ClearSelection()
{
_selectedPeople = new List<Person>();
}
}
API
Parameters
| 参数名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
Items |
IQueryable<TGridItem>? |
null |
本地数据源 |
ItemsProvider |
SxGridItemsProvider<TGridItem>? |
null |
远程数据提供委托 |
Virtualize |
bool |
false |
是否启用虚拟化 |
OverscanCount |
int |
3 |
虚拟化预渲染行数 |
RowSize |
DataGridRowSize |
Small |
行高:Smaller(24px), Small(32px), Medium(44px), Large(58px) |
ShowHover |
bool |
true |
鼠标悬停高亮行 |
Striped |
bool |
false |
斑马纹(交替行颜色) |
ResizableColumns |
bool |
false |
允许调整列宽 |
ResizeOnAllRows |
bool |
false |
所有行显示调整手柄 |
ResizeType |
DataGridResizeType |
Discrete |
调整方式 |
ShowColumnOptions |
bool |
false |
显示列选项菜单 |
GenerateHeader |
GenerateHeaderOption |
Sticky |
表头生成:None, Default, Sticky |
GridTemplateColumns |
string? |
null |
自定义 CSS grid 列模板 |
Pagination |
SxPaginationState? |
null |
分页状态管理器 |
FilterState |
SxFilterState? |
null |
过滤状态管理器 |
ItemKey |
Func<TGridItem, object>? |
null |
行唯一键函数 |
RowClass |
Func<TGridItem, string?>? |
null |
行 CSS 类函数 |
RowStyle |
Func<TGridItem, string?>? |
null |
行样式函数 |
LoadingContent |
RenderFragment? |
null |
加载中内容 |
EmptyContent |
RenderFragment? |
null |
空数据内容 |
ErrorContent |
RenderFragment<Exception>? |
null |
错误内容 |
Column Parameters (SxPropertyColumn / SxTemplateColumn)
| 参数名 | 类型 | 描述 |
|---|---|---|
Property |
Expression<Func<TGridItem, TProp>> |
绑定的属性表达式 |
Title |
string? |
列标题 |
Width |
string? |
列宽,如 "150px", "2fr" |
MinWidth |
string? |
最小列宽 |
Align |
DataGridAlign |
对齐:Start, Center, End |
Sortable |
bool? |
是否可排序 |
IsDefaultSortColumn |
bool |
是否为默认排序列 |
InitialSortDirection |
SortDirection |
初始排序方向 |
Filtered |
bool? |
是否显示过滤图标 |
HeaderClass |
string? |
表头单元格 CSS 类 |
CellClass |
string? |
数据单元格 CSS 类 |
HeaderTooltip |
string? |
表头提示文本 |
CellTooltip |
Func<TGridItem, string?>? |
单元格提示文本函数 |
ColumnOptions |
RenderFragment? |
自定义列选项内容 |
Events
| 事件名 | 类型 | 描述 |
|---|---|---|
OnRowClick |
EventCallback<DataGridRowClickEventArgs<TGridItem>> |
点击行 |
OnRowDoubleClick |
EventCallback<DataGridRowClickEventArgs<TGridItem>> |
双击行 |
OnCellClick |
EventCallback<DataGridCellClickEventArgs<TGridItem>> |
点击单元格 |
OnRowFocus |
EventCallback<DataGridRowFocusEventArgs<TGridItem>> |
行获得焦点 |
OnSortChanged |
EventCallback<DataGridSortChangedEventArgs<TGridItem>> |
排序变化 |
OnFilterChanged |
EventCallback<SxFilterState> |
过滤变化 |
Methods
| 方法名 | 返回值 | 描述 |
|---|---|---|
RefreshDataAsync() |
Task |
刷新数据 |
SortByColumnAsync(column, direction) |
Task |
按指定列排序 |
ClearSortAsync() |
Task |
清除排序 |
ClearAllFilters() |
void |
清除所有过滤器 |
ItemsProvider 请求结构
public readonly struct SxGridItemsProviderRequest<TGridItem>
{
public int StartIndex { get; } // 起始索引
public int? Count { get; } // 请求数量
public SxColumnBase<TGridItem>? SortByColumn { get; } // 排序列
public bool SortByAscending { get; } // 是否升序
public IReadOnlyList<SxFilterDescriptor>? Filters { get; } // 过滤器
public CancellationToken CancellationToken { get; } // 取消令牌
// 辅助方法
public IQueryable<TGridItem> ApplyFilters(IQueryable<TGridItem> source);
public IQueryable<TGridItem> ApplySorting(IQueryable<TGridItem> source);
public IReadOnlyCollection<SxSortedProperty> GetSortProperties();
}
public readonly struct SxGridItemsProviderResult<TGridItem>
{
public ICollection<TGridItem> Items { get; }
public int TotalItemCount { get; }
public static SxGridItemsProviderResult<TGridItem> From(
ICollection<TGridItem> items, int totalItemCount);
}
完整示例
@page "/data-grid-demo"
<SxDataGrid TGridItem="Employee"
ItemsProvider="LoadEmployeesAsync"
Virtualize="true"
ShowColumnOptions="true"
ShowHover="true"
Striped="true"
ResizableColumns="true"
RowSize="DataGridRowSize.Medium"
FilterState="@_filterState"
OnRowClick="HandleRowClick"
OnSortChanged="HandleSortChanged">
<SxSelectColumn TGridItem="Employee"
SelectMode="DataGridSelectMode.Multiple"
@bind-SelectedItems="_selectedEmployees" />
<SxPropertyColumn Property="@(e => e.Id)" Title="ID" Width="80px" />
<SxPropertyColumn Property="@(e => e.Name)" Title="姓名"
Sortable="true"
IsDefaultSortColumn="true" />
<SxPropertyColumn Property="@(e => e.Department)" Title="部门"
Sortable="true" />
<SxPropertyColumn Property="@(e => e.Salary)" Title="薪资"
Sortable="true"
Align="DataGridAlign.End" />
<SxPropertyColumn Property="@(e => e.IsActive)" Title="状态">
<Template>
@if (context.IsActive)
{
<SxBadge Color="Color.Success">在职</SxBadge>
}
else
{
<SxBadge Color="Color.Neutral">离职</SxBadge>
}
</Template>
</SxPropertyColumn>
<SxTemplateColumn Title="操作" Width="120px">
<Template>
<SxButton Size="ControlSize.Small" OnClick="() => Edit(context)">
编辑
</SxButton>
</Template>
</SxTemplateColumn>
</SxDataGrid>
<div>已选择: @_selectedEmployees.Count 人</div>
@code {
private SxFilterState _filterState = new();
private ICollection<Employee> _selectedEmployees = new List<Employee>();
private async ValueTask<SxGridItemsProviderResult<Employee>> LoadEmployeesAsync(
SxGridItemsProviderRequest<Employee> request)
{
// 模拟 API 调用
await Task.Delay(100);
var query = _allEmployees.AsQueryable();
query = request.ApplyFilters(query);
query = request.ApplySorting(query);
var total = query.Count();
var items = query.Skip(request.StartIndex)
.Take(request.Count ?? 50)
.ToList();
return SxGridItemsProviderResult<Employee>.From(items, total);
}
private void HandleRowClick(DataGridRowClickEventArgs<Employee> args)
{
Console.WriteLine($"点击: {args.Item.Name}");
}
private void HandleSortChanged(DataGridSortChangedEventArgs<Employee> args)
{
Console.WriteLine($"排序: {args.Column?.Title} {(args.Ascending ? "↑" : "↓")}");
}
private void Edit(Employee employee)
{
// 编辑逻辑
}
}