实现点击标头按所在列值进行排序,是一个非常有用的功能,其他的UI一般搞得非常复杂,添加标志图标什么的,使得本就不宽裕的表格更加拥挤。我的思路是,点击所在列的标头部位,传递标头值,然后根据标头值来改变查询语句。因为并不是所有列都需要排序,需要有一个特征字符。
以下是Table.razor
@typeparam T
<style>
th {
text-align: center;
font-weight: 500;
font-size: 16px;
white-space: nowrap;
}
td {
font-size: 14.66px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 25px;
}
</style>
<div>
@ToolBarContent
</div>
<div>
<table class="table table-bordered table-hover table-sm table-striped">
<thead>
<tr >
@foreach (var item in HeaderTitles)
{
if (IsSortabledColonum((string)item))
{
<th @onclick="()=>HeaderClick((string)item)" style="color:blue;cursor:pointer;">@HeaderTitle((string)item)</th>
}
else
{
<th>@item</th>
}
}
</tr>
</thead>
<tbody>
@if (PageData.HasData)
{
foreach (var item in PageData)
{
<tr @onclick="()=>RowClick(item)" >
@RowContent(item)
</tr>
}
}
else
{
<tr>
<td colspan="@columnsCount" class="text-center">暂无数据</td>
</tr>
}
</tbody>
</table>
<div class="row col-12 justify-content-end">
<p style="width:auto;">
<label class="form-label">
设置行数:
<select class="form-control-sm mt-0" @onchange="PageSizeChanged">
<option value="10">10</option>
<option value="15" selected>15</option>
<option value="20">20</option>
<option value="25">25</option>
<option value="30">30</option>
</select>
</label>
</p>
<button class="btn btn-primary btn-sm" disabled="@PageData.NoPreviousPage" @onclick="()=>GotoPage(1)" style="width:80px;height:30px;">首页</button>
<button class="btn btn-primary btn-sm" disabled="@PageData.NoPreviousPage" @onclick="PreviousPage" style="width:80px;height:30px;">上一页</button>
<button class="btn btn-primary btn-sm" disabled="@PageData.NoNextPage" @onclick="NextPage" style="width:80px;height:30px;">下一页</button>
<button class="btn btn-primary btn-sm" disabled="@PageData.NoNextPage" @onclick="()=>GotoPage(PageData.TotalPages)" style="width:60px;height:30px;">尾页</button>
<input type="number" style="width:60px;height:30px;" @bind-value="pageNum" />
<button class="btn btn-primary btn-sm" disabled="@PageData.NoGoto" @onclick="()=>GotoPage(pageNum)" style="width:60px;height:30px;">跳转</button>
<p class="mx-4" style="width:130px;height:30px;">总页数:@PageData.TotalPages</p>
<p style="width:130px;height:30px;">当前页:@pageIndex</p>
<p style="width:130px;height:30px;">总条数:@PageData.ItemCount</p>
</div>
</div>
Table.razor.cs
public partial class Table<T> : ComponentBase, IDisposable where T : class
{
private int pageIndex = 1;
private int pageSize = 15;
private int pageNum;
private IQueryable<T>? query;
private int columnsCount;//设定列数
private PaginatedList<T> PageData { get; set; } = new(new List<T>(), 0, 1, 1);
[Parameter]
public RenderFragment? ToolBarContent { get; set; }
[Parameter]
public required Array HeaderTitles { get; set; }
[Parameter]
public required RenderFragment<T> RowContent { get; set; }
[Parameter]
public RenderFragment<RenderFragment>? RowTemplate { get; set; }
[Parameter]
public EventCallback<T> OnRowClick { get; set; }
[Parameter]
public EventCallback<string> OnHeaderClick { get; set; }
public async void RowClick(T item)
{
if (OnRowClick.HasDelegate)
await OnRowClick.InvokeAsync(item);
}
public async void HeaderClick(string title)
{
if (OnHeaderClick.HasDelegate)
await OnHeaderClick.InvokeAsync(title[..^1]);
}
protected override void OnParametersSet()
{
base.OnParametersSet();
columnsCount = HeaderTitles.Length;
}
public int PageSize
{
get => pageSize;
set
{
if (value != pageSize)
{
pageSize = value;
OnChange();
}
}
}
public IQueryable<T> Query
{
get => query ?? (new List<T>()).AsQueryable();
set
{
if (value != query)
{
query = value;
OnChange();
}
}
}
private void OnChange()
{
PageData = PaginatedList<T>.Create(Query, pageIndex, PageSize);
StateHasChanged();
}
private void PreviousPage()
{
pageIndex--;
OnChange();
}
private void NextPage()
{
pageIndex++;
OnChange();
}
private void GotoPage(int pageNum)
{
if (pageNum > 0 && pageNum != pageIndex && pageNum <= PageData.TotalPages)
{
pageIndex = pageNum;
OnChange();
}
}
private void PageSizeChanged(ChangeEventArgs e)
{
if (e.Value != null)
{
PageSize = int.Parse((string)e.Value);
}
}
private string HeaderTitle(string title)
{
if (title.EndsWith('|'))
{
return title[..^1];
}
return title;
}
private bool IsSortabledColonum(string title)
{
return title.EndsWith('|'); //点击标头可以排序等操作。
}
protected virtual void Dispose(bool disposing)
{
}
void IDisposable.Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
还是那个天气表格:
@page "/fetchdata"
@using MySoft.Data
@inject WeatherForecastService ForecastService
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
<Table @ref="myTable" HeaderTitles="headerTitles" T="WeatherForecast" OnHeaderClick="OnSort">
<RowContent>
<td>@context.Date.ToShortDateString()</td>
<td>@context.TemperatureC</td>
<td>@context.TemperatureF</td>
<td>@context.Summary</td>
</RowContent>
</Table>
@code {
private WeatherForecast[] forecasts = new WeatherForecast[] { };
private YtTable<WeatherForecast>? myTable;
private Array headerTitles = new string[]
{
"Date|","Temp. (C)","Temp. (F)","Summary|"
};
private bool dateSort, summarySort;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now));
}
private IQueryable<WeatherForecast> Query
{
get
{
if (myTable != null)
{
return myTable.Query;
}
return (new List<WeatherForecast>()).AsQueryable();
}
set
{
if (myTable != null)
{
myTable.Query = value;
}
}
}
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
if (firstRender)
{
Query = forecasts.AsQueryable();
}
}
private void OnSort(string title)
{
switch (title)
{
case "Date":
sortOnDate();
break;
case "Summary":
sortOnSummary();
break;
default:
break;
}
}
private void sortOnDate()
{
dateSort = !dateSort;
if (dateSort)
{
Query = Query.OrderBy(o => o.Date);
}
else
{
Query = Query.OrderByDescending(o => o.Date);
}
}
private void sortOnSummary()
{
summarySort = !summarySort;
if (summarySort)
{
Query = Query.OrderBy(o => o.Summary);
}
else
{
Query = Query.OrderByDescending(o => o.Summary);
}
}
}
效果: