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)
    {
        // 编辑逻辑
    }
}

参考设计 (References)