受欢迎的博客标签

QuickGrid

Published

.net 10 blazor server QuickGrid添加,更新,删除,过滤,排序分页

 

Data sources

QuickGrid supports the following data sources

In-memory IQueryable
EF-Core IQueryable
Remote data

DataProvider - Items

<QuickGrid Items="Stocks">

全部交给 QuickGrid
  ↓
QuickGrid 自己处理

 

DataProvider - ItemsProvider

<QuickGrid ItemsProvider="LoadStocks">

QuickGrid
    ↓
“我现在需要第 100~119 条”
    ↓
LoadStocks()
    ↓
返回 20 条

从服务器上基于 JSON 的 Web API 获取数据是一项常见要求。

要提取当前页/数据视区所需的数据,并在服务器上应用排序或筛选规则,使用 ItemsProvider 参数。

提供与 GridItemsProvider<TGridItem> 委托类型匹配的回调,其中 TGridItem 是网格中显示的数据类型。 该回调被赋予一个 GridItemsProviderRequest<TGridItem> 类型的参数,它指定了要返回的数据的起始索引、最大行计数和排序顺序。 除了返回匹配项之外,分页和虚拟化还需要总项计数 (totalItemCount) 才能正常运行。

Sorting and remote data (ItemsProvider)

QuickGrid 搭配 ItemsProvider 时属于服务端排序/分页模式。需要利用 request.SortByColumn 获取用户点击的列,并将排序依据传递给数据库 SQL(ORDER BY),前端网格无法对后台仅返回的单页数据进行正确内存排序。

 

Virtualize

 

Virtualize="true":几千~几万条 JSON 股票数据,QuickGrid 不一次渲染全部行,而是滚动时上下滚动,动态加载/渲染。

Pagination:第 1 页、第 2 页、第 3 页

 

每个页面必须加上 @rendermode InteractiveServer

所有页面都支持 Interactive

在 Components/Routes.razor 中统一指定。

<Routes @rendermode="InteractiveServer" />

 

QuickGrid-ItemsProvider-Paginator.razor

<h3>QuickGrid_ItemsProvider_Paginator</h3>

@page "/StockDataPhysicalDisk/QuickGrid_ItemsProvider_Paginator"


@using Microsoft.AspNetCore.Components.QuickGrid
@using System.Timers
@rendermode InteractiveServer
@implements IDisposable



<PageTitle>QuickGrid 分页示例</PageTitle>

<h3>员工名册 (分页模式)</h3>

<!-- 1. 查找功能区 -->
<div class="search-container mb-3 d-flex align-items-center gap-3">
    <div>
        <label for="searchBox">查找: </label>
        <input id="searchBox"
               type="text"
               class="form-control d-inline-block w-auto"
               placeholder="按姓名或职位搜索..."
               @bind="SearchTerm"
               @bind:event="oninput" />
    </div>

    <div>
        <label for="pageSize">每页显示: </label>
        <select id="pageSize" class="form-select d-inline-block w-auto" @bind="pagination.ItemsPerPage">
            <option value="5">5 条</option>
            <option value="10">10 条</option>
            <option value="20">20 条</option>
            <option value="50">50 条</option>
        </select>
    </div>
</div>

<!-- 2. 数据表格区 (绑定了 Pagination) -->
<div class="grid-container border rounded">
    <QuickGrid TGridItem="Employee"
               ItemsProvider="@employeeProvider"
               Pagination="@pagination"
               @ref="grid">

        <PropertyColumn Property="@(e => e.Id)" Title="编号" Sortable="true" />
        <PropertyColumn Property="@(e => e.Name)" Title="姓名" Sortable="true" />
        <PropertyColumn Property="@(e => e.Position)" Title="职位" Sortable="true" />
        <PropertyColumn Property="@(e => e.Salary)" Title="薪水" Format="C0" Sortable="true" />
        <PropertyColumn Property="@(e => e.JoinDate)" Title="入职日期" Format="yyyy-MM-dd" Sortable="true" />

    </QuickGrid>
</div>

<!-- 3. 分页控件区 -->
<div class="mt-3">
    @* Paginator 是 QuickGrid 提供的配套分页UI组件 *@
    <Paginator State="@pagination" />
</div>

@code {
    // 组件引用和状态
    private QuickGrid<Employee>? grid;
    private GridItemsProvider<Employee>? employeeProvider;
    private IQueryable<Employee> fakeDatabase = default!;

    // 分页状态管理 (默认每页 10 条)
    private PaginationState pagination = new PaginationState { ItemsPerPage = 10 };

    // 搜索防抖定时器
    private Timer? debounceTimer;
    private string _searchTerm = string.Empty;
    private string SearchTerm
    {
        get => _searchTerm;
        set
        {
            _searchTerm = value;
            debounceTimer?.Stop();
            debounceTimer?.Start();
        }
    }

    protected override void OnInitialized()
    {
        // 模拟生成 1,000 条测试数据作为数据库
        var list = new List<Employee>();
        var positions = new[] { "前端开发", "后端开发", "产品经理", "测试工程师", "UI设计师", "运维工程师" };
        var random = new Random();

        for (int i = 1; i <= 1000; i++)
        {
            list.Add(new Employee
            {
                Id = i,
                Name = $"员工_{i:D4}",
                Position = positions[random.Next(positions.Length)],
                Salary = random.Next(8000, 40000),
                JoinDate = DateTime.Now.AddDays(-random.Next(1, 3000))
            });
        }
        fakeDatabase = list.AsQueryable();

        // 订阅分页变化事件:当用户改变“每页条数”时,可能需要回到第一页
        pagination.TotalItemCountChanged += (sender, args) => StateHasChanged();

        // 初始化防抖定时器 (300毫秒)
        debounceTimer = new Timer(300);
        debounceTimer.AutoReset = false;
        debounceTimer.Elapsed += async (sender, args) =>
        {
            await InvokeAsync(async () =>
            {
                // 搜索条件改变时,应当将页码重置为第一页
                await pagination.SetCurrentPageIndexAsync(0);
                if (grid != null)
                {
                    await grid.RefreshDataAsync();
                }
            });
        };

        // 核心:配置 ItemsProvider 委托
        employeeProvider = async request =>
        {
            var query = fakeDatabase;

            // A. 执行过滤 (查找)
            if (!string.IsNullOrWhiteSpace(_searchTerm))
            {
                query = query.Where(e => e.Name.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase) ||
                                         e.Position.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase));
            }

            // B. 执行排序
            if (request.SortByColumn != null)
            {
                var sortTitle = request.SortByColumn.Title;
                var isAsc = request.SortByAscending;

                query = sortTitle switch
                {
                    "编号" => isAsc ? query.OrderBy(e => e.Id) : query.OrderByDescending(e => e.Id),
                    "姓名" => isAsc ? query.OrderBy(e => e.Name) : query.OrderByDescending(e => e.Name),
                    "职位" => isAsc ? query.OrderBy(e => e.Position) : query.OrderByDescending(e => e.Position),
                    "薪水" => isAsc ? query.OrderBy(e => e.Salary) : query.OrderByDescending(e => e.Salary),
                    "入职日期" => isAsc ? query.OrderBy(e => e.JoinDate) : query.OrderByDescending(e => e.JoinDate),
                    _ => query
                };
            }
            else
            {
                query = query.OrderBy(e => e.Id);
            }

            // C. 必须计算总数 (Paginator 需要靠这个计算总共有多少页)
            // 在真实数据库中请使用 Entity Framework 的 await query.CountAsync()
            var totalCount = query.Count();

            // D. 获取当前页的数据切片 (Skip 和 Take)
            // 当 QuickGrid 绑定了 PaginationState 时,request.StartIndex 和 request.Count 会由组件自动计算
            var pagedData = query.Skip(request.StartIndex)
                                 .Take(request.Count ?? pagination.ItemsPerPage)
                                 .ToList(); // 真实数据库请使用 await query.ToListAsync()

            // 模拟数据库查询耗时
            await Task.Delay(100);

            return GridItemsProviderResult.From(items: pagedData, totalItemCount: totalCount);
        };
    }

    public void Dispose()
    {
        debounceTimer?.Dispose();
    }

    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Position { get; set; } = string.Empty;
        public decimal Salary { get; set; }
        public DateTime JoinDate { get; set; }
    }
}

 

QuickGrid_ItemsProvider_Virtualize

p:

QuickGrid_ItemsProvider_Virtualize  blazor .net 10 ,完整的示例代码,包括排序,查找

 

run ok

<h3>QuickGrid_ItemsProvider_Virtualize</h3>

@page "/StockDataPhysicalDisk/QuickGrid_ItemsProvider_Virtualize"
@using Microsoft.AspNetCore.Components.QuickGrid
@using System.Timers

@rendermode InteractiveServer

<PageTitle>QuickGrid 虚拟化示例</PageTitle>

<h3>员工名册 (总数据: 10,000 条)</h3>

<div class="search-container mb-3">
    <label for="searchBox">查找: </label>
    <!-- 使用 oninput 绑定实现实时搜索,通过 Timer 实现防抖 (Debounce) -->
    <input id="searchBox"
           type="text"
           class="form-control d-inline-block w-auto"
           placeholder="按姓名或职位搜索..."
           @bind="SearchTerm"
           @bind:event="oninput" />
</div>

@*
   注意:开启 Virtualize="true" 时,必须为外层容器或 QuickGrid
   指定固定的高度,并设置 overflow-y: auto,否则虚拟化无法计算可视区域!
*@
<div class="grid-container" style="height: 500px; overflow-y: auto; border: 1px solid #ccc;">
    <QuickGrid TGridItem="Employee"
               ItemsProvider="@employeeProvider"
               Virtualize="true"
               ItemSize="45"
               @ref="grid">

        <PropertyColumn Property="@(e => e.Id)" Title="编号" Sortable="true" />
        <PropertyColumn Property="@(e => e.Name)" Title="姓名" Sortable="true" />
        <PropertyColumn Property="@(e => e.Position)" Title="职位" Sortable="true" />
        <PropertyColumn Property="@(e => e.Salary)" Title="薪水" Format="C0" Sortable="true" />
        <PropertyColumn Property="@(e => e.JoinDate)" Title="入职日期" Format="yyyy-MM-dd" Sortable="true" />

    </QuickGrid>
</div>

@code {
    // 1. 组件引用和状态变量
    private QuickGrid<Employee>? grid;
    private GridItemsProvider<Employee>? employeeProvider;
    private IQueryable<Employee> fakeDatabase = default!;

    // 2. 搜索防抖定时器
    private Timer? debounceTimer;
    private string _searchTerm = string.Empty;
    private string SearchTerm
    {
        get => _searchTerm;
        set
        {
            _searchTerm = value;
            // 每次输入改变时,重启定时器,避免频繁请求数据库
            debounceTimer?.Stop();
            debounceTimer?.Start();
        }
    }

    protected override void OnInitialized()
    {
        // 模拟生成 10,000 条测试数据作为数据库
        var list = new List<Employee>();
        var positions = new[] { "前端开发", "后端开发", "产品经理", "测试工程师", "UI设计师" };
        var random = new Random();

        for (int i = 1; i <= 10000; i++)
        {
            list.Add(new Employee
            {
                Id = i,
                Name = $"员工_{i}",
                Position = positions[random.Next(positions.Length)],
                Salary = random.Next(10000, 50000),
                JoinDate = DateTime.Now.AddDays(-random.Next(1, 3650))
            });
        }
        fakeDatabase = list.AsQueryable();

        // 初始化防抖定时器 (300毫秒)
        debounceTimer = new Timer(300);
        debounceTimer.AutoReset = false;
        debounceTimer.Elapsed += async (sender, args) =>
        {
            await InvokeAsync(async () =>
            {
                if (grid != null)
                {
                    await grid.RefreshDataAsync(); // 触发 ItemsProvider 重新加载数据
                }
            });
        };

        // 3. 核心:配置 ItemsProvider 委托
        employeeProvider = async request =>
        {
            var query = fakeDatabase;

            // A. 执行过滤 (查找)
            if (!string.IsNullOrWhiteSpace(_searchTerm))
            {
                query = query.Where(e => e.Name.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase) ||
                                         e.Position.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase));
            }

            // B. 执行排序
            if (request.SortByColumn != null)
            {
                // QuickGrid 会将列的 Title 传递过来,我们可以通过 Title 或 Property 名字来进行排序
                var sortTitle = request.SortByColumn.Title;
                var isAsc = request.SortByAscending;

                query = sortTitle switch
                {
                    "编号" => isAsc ? query.OrderBy(e => e.Id) : query.OrderByDescending(e => e.Id),
                    "姓名" => isAsc ? query.OrderBy(e => e.Name) : query.OrderByDescending(e => e.Name),
                    "职位" => isAsc ? query.OrderBy(e => e.Position) : query.OrderByDescending(e => e.Position),
                    "薪水" => isAsc ? query.OrderBy(e => e.Salary) : query.OrderByDescending(e => e.Salary),
                    "入职日期" => isAsc ? query.OrderBy(e => e.JoinDate) : query.OrderByDescending(e => e.JoinDate),
                    _ => query
                };
            }
            else
            {
                // 默认排序
                query = query.OrderBy(e => e.Id);
            }

            // C. 获取过滤后的总行数 (用于滚动条高度计算)
            var totalCount = query.Count(); // 如果使用的是 EF Core,这里应使用 await query.CountAsync()

            // D. 执行分页/虚拟化切片 (Skip 和 Take)
            // request.StartIndex 和 request.Count 由 Virtualize 机制自动计算并提供
            var pagedData = query.Skip(request.StartIndex)
                                 .Take(request.Count ?? 50)
                                 .ToList(); // 真实数据库中请使用 await query.ToListAsync()

            // 模拟网络延迟 (可选,仅为展示加载效果)
            await Task.Delay(100);

            // E. 返回结果
            return GridItemsProviderResult.From(items: pagedData, totalItemCount: totalCount);
        };
    }

    public void Dispose()
    {
        debounceTimer?.Dispose();
    }

    // 数据模型
    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Position { get; set; } = string.Empty;
        public decimal Salary { get; set; }
        public DateTime JoinDate { get; set; }
    }
}

 

Custom styles

在 Blazor (.NET 10) 中控制 QuickGrid 的行/单元格背景色以及鼠标悬停(Hover)、光标手型(Cursor)效果

 

使用 Bootstrap 的 Class="table" 样式

<QuickGrid Class="table" Items="characters" Pagination="pagination" ">

直接使用 Bootstrap 自带的悬停类

直接使用 Bootstrap 自带的斑马纹

<QuickGrid Class="table table-striped table-hover" Items="characters" Pagination="pagination" >

 

wwwroot/css/app.css

 

 

使用 ItemClass 动态条件样式

<QuickGrid Items="@items" ItemClass="@GetRowClass">
    <PropertyColumn Property="@(p => p.Id)" Title="ID" />
    <PropertyColumn Property="@(p => p.Name)" Title="名称" />
    <PropertyColumn Property="@(p => p.Status)" Title="状态" />
</QuickGrid>

@code {
    private IQueryable<ItemData> items = ...;

    // 根据行的属性动态返回 CSS 类名
    private string GetRowClass(ItemData item) => item.Status switch
    {
        "Warning" => "row-warning",
        "Error"   => "row-error",
        _         => string.Empty
    };
}

 

Theme="corporate"

自定义 CSS Variables 实现整体主题

<QuickGrid Items="@Data.People" Theme="corporate">

https://www.yogihosting.com/blazor-quickgrid/

https://aspnet.github.io/quickgridsamples/styling

 

QuickGrid使用了 Bootstrap 的 Class="table" 样式

 

 

useful links

blazor 

https://learn.microsoft.com/zh-cn/aspnet/core/blazor/components/quickgrid?view=aspnetcore-10.0&tabs=visual-studio-code

 

.net 8 quickgrid samples

https://aspnet.github.io/quickgridsamples/getstarted