feat(templatetable): update ui classes and query for table (#2549)

* feat:添加参数控制部分功能显示和内部显示样式;支持表格自定义列展示;修复cubejs模式下的查询排序条件拼接和解析问题

* feat: update custom cols render
This commit is contained in:
Qinyouzeng
2025-09-11 10:48:59 +08:00
committed by GitHub
parent bbfd41d381
commit 848802ec35
23 changed files with 805 additions and 446 deletions

View File

@@ -0,0 +1,19 @@
namespace Masa.Blazor.Components.TemplateTable.Contracts;
public static class TemplateTableEnumTypeConvert
{
public static ExpectedType ConvertToExpectedType(this ColumnType columnType)
{
return columnType switch
{
ColumnType.Date => ExpectedType.DateTime,
ColumnType.Progress => ExpectedType.Float,
ColumnType.Number => ExpectedType.Float,
ColumnType.Rating => ExpectedType.Float,
ColumnType.Switch => ExpectedType.Boolean,
ColumnType.Checkbox => ExpectedType.Boolean,
ColumnType.RowSelect => ExpectedType.Boolean,
_ => ExpectedType.String,
};
}
}

View File

@@ -1,15 +1,15 @@
using System.Runtime.CompilerServices;
using GraphQL;
using GraphQL;
using GraphQL.Client.Abstractions.Utilities;
using GraphQL.Client.Http;
using Masa.Blazor.Components.TemplateTable.Abstractions;
using Masa.Blazor.Components.TemplateTable.Contracts;
using System.Runtime.CompilerServices;
using GraphQLClient = GraphQL.Client.Abstractions.IGraphQLClient;
[assembly: InternalsVisibleTo("Masa.Blazor.Components.TemplateTable.Test")]
namespace Masa.Blazor.Components.TemplateTable.Cubejs.GraphQL;
public class CubejsGraphQLClient(GraphQLHttpClient httpClient) : IGraphQLClient
public class CubejsGraphQLClient(GraphQLClient httpClient) : IGraphQLClient
{
public async Task<QueryResult> QueryAsync(QueryRequest request)
{
@@ -187,6 +187,11 @@ public class CubejsGraphQLClient(GraphQLHttpClient httpClient) : IGraphQLClient
expected = "false";
type = ExpectedType.Boolean;
break;
case StandardFilter.Set:
filter = CubejsFilter.Set;
expected = "true";
type = ExpectedType.Boolean;
break;
case StandardFilter.True:
filter = CubejsFilter.Equals;
expected = "true";

View File

@@ -74,7 +74,7 @@
input = @<NumberConfigInput Value="@_column.Config" OnUpdate="HandleOnConfigUpdate"/>;
break;
case ColumnType.Link:
input = @<LInkConfigInput Value="@_column.Config" OnUpdate="HandleOnConfigUpdate"/>;
input = @<LinkConfigInput Value="@_column.Config" OnUpdate="HandleOnConfigUpdate"/>;
break;
}

View File

@@ -1,3 +1,3 @@
namespace Masa.Blazor.Components.TemplateTable;
public record CustomCellContext(string ColumnId, JsonElement Value, IReadOnlyDictionary<string, JsonElement> Data);
public record CustomCellContext(string ColumnId, JsonElement Value, IReadOnlyDictionary<string, JsonElement> Data, RenderFragment @Default);

View File

@@ -5,14 +5,60 @@ public static class IReadOnlyDictionaryExtensions
public static bool TryGetValueWithCaseInsensitiveKey(this IReadOnlyDictionary<string, JsonElement> dict, string key,
out string? value)
{
var k = dict.Keys.FirstOrDefault(u => u.Equals(key, StringComparison.OrdinalIgnoreCase));
if (k == null)
{
value = default;
return false;
}
value = dict[k].GetString();
if (!key.Contains('.'))
{
var k = dict.Keys.FirstOrDefault(u => u.Equals(key, StringComparison.OrdinalIgnoreCase));
if (k == null)
{
value = default;
return false;
}
value = dict[k].GetString();
}
else
{
value = GetNestedValue(dict, key);
}
return true;
}
private static string? GetNestedValue(IReadOnlyDictionary<string, JsonElement> dict, string key)
{
var parts = key.Split('.');
var index = 0;
JsonElement? temp = null;
do
{
string? k;
if (temp.HasValue)
{
(k, temp) = GetObjectValue(parts[index], temp);
}
else
{
k = dict.Keys.FirstOrDefault(u => u.Equals(parts[index], StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(k))
temp = dict[k];
}
if (k == null)
return null;
index++;
}
while (parts.Length - index > 0);
return temp?.GetString();
}
private static (string?, JsonElement?) GetObjectValue(string key, JsonElement? value)
{
if (value == null || value.Value.ValueKind != JsonValueKind.Object)
return (null, null);
var item = value.Value.EnumerateObject()
.FirstOrDefault(i => i.Name.Equals(key, StringComparison.OrdinalIgnoreCase));
return item.Name != null ? (item.Name, item.Value) : (null, null);
}
}

View File

@@ -1,25 +1,27 @@
@namespace Masa.Blazor.Components.TemplateTable.FilterDialogs
@inject I18n i18N;
<PModal @bind-Value="dialog"
Width="668"
<PModal @bind-Value="Show"
Width="@Width"
BodyStyle="min-height: 180px"
Title="Filters"
Title="@i18N.T("Filters")"
Persistent
OnCancel="@HandleOnCancel"
OnSave="HandleOnSave">
@for (int i = 0; i < _filters.Count; i++)
{
var index = i;
var filter = _filters[index];
if (filter.Persistent) continue;
<div class="d-flex align-center mb-2">
<div style="width: 86px; flex: none;"
class="mr-2 text-right">
@if (index == 0)
{
<span>When</span>
<span>@i18N.T("When")</span>
}
else if (index == 1)
@* else if (index == 1)
{
<MSelect @bind-Value="@_operator"
Items="@Operators"
@@ -30,18 +32,19 @@
Outlined
HideDetails="@true">
</MSelect>
}
} *@
else
{
<span>@_operator</span>
<span>@i18N.T(@_operator)</span>
}
</div>
<MSelect @bind-Value="@filter.ColumnId"
Placeholder="@i18N.T("ColumnId")"
Items="_computedColumns"
ItemText="u => u.Name"
ItemValue="u => u.Id"
Label="Field"
Label="@i18N.T("ColumnId")"
TItem="ColumnInfo"
TItemValue="string"
TValue="string"
@@ -50,65 +53,92 @@
Dense
Filled
HideDetails="@true"
Readonly="filter.Persistent"
OnSelect="@filter.OnSelect">
</MSelect>
<MSelect @bind-Value="@filter.Func"
<MSelect @bind-Value="@filter.Func" Placeholder="@i18N.T("Func")"
Items="@filter.FuncList"
ItemText="u => u.ToString()"
ItemText="@(u => i18N.T($"SDF_{u}"))"
ItemValue="u => u"
Label="Func"
Label="@i18N.T("Func")"
Class="mr-2"
Style="max-width: 150px"
Dense
Filled
Readonly="filter.Persistent"
HideDetails="@true">
</MSelect>
@if ((filter.Func is StandardFilter.Equals or StandardFilter.NotEquals) && filter.SelectOptions is not null)
@if ((filter.Func is StandardFilter.Equals or StandardFilter.NotEquals or StandardFilter.Contains or StandardFilter.NotContains) && filter.SelectOptions is not null)
{
<MSelect @bind-Value="@filter.Expected"
Items="@filter.SelectOptions"
ItemText="u => u.Label"
ItemValue="u => u.Value"
Label="Expected"
Class="mr-2"
Style="max-width: 150px"
Dense
Filled
HideDetails="@true">
</MSelect>
}
else if (!_noExpectedFuncs.Contains(filter.Func))
{
if (filter.Type == ExpectedType.DateTime)
bool isList = filter.Func is StandardFilter.Contains or StandardFilter.NotContains;
if (isList)
{
DateTime? date = DateTime.TryParse(filter.Expected, out var dt) ? dt : null;
<PDateDigitalClockPicker Value="@date"
ValueChanged="@(v => filter.Expected = (v.ToString() ?? string.Empty))"
TValue="DateTime?"
ViewType="DateTimePickerViewType.Desktop"
TimeFormat="TimeFormat.Hr24"
MultiSection
UseSeconds>
<PDefaultDateTimePickerActivator Label="Expected"
Filled
Format="yyyy-MM-dd HH:mm:ss"/>
</PDateDigitalClockPicker>
List<string> values = [.. filter.Expected.Replace('[', ' ').Replace(']', ' ').Replace('"', ' ').Split(',').Select(s => s.Trim())];
<MAutocomplete Value="values"
ValueChanged="(List<string> values) => ValueChange(index, values)"
Clearable
Multiple
Items="@filter.SelectOptions"
ItemText="u => u.Label"
ItemValue="u => u.Value"
Label="@i18N.T("Expected")"
Placeholder="@i18N.T("Expected")"
Color="primary"
Class="mr-2 m-input--dense-48"
Style="max-width: 150px"
Dense
Filled
Readonly="filter.Persistent"
HideDetails="@("auto")">
</MAutocomplete>
}
else
{
<MTextField @bind-Value="@filter.Expected"
Label="Expected"
Class="mr-2"
Style="max-width: 150px"
Filled
Dense
HideDetails="@true">
</MTextField>
<MAutocomplete @bind-Value="@filter.Expected"
Placeholder="@i18N.T("Expected")"
Items="@filter.SelectOptions"
ItemText="u => u.Label"
ItemValue="u => u.Value"
Label="@i18N.T("Expected")"
Class="mr-2 m-input--dense-48"
Style="max-width: 150px"
Dense
Filled
Readonly="filter.Persistent"
HideDetails="@("auto")">
</MAutocomplete>
}
}
else if (filter.Type == ExpectedType.DateTime && (filter.Func is not StandardFilter.Set && filter.Func is not StandardFilter.NotSet))
{
DateTime? date = DateTime.TryParse(filter.Expected, out var dt) ? dt : null;
<PDateDigitalClockPicker Value="@date"
ValueChanged="@(v => filter.Expected = (v.ToString() ?? string.Empty))"
TValue="DateTime?"
ViewType="DateTimePickerViewType.Desktop"
TimeFormat="TimeFormat.Hr24"
MultiSection
UseSeconds>
<PDefaultDateTimePickerActivator Label="Expected"
Filled
Format="yyyy-MM-dd HH:mm:ss" />
</PDateDigitalClockPicker>
}
else if (!_noExpectedFuncs.Contains(filter.Func))
{
<MTextField @bind-Value="@filter.Expected"
Placeholder="@i18N.T("Expected")"
Readonly="filter.Persistent"
Label="@i18N.T("Expected")"
Class="mr-2 m-input--dense-48"
Style="max-width: 150px"
Filled
Dense
HideDetails="@("auto")">
</MTextField>
}
<MSpacer/>
<MSpacer />
@if (filter.Persistent)
{
@@ -127,9 +157,7 @@
<MButton Text
LeftIconName="mdi-plus"
Color="primary"
OnClick="@AddNewFilter">
Add filter
</MButton>
OnClick="@AddNewFilter">@i18N.T("Add")</MButton>
</div>
</PModal>
@@ -138,6 +166,7 @@
[Parameter] public IList<ColumnInfo> Columns { get; set; } = [];
/// <summary>
///
/// Use this to filter out hidden columns when selecting columns.
/// </summary>
[Parameter] public HashSet<string> HiddenColumnIds { get; set; } = [];
@@ -146,6 +175,16 @@
[Parameter] public EventCallback<Filter> OnSave { get; set; }
[Parameter] public Filter Filter { get; set; } = new();
[Parameter] public EventCallback<Filter> FilterChanged { get; set; }
[Parameter] public StringNumber Width { get; set; } = 720;
[Parameter] public bool Show { get; set; }
[Parameter] public EventCallback<bool> ShowChanged { get; set; }
private static readonly IList<string> Operators = ["and", "or"];
private static readonly HashSet<StandardFilter> _noExpectedFuncs =
@@ -179,7 +218,9 @@
_filters.Clear();
foreach (var option in ActiveView.Filter.Options)
{
var column = Columns.FirstOrDefault(u => u.Id == option.ColumnId);
var columnName = option.ColumnId;
var column = Columns.FirstOrDefault(u => u.Id == option.ColumnId || option.Type == ExpectedType.DateTime && u.Id.StartsWith(option.ColumnId));
if (column is null)
{
continue;
@@ -199,13 +240,11 @@
}
}
private bool dialog;
private readonly List<FilterModel> _filters = [];
internal void Open()
{
dialog = true;
Show = true;
StateHasChanged();
}
@@ -216,7 +255,7 @@
return;
}
var filter = new FilterModel(Columns.First());
var filter = new FilterModel(Columns[0]);
_filters.Add(filter);
}
@@ -236,17 +275,38 @@
filter.Expected = "{ in: [" + string.Join(", ", filter.MultiSelect.Select(u => $"\"{u}\"")) + "]}";
}
private void HandleOnSave()
private async Task HandleOnSave()
{
dialog = false;
await HandleOnCancel();
var request = new Filter
Filter.Options = _filters.Select(u =>
{
Options = _filters.Select(u => u.ToFilterOption()).ToList(),
Operator = _operator == "and" ? FilterOperator.And : FilterOperator.Or
};
u.UpdateType();
return u.ToFilterOption();
}).ToList();
Filter.Operator = _operator == "and" ? FilterOperator.And : FilterOperator.Or;
OnSave.InvokeAsync(request);
if (FilterChanged.HasDelegate)
await FilterChanged.InvokeAsync(Filter);
}
private async Task HandleOnCancel()
{
Show = false;
if (ShowChanged.HasDelegate)
await ShowChanged.InvokeAsync(Show);
}
private void TimeChange(int index, DateTime time)
{
var filter = _filters[index];
filter.Expected = time.ToString();
}
private void ValueChange(int index, List<string> values)
{
var filter = _filters[index];
var tt = $"[\"{string.Join(""",""", values)}\"]";
filter.Expected = values.Count == 0 ? "[]" : tt;
}
}

View File

@@ -15,7 +15,6 @@ public class FilterModel : FilterOption
{
Column = column;
ColumnId = column.Id;
UpdateOperator();
}
@@ -61,7 +60,7 @@ public class FilterModel : FilterOption
case ColumnType.Select:
FuncList = SupportedFilter.SupportedSelectFilters;
Func = FuncList[0];
Type = ExpectedType.String;
Type = Func == StandardFilter.Contains || Func == StandardFilter.NotContains ? ExpectedType.Expression : ExpectedType.String;
SelectOptions = (Column.ConfigObject as SelectConfig)?.Options;
break;
case ColumnType.Date:
@@ -71,4 +70,22 @@ public class FilterModel : FilterOption
break;
}
}
public void UpdateType()
{
if (Type == ExpectedType.Expression)
{
if (Func is StandardFilter.Contains or StandardFilter.NotContains)
return;
Type = ExpectedType.String;
}
else if (Func is StandardFilter.Contains or StandardFilter.NotContains)
{
if (Type == ExpectedType.Expression)
return;
Type = ExpectedType.Expression;
}
}
}

View File

@@ -42,6 +42,8 @@ public static class SupportedFilter
public static readonly StandardFilter[] SupportedSelectFilters =
[
StandardFilter.Contains,
StandardFilter.NotContains,
StandardFilter.Equals,
StandardFilter.NotEquals,
StandardFilter.Set,

View File

@@ -17,6 +17,9 @@
{
<div class="m-template-table">
<Toolbar DefaultViewId="@_sheet.DefaultViewId"
ShowToolbar="@ShowToolbar"
ShowToolbarActions="@ShowToolbarActions"
ShowToolbarViews="@ShowToolbarViews"
Role="@Role"
ActiveView="@_sheet.ActiveViewId"
ActiveViewChanged="@HandleOnActiveViewChanged"
@@ -49,14 +52,20 @@
OnSortClick="@(() => _sortDialog?.Open())"
OnRowRemove="@Remove"
OnSearch="@Search"
OnSave="@SaveSheet"/>
OnSave="@SaveSheet" />
<Viewer ViewColumns="@_sheet.ActiveView.Columns"
HiddenColumnIds="@_sheet.ActiveViewHiddenColumnIds"
ColumnOrder="@_sheet.ActiveView.Order"
ColumnOrderChanged="@HandleOnColumnOrderChanged"
Editable="@Editable"
Class="@TableClass"
Loading="@_loading"
TableStripeClass="@StripeClass"
TableBodyTdClass="@BodyTdClass"
TableBodyTrClass="@BodyTrClass"
TableHeaderThClass="@HeaderThClass"
TableHeaderClass="@HeaderClass"
RowHeight="@_rowHeight"
Rows="@_rows"
Sort="@_sheet.ActiveView.Value.Sort"
@@ -98,21 +107,26 @@
</ColumnDialog>
<FilterDialog @ref="_filterDialog"
Show=ShowFilter
ShowChanged="ShowFilterChanged"
ActiveView="@_sheet.ActiveView.Value"
Columns="@_sheet.Columns"
OnSave="@SaveFilter">
Filter=" _sheet!.ActiveView.Value.Filter"
FilterChanged="SaveFilter">
</FilterDialog>
<SortDialog @ref="_sortDialog"
Show=ShowSort
ShowChanged="ShowSortChanged"
ActiveView="@_sheet.ActiveView.Value"
Columns="@_sheet.Columns"
HiddenColumnIds="@_sheet.ActiveViewHiddenColumnIds"
OnSave="@SortUpdate">
</SortDialog>
<ImageViewer @ref="_imageViewer"/>
<ImageViewer @ref="_imageViewer" />
<DetailDialog @ref="_detailDialog" OnImagePreview="@OpenImageViewer"/>
<DetailDialog @ref="_detailDialog" OnImagePreview="@OpenImageViewer" />
</div>
}
@@ -191,11 +205,12 @@
EnsureActiveViewActionsColumnWidthCorrectly();
ResetSheet();
await Task.CompletedTask;
}
private async Task ResetView()
{
foreach (var column in _sheet.ActiveView.Columns.Where(u => !Preset.InternalColumnIds.Contains(u.ColumnId)))
foreach (var column in _sheet!.ActiveView.Columns.Where(u => !Preset.InternalColumnIds.Contains(u.ColumnId)))
{
column.Hidden = false;
column.Width = 0;
@@ -251,7 +266,7 @@
if (_sheet.ActiveViewId == view.Value.Id)
{
_sheet.SetActiveView(_sheet.Views.First().Value.Id);
_sheet.SetActiveView(_sheet.Views[0].Value.Id);
}
}
@@ -276,7 +291,7 @@
private async Task HandleOnActiveViewChanged(Guid viewId)
{
_sheet!.SetActiveView(viewId);
_viewActionsContext = new ViewActionsContext();
ResetSheet();
@@ -304,7 +319,7 @@
private void HandleOnColumnOrderChanged(List<string> columnOrder)
{
_sheet.ActiveView.Order = columnOrder;
_sheet!.ActiveView.Order = columnOrder;
if (!_sheet.ActiveView.Order.Contains(Preset.ActionsColumnId))
{
@@ -336,7 +351,7 @@
if (!pageSizeOptions.Contains(_sheet.ActiveView.PageSize))
{
_sheet.ActiveView.PageIndex = 1;
_sheet.ActiveView.PageSize = pageSizeOptions.First();
_sheet.ActiveView.PageSize = pageSizeOptions[0];
await RefreshItemsAsync(GetItemsProviderRequest());
}
@@ -362,7 +377,7 @@
private void OpenColumnEditDialog(Column column)
{
var viewColumn = _sheet.ActiveView.Columns.First(u => u.ColumnId == column.Id);
var viewColumn = _sheet!.ActiveView.Columns.First(u => u.ColumnId == column.Id);
_columnEditDialog?.Open(column, viewColumn.Fixed);
}
@@ -410,9 +425,9 @@
{
_sheet.ActiveView.Selection[item] = selected;
}
_viewActionsContext = new ViewActionsContext(_sheet.ActiveView.Selection);
StateHasChanged();
}
@@ -483,4 +498,13 @@
await OnSave.InvokeAsync(sheet);
}
public async Task ReloadAsync(int? page = default, int? pageSize = default)
{
if (page.HasValue && page > 0)
_sheet!.ActiveView.PageIndex = page.Value;
if (pageSize.HasValue && pageSize > 0)
_sheet!.ActiveView.PageSize = pageSize.Value;
await RefreshItemsAsync(GetItemsProviderRequest());
}
}

View File

@@ -9,7 +9,11 @@ public partial class MTemplateTable
[Inject] private IGraphQLClient GraphQLClient { get; set; } = null!;
[Parameter] [EditorRequired] public Sheet? Sheet { get; set; }
[Parameter] public string Class { get; set; } = default!;
[Parameter] public bool Dense { get; set; } = false;
[Parameter][EditorRequired] public Sheet? Sheet { get; set; }
[Parameter] public IList<View>? UserViews { get; set; }
@@ -31,14 +35,48 @@ public partial class MTemplateTable
[Parameter] public RenderFragment<ViewActionsContext>? ViewActionsContent { get; set; }
[Parameter] public bool ShowToolbarViews { get; set; }
[Parameter] public bool ShowToolbar { get; set; }
[Parameter] public bool ShowToolbarActions { get; set; }
[Parameter] public RenderFragment<CustomCellContext>? CustomCellContent { get; set; }
[Parameter] public Role Role { get; set; }
[Parameter] public int DefaultPageSize { get; set; } = 10;
[Parameter] public bool ShowFilter { get; set; }
[Parameter] public EventCallback<bool> ShowFilterChanged { get; set; }
[Parameter] public bool ShowSort { get; set; }
[Parameter] public EventCallback<bool> ShowSortChanged { get; set; }
[Parameter] public bool Stripe { get; set; }
[Parameter]
public string TableClass { get; set; } = default!;
[Parameter]
public string HeaderClass { get; set; } = "m-data-table-header";
[Parameter]
public string HeaderThClass { get; set; } = "sortable text-start";
[Parameter]
public string BodyTrClass { get; set; } = default!;
[Parameter]
public string BodyTdClass { get; set; } = "text-start";
[Parameter]
public string StripeClass { get; set; } = default!;
private SheetInfo? _sheet = null;
private bool _init;
private ViewActionsContext _viewActionsContext = new();
/// <summary>
@@ -57,7 +95,7 @@ public partial class MTemplateTable
protected override async Task OnParametersSetAsync()
{
// base.OnParametersSetAsync();
await base.OnParametersSetAsync();
if (_prevSheet != Sheet)
{
@@ -77,12 +115,21 @@ public partial class MTemplateTable
}
}
}
if (ShowFilter)
{
_filterDialog?.Open();
}
if (ShowSort)
{
_sortDialog?.Open();
}
}
private void FormatSheet()
{
_sheet = SheetInfo.From(Sheet);
_allColumns = [Preset.CreateSelectColumn(), .._sheet.Columns, Preset.CreateActionsColumn()];
_allColumns = [Preset.CreateSelectColumn(), .. _sheet.Columns, Preset.CreateActionsColumn()];
UpdateStateOfActiveView();
}
@@ -174,7 +221,7 @@ public partial class MTemplateTable
{
Operator = _sheet!.ActiveView.Value.Filter.Operator,
Search = _sheet.ActiveView.Value.Filter.Search,
Options = [.._sheet.ActiveView.Value.Filter.Options]
Options = [.. _sheet.ActiveView.Value.Filter.Options]
};
return new QueryRequest(_sheet.QueryBody, _sheet.CountField, filterRequest,

View File

@@ -7,15 +7,27 @@
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser"/>
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.7"/>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Masa.Blazor.Components.TemplateTable.Abstractions\Masa.Blazor.Components.TemplateTable.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\i18n\en-US.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\i18n\supportedCultures.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\i18n\zh-CN.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -4,8 +4,8 @@
Options="@_options"
ItemsPerPageOptions="@_itemsPerPageOptions"
ShowItemsPerPageOptionsEvenIfOne
OnOptionsUpdate="@HandleOnOptionsUpdate"
></MDataFooter>
Class="white"
OnOptionsUpdate="@HandleOnOptionsUpdate"></MDataFooter>
@code {
[Parameter] public int PageIndex { get; set; }

View File

@@ -1,8 +1,4 @@
using System.Net.Http.Headers;
using GraphQL.Client.Http;
using GraphQL.Client.Serializer.SystemTextJson;
namespace Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
@@ -16,7 +12,7 @@ public static class ServiceCollectionExtensions
public static IMasaBlazorBuilder AddGraphQLClientForTemplateTable(this IMasaBlazorBuilder builder, string endPoint,
string? bearerToken = null)
{
builder.Services.AddScoped<GraphQLHttpClient>(_ =>
builder.Services.AddScoped<GraphQL.Client.Abstractions.IGraphQLClient>(_ =>
{
var client = new GraphQLHttpClient(endPoint,
new SystemTextJsonSerializer(new JsonSerializerOptions(JsonSerializerDefaults.Web)));
@@ -27,9 +23,23 @@ public static class ServiceCollectionExtensions
new AuthenticationHeaderValue("Bearer", bearerToken);
}
return client;
return (GraphQL.Client.Abstractions.IGraphQLClient)client;
});
return builder;
}
public static IMasaBlazorBuilder AddGraphQLClientForTemplateTable(this IMasaBlazorBuilder builder, Func<IServiceProvider, GraphQL.Client.Abstractions.IGraphQLClient> func)
{
ArgumentNullException.ThrowIfNull(func);
builder.Services.AddScoped(func);
return builder;
}
public static Task<IMasaBlazorBuilder> AddTemplateTableI18nAsync(this IMasaBlazorBuilder builder, string hostPath)
{
ArgumentNullException.ThrowIfNull(hostPath);
var i18nPath = $"{hostPath}/_content/Masa.Blazor.Components.TemplateTable/i18n";
return builder.AddI18nForWasmAsync(i18nPath);
}
}

View File

@@ -1,8 +1,9 @@
@namespace Masa.Blazor.Components.TemplateTable.SortDialogs
@inject I18n i18N
<PModal @bind-Value="dialog"
<PModal @bind-Value="Show"
Width="640"
Title="Sorting"
Title="@i18N.T("Sorting")"
Persistent
BodyStyle="min-height: 180px"
OnCancel="@HandleOnCancel"
@@ -13,7 +14,7 @@
var option = _sorts[index];
<div class="d-flex align-center justify-end mb-2">
<div class="mr-2 text-right">
@(index == 0 ? "Sort by" : "Then by")
@(index == 0 ? i18N.T("Sortby") : i18N.T("Thenby"))
<span class="font-weight-bold">
@($"{option.Column.Name}({option.ColumnId})")
</span>
@@ -22,7 +23,7 @@
<MSelect @bind-Value="@option.OrderBy"
Items="@_directions"
ItemValue="u => u.Value"
ItemText="u => u.Label"
ItemText="@(u => i18N.T($"{u.Label}"))"
Class="mr-2"
Dense
Filled
@@ -48,7 +49,7 @@
<div class="text-right" style="margin-right: 44px;">
<SortBy Columns="@ComputedColumns"
OnSelect="@AddSort">
@(_sorts.Count == 0 ? "Sort by" : "Then by")
@(_sorts.Count == 0 ? i18N.T("Sortby") : i18N.T("Add"))
</SortBy>
</div>
}
@@ -65,13 +66,16 @@
[Parameter] public EventCallback<Sort> OnSave { get; set; }
[Parameter] public bool Show { get; set; }
[Parameter] public EventCallback<bool> ShowChanged { get; set; }
private static (SortOrder Value, string Label)[] _directions =
[
(SortOrder.Asc, "ascending"),
(SortOrder.Desc, "descending")
];
private bool dialog;
private IList<ColumnInfo> _computedColumns = [];
private IList<SortModel> _sorts = [];
@@ -111,7 +115,7 @@
internal void Open()
{
SetSorts();
dialog = true;
Show = true;
StateHasChanged();
}
@@ -126,25 +130,30 @@
{
return;
}
_sorts.Remove(model);
}
private void HandleOnCancel()
private async Task HandleOnCancel()
{
_sorts.Clear();
dialog = false;
Show = false;
if (ShowChanged.HasDelegate)
await ShowChanged.InvokeAsync(Show);
}
private void HandleOnSave()
private async Task HandleOnSave()
{
dialog = false;
Show = false;
if (ShowChanged.HasDelegate)
await ShowChanged.InvokeAsync(Show);
var sort = new Sort()
{
Options = _sorts.Select(u => u.ToSortOption()).ToList()
};
OnSave.InvokeAsync(sort);
if (OnSave.HasDelegate)
await OnSave.InvokeAsync(sort);
}
}

View File

@@ -1,199 +1,211 @@
@using System.Globalization
@inject IPopupService PopupService
@inject I18n i18N;
<div class="masa-table__toolbar">
<MRow Dense>
<MCol Cols="12" Md="@(Role == Role.Manager ? 12 : 5)" Class="d-flex align-center">
@{
var firstUserView = Views.FirstOrDefault(u => u.IsUserView);
}
<MSelect Value="ActiveView"
ValueChanged="ActiveViewChanged"
Items="@Views"
ItemText="v => v.Value.Name"
ItemValue="v => v.Value.Id"
Class="m-template-table__view-select mr-2"
Label="View"
Dense
Outlined
HideDetails="true"
TItem="ViewInfo"
TItemValue="Guid"
TValue="Guid"
OnSelect="@(tuple => OnActiveViewChanged(tuple.Item))">
<SelectionContent>
@GenModifiedChip(context.Item)
</SelectionContent>
<ItemContent>
<div class="m-template-table__view-select-item">
@if (context.Item == firstUserView)
{
<MDivider @key="@context.Item"/>
}
@GenModifiedChip(context.Item)
</div>
</ItemContent>
</MSelect>
@if (_activeViewInfo is not null)
{
<ViewAction Loading="@_viewActionLoading"
Role="@Role"
ViewInfo="@_activeViewInfo"
OnSaveAsNewView="@SaveAsNewView"
OnViewDelete="@HandleOnDelete"
OnViewRename="@UpdateViewName"
OnViewSave="@HandleOnViewSave"/>
}
@if (Role == Role.Manager)
{
<MTooltip Top>
<ActivatorContent>
<MButton Class="ml-1"
Color="primary"
OnClick="@OnSave"
@attributes="@context.Attrs">
保存
</MButton>
</ActivatorContent>
<ChildContent>
保存所有设置及视图
</ChildContent>
</MTooltip>
}
</MCol>
@if (Role == Role.User)
@if (ShowToolbar)
{
<div class="masa-table__toolbar">
@if (ShowToolbarViews)
{
var searchableColumns = _activeViewInfo?.GetSearchableColumn();
var hasSearch = searchableColumns?.Count > 0;
if (hasSearch)
{
var placeholder = $"Search for {string.Join(", ", searchableColumns.Select(u => u.Column.Name))}";
<MCol Cols="12" Md="6" OffsetMd="1" Align="AlignTypes.Center">
<MTextField @bind-Value="_activeViewInfo!.Value.Filter.Search"
Placeholder="@placeholder"
PersistentPlaceholder
Outlined
Clearable
Dense
HideDetails="true"
OnEnter="@OnSearch"
OnClearClick="@OnSearch">
</MTextField>
</MCol>
}
}
</MRow>
<MDivider Class="mt-2"/>
<MRow Class="masa-table__toolbar-actions"
NoGutters>
<MCol Cols="12" Md="6" Align="AlignTypes.Center">
@if (Editable)
{
<MButton LeftIconName="mdi-cog-outline" Text Small OnClick="@(() => _configDialog = true)">
<MBadge Value="@HasCustom" Dot Color="red">
表格配置
</MBadge>
</MButton>
<MDivider Vertical Style="height: 1rem"/>
}
<MButton LeftIconName="mdi-filter-variant" Text Small OnClick="@OnFilterClick">
<MBadge Value="@HasFilter" Dot Color="red">
筛选
</MBadge>
</MButton>
<MDivider Vertical Style="height: 1rem"/>
<MButton LeftIconName="mdi-sort" Text Small OnClick="@OnSortClick">
<MBadge Value="@HasSort" Dot Color="red">
排序
</MBadge>
</MButton>
<MDivider Vertical Style="height: 1rem"/>
<MMenu MinWidth="160">
<ActivatorContent>
<MButton LeftIconName="mdi-format-line-spacing"
Text Small
@attributes="@context.Attrs">
行高
</MButton>
</ActivatorContent>
<ChildContent>
<MList Slim Dense>
<MListItemGroup Value="@((int)RowHeight)"
ValueChanged="@((val) => RowHeightChanged.InvokeAsync((RowHeight)val.AsT1))"
Mandatory
Color="primary">
<MListItem Value="0">
<ItemContent>
<MListItemAction>
<FadeTransition>
<MIcon Dense TransitionShow="@context.Active">mdi-check</MIcon>
</FadeTransition>
</MListItemAction>
<MListItemContent>
<MListItemTitle>
</MListItemTitle>
</MListItemContent>
</ItemContent>
</MListItem>
<MListItem Value="1">
<ItemContent>
<MListItemAction>
<FadeTransition>
<MIcon Dense TransitionShow="@context.Active">mdi-check</MIcon>
</FadeTransition>
</MListItemAction>
<MListItemContent>
<MListItemTitle>
</MListItemTitle>
</MListItemContent>
</ItemContent>
</MListItem>
<MListItem Value="2">
<ItemContent>
<MListItemAction>
<FadeTransition>
<MIcon Dense TransitionShow="@context.Active">mdi-check</MIcon>
</FadeTransition>
</MListItemAction>
<MListItemContent>
<MListItemTitle>
</MListItemTitle>
</MListItemContent>
</ItemContent>
</MListItem>
</MListItemGroup>
</MList>
</ChildContent>
</MMenu>
</MCol>
<MCol Cols="12" Md="6" Class="m-template-table__toolbar-actions">
<MDefaultsProvider Defaults="@_defaults">
@ViewActionsContent
@if (ShowBulkDelete && HasSelect)
{
<MButton Disabled="@(!HasSelectedKeys)"
Color="error"
<MRow Dense>
<MCol Cols="12" Md="@(Role == Role.Manager ? 12 : 5)" Class="d-flex align-center">
@{
var firstUserView = Views.FirstOrDefault(u => u.IsUserView);
}
<MSelect Value="ActiveView"
ValueChanged="ActiveViewChanged"
Items="@Views"
ItemText="v => v.Value.Name"
ItemValue="v => v.Value.Id"
Class="m-template-table__view-select mr-2"
Label="View"
Dense
Outlined
Loading="@_deleting"
OnClick="@HandleOnBulkDelete">
批量删除
</MButton>
HideDetails="true"
TItem="ViewInfo"
TItemValue="Guid"
TValue="Guid"
OnSelect="@(tuple => OnActiveViewChanged(tuple.Item))">
<SelectionContent>
@GenModifiedChip(context.Item)
</SelectionContent>
<ItemContent>
<div class="m-template-table__view-select-item">
@if (context.Item == firstUserView)
{
<MDivider @key="@context.Item" />
}
@GenModifiedChip(context.Item)
</div>
</ItemContent>
</MSelect>
@if (_activeViewInfo is not null)
{
<ViewAction Loading="@_viewActionLoading"
Role="@Role"
ViewInfo="@_activeViewInfo"
OnSaveAsNewView="@SaveAsNewView"
OnViewDelete="@HandleOnDelete"
OnViewRename="@UpdateViewName"
OnViewSave="@HandleOnViewSave" />
}
@if (Role == Role.Manager)
{
<MTooltip Top>
<ActivatorContent>
<MButton Class="ml-1"
Color="primary"
OnClick="@OnSave"
@attributes="@context.Attrs">
保存
</MButton>
</ActivatorContent>
<ChildContent>
保存所有设置及视图
</ChildContent>
</MTooltip>
}
</MCol>
@if (Role == Role.User)
{
var searchableColumns = _activeViewInfo?.GetSearchableColumn();
var hasSearch = searchableColumns?.Count > 0;
if (hasSearch)
{
var placeholder = $"Search for {string.Join(", ", searchableColumns.Select(u => u.Column.Name))}";
<MCol Cols="12" Md="6" OffsetMd="1" Align="AlignTypes.Center">
<MTextField @bind-Value="_activeViewInfo!.Value.Filter.Search"
Placeholder="@placeholder"
PersistentPlaceholder
Outlined
Clearable
Dense
HideDetails="true"
OnEnter="@OnSearch"
OnClearClick="@OnSearch">
</MTextField>
</MCol>
}
}
<MButton Color="secondary"
Outlined
OnClick="@ResetView">
重置
</MButton>
</MDefaultsProvider>
</MCol>
</MRow>
</div>
</MRow>
}
@if (ShowToolbarViews && ShowToolbarActions)
{
<MDivider Class="mt-2" />
}
@if (ShowToolbarActions)
{
<MRow Class="masa-table__toolbar-actions" NoGutters>
<MCol Cols="12" Md="6" Align="AlignTypes.Center">
@if (Editable)
{
<MButton LeftIconName="mdi-cog-outline" Text Small OnClick="@(() => _configDialog = true)">
<MBadge Value="@HasCustom" Dot Color="red">
表格配置
</MBadge>
</MButton>
<MDivider Vertical Style="height: 1rem" />
}
<MButton LeftIconName="mdi-filter-variant" Text Small OnClick="@OnFilterClick">
<MBadge Value="@HasFilter" Dot Color="red">
筛选
</MBadge>
</MButton>
<MDivider Vertical Style="height: 1rem" />
<MButton LeftIconName="mdi-sort" Text Small OnClick="@OnSortClick">
<MBadge Value="@HasSort" Dot Color="red">
排序
</MBadge>
</MButton>
<MDivider Vertical Style="height: 1rem" />
<MMenu MinWidth="160">
<ActivatorContent>
<MButton LeftIconName="mdi-format-line-spacing"
Text Small
@attributes="@context.Attrs">
行高
</MButton>
</ActivatorContent>
<ChildContent>
<MList Slim Dense>
<MListItemGroup Value="@((int)RowHeight)"
ValueChanged="@((val) => RowHeightChanged.InvokeAsync((RowHeight)val.AsT1))"
Mandatory
Color="primary">
<MListItem Value="0">
<ItemContent>
<MListItemAction>
<FadeTransition>
<MIcon Dense TransitionShow="@context.Active">mdi-check</MIcon>
</FadeTransition>
</MListItemAction>
<MListItemContent>
<MListItemTitle>
</MListItemTitle>
</MListItemContent>
</ItemContent>
</MListItem>
<MListItem Value="1">
<ItemContent>
<MListItemAction>
<FadeTransition>
<MIcon Dense TransitionShow="@context.Active">mdi-check</MIcon>
</FadeTransition>
</MListItemAction>
<MListItemContent>
<MListItemTitle>
</MListItemTitle>
</MListItemContent>
</ItemContent>
</MListItem>
<MListItem Value="2">
<ItemContent>
<MListItemAction>
<FadeTransition>
<MIcon Dense TransitionShow="@context.Active">mdi-check</MIcon>
</FadeTransition>
</MListItemAction>
<MListItemContent>
<MListItemTitle>
</MListItemTitle>
</MListItemContent>
</ItemContent>
</MListItem>
</MListItemGroup>
</MList>
</ChildContent>
</MMenu>
</MCol>
<MCol Cols="12" Md="6" Class="m-template-table__toolbar-actions">
<MDefaultsProvider Defaults="@_defaults">
@ViewActionsContent
@if (ShowBulkDelete && HasSelect)
{
<MButton Disabled="@(!HasSelectedKeys)"
Color="error"
Outlined
Loading="@_deleting"
OnClick="@HandleOnBulkDelete">
批量删除
</MButton>
}
<MButton Color="secondary"
Outlined
OnClick="@ResetView">
重置
</MButton>
</MDefaultsProvider>
</MCol>
</MRow>
}
</div>
}
<PDrawer @bind-Value="_configDialog"
Title="表格配置"
@@ -258,7 +270,7 @@
Style="color: inherit"
OnClick="@(() => OnColumnEditClick.InvokeAsync(sortContext.Item))">
</MButton>
<MDivider Vertical Length="16" Class="my-auto mx-1"/>
<MDivider Vertical Length="16" Class="my-auto mx-1" />
}
<MToggle Value="@(!hidden)"
DataOn="@("mdi-eye-outline")"
@@ -267,7 +279,7 @@
<MButton IconName="@context.Data"
Small
Style="color: inherit"
OnClick="@(() => OnColumnToggle.InvokeAsync(sortContext.Item.Id))"/>
OnClick="@(() => OnColumnToggle.InvokeAsync(sortContext.Item.Id))" />
</MToggle>
</MChip>
</ItemContent>
@@ -313,7 +325,7 @@
<MDialog @bind-Value="_filterDialog"
MaxWidth="400">
<MButton Outlined Block>Add filter</MButton>
<MButton Outlined Block>@i18N.T("ADD_FILTER")</MButton>
</MDialog>
@code {

View File

@@ -1,6 +1,4 @@
using Masa.Blazor.Components.TemplateTable.Actions;
namespace Masa.Blazor.Components.TemplateTable.Toolbars;
namespace Masa.Blazor.Components.TemplateTable.Toolbars;
public partial class Toolbar
{
@@ -50,6 +48,12 @@ public partial class Toolbar
[Parameter] public EventCallback OnRowRemove { get; set; }
[Parameter] public bool ShowToolbarViews { get; set; } = true;
[Parameter] public bool ShowToolbar { get; set; } = true;
[Parameter] public bool ShowToolbarActions { get; set; } = true;
[Parameter] public bool HasSelectedKeys { get; set; }
[Parameter] public bool HasActions { get; set; }

View File

@@ -1,7 +1,7 @@
@inject IJSRuntime JSRuntime
<div @ref="_ref"
class="masa-table-viewer__header-column-content @Class"
class="masa-table-viewer__header-column-content d-flex px-4 @Class"
@onclick="@HandleOnClick">
@if (IsRowSelect)
{
@@ -9,12 +9,12 @@
Indeterminate="@IsIndeterminate"
Value="@IsSelectedAll"
ValueChanged="@OnSelectAll"
Color="@CheckboxColor"/>
Color="@CheckboxColor" />
}
else
{
@Templates.GenTypeIcon(Data.Type, true)
<span class="text-truncate">@Data.Name</span>
<span class="text-truncate px-2">@Data.Name</span>
}
@if (!Internal)
@@ -43,7 +43,7 @@
@if (!Internal)
{
<Resizor/>
<Resizor />
}
@code {
@@ -130,8 +130,8 @@
{
ColumnId = Data.Id,
OrderBy = SortOrder.Asc,
Index = sort.Options.MaxBy(u => u.Index)?.Index + 1 ?? 0,
Type = Data.GetExpectedType()
Type = Data.Type.ConvertToExpectedType(),
Index = sort.Options.MaxBy(u => u.Index)?.Index + 1 ?? 0
};
sort.Options.Add(sortOption);

View File

@@ -4,141 +4,143 @@
@using Masa.Blazor.Components.TemplateTable.Actions
@using Microsoft.AspNetCore.Components.Web.Virtualization
<MSimpleTable FixedHeader
Class="@_modifierBuilder.Add(RowHeight.ToString()).Add(_sized).Build()"
Style="@($"--mt-row-height: {RowHeightValue}")"
<MSimpleTable FixedHeader Class="@Class"
Height="@Height"
@ref="_simpleTable">
<thead>
<tr @ref="_headerTrRef">
<MSortableProvider Items="ViewColumns"
ItemKey="@(u => u.ColumnId)"
GhostClass="accent"
ContainerRef="@_headerTrRef"
ForceFallback
Ignore=".ignore-elements"
Order="@ColumnOrder"
OrderChanged="@ColumnOrderChanged">
<ItemContent Context="context">
@{
var (hidden, css, style) = GetHeaderColCss(context.Item);
}
<th class="@css"
style="@style"
@key="@(context.Item.Column.Id)"
@attributes="@context.Attrs">
@if (!hidden)
{
<ColumnName Data="@context.Item.Column"
Editable="@Editable"
Sort="@Sort"
OnClick="@OnSortUpdate"
OnColumnToggle="@OnColumnToggle"
OnColumnEditClick="@OnColumnEditClick"
IsIndeterminate="@SelectionState.Indeterminate"
IsSelectedAll="@SelectionState.AllSelected"
OnSelectAll="@OnSelectAll"
CheckboxColor="@CheckboxColor"/>
<thead class="@TableHeaderClass">
<tr @ref="_headerTrRef">
<MSortableProvider Items="ViewColumns"
ItemKey="@(u => u.ColumnId)"
GhostClass="accent"
ContainerRef="@_headerTrRef"
ForceFallback
Ignore=".ignore-elements"
Order="@ColumnOrder"
OrderChanged="@ColumnOrderChanged">
<ItemContent Context="context">
@{
var (hidden, css, style) = GetHeaderColCss(context.Item);
}
</th>
</ItemContent>
</MSortableProvider>
</tr>
<th class="@TableHeaderThClass @css"
style="@style"
@key="@(context.Item.Column.Id)"
@attributes="@context.Attrs">
@if (!hidden)
{
<ColumnName Data="@context.Item.Column"
Editable="@Editable"
Sort="@Sort"
OnClick="@OnSortUpdate"
OnColumnToggle="@OnColumnToggle"
OnColumnEditClick="@OnColumnEditClick"
IsIndeterminate="@SelectionState.Indeterminate"
IsSelectedAll="@SelectionState.AllSelected"
OnSelectAll="@OnSelectAll"
CheckboxColor="@CheckboxColor" />
}
</th>
</ItemContent>
</MSortableProvider>
</tr>
</thead>
@if (Loading)
{
<thead>
<tr class="m-data-table__progress">
<th class="column" colspan="1000">
@*TODO: 设置为正常的colspan*@
<MProgressLinear Absolute Indeterminate Color="primary"></MProgressLinear>
</th>
</tr>
<tr class="m-data-table__progress">
<th class="column" colspan="1000">
@*TODO: 设置为正常的colspan*@
<MProgressLinear Absolute Indeterminate Color="primary"></MProgressLinear>
</th>
</tr>
</thead>
}
@{
int rowIndex = 1;
}
<tbody>
<Virtualize Items="Rows"
Context="item"
ItemSize="RowHeightValue"
OverscanCount="1">
@{
var isSelected = SelectedKeys.Contains(item.Key);
}
<Virtualize Items="Rows"
Context="item"
ItemSize="RowHeightValue"
OverscanCount="1">
@{
var isSelected = SelectedKeys.Contains(item.Key);
bool isStripe = rowIndex % 2 == 0;
rowIndex++;
}
<tr class="@_rowModifierBuilder.AddClass("m-data-table__selected", isSelected)">
@foreach (var template in _orderedViewColumns)
{
var (hidden, css, style) = GetCss(template);
<tr class="@_rowModifierBuilder.AddClass("m-data-table__selected", isSelected).AddClass(TableStripeClass, isStripe)">
@foreach (var template in _orderedViewColumns)
{
var (hidden, css, style) = GetCss(template);
<td class="@css"
style="@style"
@key="template.Column.Id">
@if (!hidden)
{
<div class="masa-table-viewer__cell">
<div class="masa-table-viewer__cell-content">
@if (template.Column.Type == ColumnType.Actions)
{
<MDefaultsProvider Defaults="@_actionsDefautls">
@GenActionsCell(item.Data)
</MDefaultsProvider>
}
else if (template.Column.Type == ColumnType.RowSelect)
{
@GenRowSelectCell(item, isSelected)
}
else
{
JsonElement? value = null;
if (template.Column.IsNested)
<td class="@TableBodyTdClass @css"
style="@style"
@key="template.Column.Id">
@if (!hidden)
{
<div class="masa-table-viewer__cell">
<div class="masa-table-viewer__cell-content">
@if (template.Column.Type == ColumnType.Actions)
{
var tree = template.Column.Id.Split('.');
var firstKey = tree[0];
var key = item.Data.Keys.FirstOrDefault(u => string.Equals(u, firstKey, StringComparison.OrdinalIgnoreCase));
if (key is not null)
<MDefaultsProvider Defaults="@_actionsDefautls">
@GenActionsCell(item.Data)
</MDefaultsProvider>
}
else if (template.Column.Type == ColumnType.RowSelect)
{
@GenRowSelectCell(item, isSelected)
}
else
{
JsonElement? value = null;
if (template.Column.IsNested)
{
value = item.Data[key];
for (var i = 1; i < tree.Length; i++)
var tree = template.Column.Id.Split('.');
var firstKey = tree[0];
var key = item.Data.Keys.FirstOrDefault(u => string.Equals(u, firstKey, StringComparison.OrdinalIgnoreCase));
if (key is not null)
{
// 默认使用驼峰命名法
value = value.Value.GetProperty(tree[i].ToCamelCase());
value = item.Data[key];
for (var i = 1; i < tree.Length; i++)
{
// 默认使用驼峰命名法
value = value.Value.GetProperty(tree[i].ToCamelCase());
}
}
}
}
else
{
var key = item.Data.Keys.FirstOrDefault(u => string.Equals(u, template.ColumnId, StringComparison.OrdinalIgnoreCase));
if (key is not null)
else
{
value = item.Data[key];
var key = item.Data.Keys.FirstOrDefault(u => string.Equals(u, template.ColumnId, StringComparison.OrdinalIgnoreCase));
if (key is not null)
{
value = item.Data[key];
}
}
if (value is null)
{
@GenNotFound(template.Column)
}
else if (CustomCellContent is not null)
{
CustomCellContext context = new(template.Column.Id, value.Value, item.Data, @GenCell(template.Column, value.Value));
@CustomCellContent.Invoke(context)
}
else
{
@GenCell(template.Column, value.Value)
}
}
if (value is null)
{
@GenNotFound(template.Column)
}
else if (template.Column.Type == ColumnType.Custom && CustomCellContent is not null)
{
CustomCellContext context = new(template.Column.Id, value.Value, item.Data);
@CustomCellContent.Invoke(context)
}
else
{
@GenCell(template.Column, value.Value)
}
}
</div>
</div>
</div>
}
</td>
}
</tr>
</Virtualize>
}
</td>
}
</tr>
</Virtualize>
</tbody>
</MSimpleTable>
@@ -205,13 +207,13 @@
{
var isChecked = value.GetBoolean();
<MIcon Icon="@(isChecked ? "$checkboxOn" : "$checkboxOff")"
Color="@(isChecked ? "primary" : "")"/>
Color="@(isChecked ? "primary" : "")" />
};
private static RenderFragment GenSwitchCell(JsonElement value) => __builder =>
{
var isChecked = value.GetBoolean();
<MSwitch Value="@isChecked" Inset Disabled/>
<MSwitch Value="@isChecked" Inset Disabled />
};
private static RenderFragment GenTextCell(JsonElement value) => __builder =>
@@ -364,7 +366,7 @@
<div class="image-list" @onclick="@(() => OnImagePreview.InvokeAsync(urls))">
@foreach (var url in urls)
{
<img src="@url" alt=""/>
<img src="@url" alt="" />
}
</div>

View File

@@ -1,7 +1,5 @@
using BemIt;
using Masa.Blazor.Components.TemplateTable.Actions;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.RenderTree;
namespace Masa.Blazor.Components.TemplateTable.Viewers;
@@ -11,6 +9,8 @@ public partial class Viewer : IAsyncDisposable
[Inject] private MasaBlazor MasaBlazor { get; set; } = default!;
[Parameter] public string Class { get; set; } = default!;
/// <summary>
/// View columns without an order, that will be ordered by <see cref="ColumnOrder"/>.
/// </summary>
@@ -75,12 +75,22 @@ public partial class Viewer : IAsyncDisposable
[Parameter] public List<string> SelectedKeys { get; set; } = [];
// [Parameter] public List<string> SelectableKeys { get; set; } = [];
[Parameter] public Action<(Row Item, bool Selected)> OnSelect { get; set; } = default!;
[Parameter] public Action<bool> OnSelectAll { get; set; } = default!;
[Parameter] public string TableHeaderClass { get; set; } = default!;
[Parameter] public string TableHeaderThClass { get; set; } = default!;
[Parameter] public string TableBodyTrClass { get; set; } = default!;
[Parameter] public string TableBodyTdClass { get; set; } = default!;
[Parameter] public string TableStripeClass { get; set; } = default!;
[Parameter] public EventCallback<IReadOnlyCollection<Row>> OnRemove { get; set; }
private IDictionary<string, IDictionary<string, object?>> _actionsDefautls =
new Dictionary<string, IDictionary<string, object?>>
{
@@ -303,6 +313,7 @@ public partial class Viewer : IAsyncDisposable
if (width > 0)
{
styleBuilder.AddWidth(width);
styleBuilder.AddMinWidth(width);
}
if (fixedLeft)

View File

@@ -1,12 +1,15 @@
// Global using directives
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using GraphQL.Client.Http;
global using GraphQL.Client.Serializer.SystemTextJson;
global using Masa.Blazor.Components.TemplateTable;
global using Masa.Blazor.Components.TemplateTable.Abstractions;
global using Masa.Blazor.Components.TemplateTable.ColumnConfigs;
global using Masa.Blazor.Components.TemplateTable.Contracts;
global using Masa.Blazor.Core;
global using Masa.Blazor.Extensions;
global using Microsoft.AspNetCore.Components;
global using Microsoft.JSInterop;
global using Microsoft.JSInterop;
global using System.Net.Http.Headers;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using Masa.Blazor.Components.TemplateTable.Actions;

View File

@@ -0,0 +1,35 @@
{
"ADD_FILTER": "Add filter",
"Filters": "Filters",
"When": "When",
"and": "and",
"or": "or",
"ColumnId": "Column",
"Func": "Function",
"Expected": "Expected",
"SDF_Equals": "=",
"SDF_NotEquals": "!=",
"SDF_Contains": "in",
"SDF_NotContains": "not in",
"SDF_StartsWith": "left like",
"SDF_NotStartsWith": "not left like",
"SDF_EndsWith": "right like",
"SDF_NotEndsWith": "not right like",
"SDF_Set": "exists",
"SDF_NotSet": "not exists",
"SDF_Gt": ">",
"SDF_Gte": ">=",
"SDF_Lt": "<",
"SDF_Lte": "<=",
"SDF_BeforeDate": "<",
"SDF_BeforeOrOnDate": "<=",
"SDF_AfterDate": ">",
"SDF_AfterOrOnDate": ">=",
"SDF_True": "true",
"SDF_False": "false",
"SortBy": "Sort by",
"ThenBy": "Then By",
"Sorting": "Sorting",
"ascending": "asc",
"descending": "desc"
}

View File

@@ -0,0 +1,6 @@
[
"zh-CN",
"en-US"
]

View File

@@ -0,0 +1,35 @@
{
"ADD_FILTER": "添加条件",
"Filters": "筛选条件",
"When": "当",
"and": "并且",
"or": "或",
"ColumnId": "列",
"Func": "运算符",
"Expected": "值",
"SDF_Equals": "=",
"SDF_NotEquals": "!=",
"SDF_Contains": "in",
"SDF_NotContains": "not in",
"SDF_StartsWith": "left like",
"SDF_NotStartsWith": "not left like",
"SDF_EndsWith": "right like",
"SDF_NotEndsWith": "not right like",
"SDF_Set": "存在",
"SDF_NotSet": "不存在",
"SDF_Gt": ">",
"SDF_Gte": ">=",
"SDF_Lt": "<",
"SDF_Lte": "<=",
"SDF_BeforeDate": "<",
"SDF_BeforeOrOnDate": "<=",
"SDF_AfterDate": ">",
"SDF_AfterOrOnDate": ">=",
"SDF_True": "true",
"SDF_False": "false",
"SortBy": "Sort by",
"ThenBy": "Then By",
"Sorting": "排序",
"ascending": "asc",
"descending": "desc"
}