diff --git a/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor.cs index 5b4d2ccd6f7..f647ca7c1b8 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor.cs @@ -159,11 +159,12 @@ private Task> OnSearchModelQueryAsync(QueryPageOptions options) private Task> OnQueryAsync(QueryPageOptions options) { // 使用内置扩展方法 ToFilter 获得过滤条件 - // 目前 ToFilterFunc 无法解决大小写敏感问题 + // 解决大小写敏感问题使用参数 StringComparison.OrdinalIgnoreCase + // 注意 EFCore 不支持 StringComparison.OrdinalIgnoreCase 需要使用 EF.Functions.Like 进行模糊搜索 var items = Items.Where(options.ToFilterFunc()); if (!string.IsNullOrEmpty(options.SearchText)) { - // 使用 Linq 处理 + // 使用 Linq 处理 处理模糊搜索 处理大小写敏感问题使用参数 StringComparison.OrdinalIgnoreCase items = Items.Where(i => (!string.IsNullOrEmpty(i.Name) && i.Name.Contains(options.SearchText, StringComparison.OrdinalIgnoreCase)) || (!string.IsNullOrEmpty(i.Address) && i.Address.Contains(options.SearchText, StringComparison.OrdinalIgnoreCase))); diff --git a/src/BootstrapBlazor/Components/SearchForm/SearchForm.razor.cs b/src/BootstrapBlazor/Components/SearchForm/SearchForm.razor.cs index 60e64c2022d..da4413a6bee 100644 --- a/src/BootstrapBlazor/Components/SearchForm/SearchForm.razor.cs +++ b/src/BootstrapBlazor/Components/SearchForm/SearchForm.razor.cs @@ -121,12 +121,12 @@ public partial class SearchForm : IShowLabel .AddClass($"--bb-row-label-width: {LabelWidth}px;", LabelWidth.HasValue) .Build(); - private IEnumerable UnsetGroupItems => Items.Where(i => string.IsNullOrEmpty(i.GroupName)); + private IEnumerable UnsetGroupItems => Items.Where(i => string.IsNullOrEmpty(i.GroupName)).OrderBy(i => i.Order); private IEnumerable>> GroupItems => Items .Where(i => !string.IsNullOrEmpty(i.GroupName)) .GroupBy(i => i.GroupName).OrderBy(i => i.Key) - .Select(i => new KeyValuePair>(i.First().GroupName!, i.OrderBy(x => x.Order))); + .Select(i => new KeyValuePair>(i.First().GroupName!, i.OrderBy(x => x.GroupOrder))); /// /// diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.Search.cs b/src/BootstrapBlazor/Components/Table/Table.razor.Search.cs index ae8fefa59aa..0269069f4f4 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.Search.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.Search.cs @@ -182,11 +182,28 @@ private IEnumerable SearchFormItems NumberEndValueLabelText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.NumberEndValueLabelText)] }; } - _searchItems ??= SearchItems ?? GetSearchColumns().Select(i => i.ParseSearchItem(SearchFormLocalizerOptions.Value)).ToList(); + _searchItems ??= GetSearchItems(SearchFormLocalizerOptions.Value); return _searchItems; } } + private IEnumerable GetSearchItems(SearchFormLocalizerOptions options) + { + if (SearchItems != null) + { + // TODO: 增加内部创建默认 ISearchItemMetaData 逻辑,减少用户使用成本 + //foreach (var item in SearchItems) + //{ + // // 创建默认 ISearchItemMetaData + // item.MetaData ??= column.BuildSearchMetaData(options); + //} + + return SearchItems; + } + + return GetSearchColumns().Select(i => i.ParseSearchItem(options)).ToList(); + } + private Task OnSearchFormFilterChanged(FilterKeyValueAction action) { _searchFilter = action; diff --git a/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs b/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs index edc909651a9..7534e364570 100644 --- a/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs +++ b/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs @@ -202,6 +202,7 @@ private static void CopyValue(this ITableColumn col, ITableColumn dest) if (col.IsRequiredWhenAdd.HasValue) dest.IsRequiredWhenAdd = col.IsRequiredWhenAdd; if (col.IsRequiredWhenEdit.HasValue) dest.IsRequiredWhenEdit = col.IsRequiredWhenEdit; if (col.IgnoreWhenExport.HasValue) dest.IgnoreWhenExport = col.IgnoreWhenExport; + if (col.SearchFormItemMetaData != null) dest.SearchFormItemMetaData = col.SearchFormItemMetaData; } /// diff --git a/test/UnitTest/Attributes/AutoGenerateClassTest.cs b/test/UnitTest/Attributes/AutoGenerateClassTest.cs index bc7a47434ec..a4c5ef3155f 100644 --- a/test/UnitTest/Attributes/AutoGenerateClassTest.cs +++ b/test/UnitTest/Attributes/AutoGenerateClassTest.cs @@ -210,6 +210,9 @@ public void AutoGenerateColumn_Ok() attrInterface.IsRequiredWhenEdit = true; Assert.True(attrInterface.IsRequiredWhenEdit); + attrInterface.SearchFormItemMetaData = new StringSearchMetaData(); + Assert.NotNull(attrInterface.SearchFormItemMetaData); + var attrEditor = (IEditorItem)attr; attrEditor.Items = null; Assert.Null(attrEditor.Items); diff --git a/test/UnitTest/Components/InternalTableColumnTest.cs b/test/UnitTest/Components/InternalTableColumnTest.cs index d0dd81bdab8..bd7445f72d0 100644 --- a/test/UnitTest/Components/InternalTableColumnTest.cs +++ b/test/UnitTest/Components/InternalTableColumnTest.cs @@ -87,6 +87,7 @@ public void InternalTableColumn_Ok() SetValue("IsRequiredWhenEdit", true); SetValue("LookupService", null); SetValue("IgnoreWhenExport", true); + SetValue("SearchFormItemMetaData", new StringSearchMetaData()); void SetValue(string propertyName, object? val) => type!.GetProperty(propertyName)!.SetValue(instance, val); } diff --git a/test/UnitTest/Components/SearchFormTest.cs b/test/UnitTest/Components/SearchFormTest.cs index 2581f88fa74..488b26ff700 100644 --- a/test/UnitTest/Components/SearchFormTest.cs +++ b/test/UnitTest/Components/SearchFormTest.cs @@ -55,6 +55,9 @@ public async Task Filter_Ok() await stringSearchMetaData.ValueChangedHandler("test1"); Assert.Single(filterKeyValueAction.Filters); Assert.Equal("test1", filterKeyValueAction.Filters[0].FieldValue); + + var searchForm = cut.Instance; + Assert.NotNull(searchForm.Filter); } [Fact] @@ -73,7 +76,9 @@ public void LabelAlign_Ok() { new SearchItem(nameof(Foo.Name), typeof(string), "Name") { + Text = "Name-Updated", GroupName = "Group1", + GroupOrder = 1, MetaData = stringSearchMetaData }, new SearchItem(nameof(Foo.Address), typeof(string), "Address") @@ -93,7 +98,7 @@ public void LabelAlign_Ok() } [Fact] - public void ShowUnsetGroupItemsOnTop_Ok() + public void Group_Ok() { var cut = Context.Render(pb => { @@ -103,6 +108,7 @@ public void ShowUnsetGroupItemsOnTop_Ok() new SearchItem(nameof(Foo.Name), typeof(string), "Name") { GroupName = "Group1", + GroupOrder= 1, MetaData = new StringSearchMetaData() }, new SearchItem(nameof(Foo.Address), typeof(string), "Address") @@ -111,6 +117,12 @@ public void ShowUnsetGroupItemsOnTop_Ok() } }); }); + + // 查找标签一共有两个第一个应该是 Address 第二个应该是 Name + var labels = cut.FindAll(".bb-search-form label"); + Assert.Equal(2, labels.Count); + Assert.Equal("Address", labels[0].TextContent); + Assert.Equal("Name", labels[1].TextContent); } [Fact] diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index 1da1c5d7d7c..2a7dc14e032 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -665,6 +665,52 @@ public async Task UseSearchForm_Ok() await cut.InvokeAsync(() => table.Instance.QueryAsync()); } + [Fact] + public async Task SearchItems_Ok() + { + var localizer = Context.Services.GetRequiredService>(); + var cut = Context.Render(pb => + { + pb.AddChildContent>(pb => + { + pb.Add(a => a.RenderMode, TableRenderMode.Table); + pb.Add(a => a.ShowToolbar, true); + pb.Add(a => a.ShowSearch, true); + pb.Add(a => a.UseSearchForm, true); + pb.Add(a => a.SearchItems, new List() + { + new SearchItem("Name", typeof(string), "名称") { MetaData = new StringSearchMetaData() } + }); + pb.Add(a => a.SearchMode, SearchMode.Top); + pb.Add(a => a.OnQueryAsync, OnQueryAsync(localizer)); + pb.Add(a => a.TableColumns, foo => builder => + { + builder.OpenComponent>(0); + builder.AddAttribute(1, "Field", ""); + builder.AddAttribute(2, "FieldExpression", Utility.GenerateValueExpression(foo, "Name", typeof(string))); + builder.CloseComponent(); + }); + }); + }); + + cut.Contains("bb-editor bb-search-form"); + + // 触发 Filter + var searchForm = cut.FindComponent(); + Assert.NotNull(searchForm); + + var input = searchForm.FindComponent>(); + Assert.NotNull(input); + + var cb = input.Instance.OnValueChanged; + Assert.NotNull(cb); + await cb("test"); + + var table = cut.FindComponent>(); + Assert.NotNull(table); + await cut.InvokeAsync(() => table.Instance.QueryAsync()); + } + [Fact] public void ShowToolbar_Ok() { diff --git a/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs b/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs index 6a02b90fc6b..458191b5e88 100644 --- a/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs +++ b/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs @@ -23,7 +23,7 @@ public void InheritValue_Ok() Sortable = true, TextEllipsis = true, ShowCopyColumn = true, - Visible = false, + Visible = false }; col.InheritValue(attr); Assert.Equal(Alignment.Center, col.Align); @@ -113,7 +113,9 @@ public void CopyValue_Ok() Required = true, RequiredErrorMessage = "test", IsRequiredWhenAdd = true, - IsRequiredWhenEdit = true + IsRequiredWhenEdit = true, + + SearchFormItemMetaData = new StringSearchMetaData() }; col.CopyValue(attr); Assert.NotNull(col.ComponentType); @@ -183,6 +185,8 @@ public void CopyValue_Ok() Assert.NotNull(col.LookupService); Assert.Equal("test-key", col.LookupServiceKey); Assert.Equal(true, col.LookupServiceData); + + Assert.NotNull(col.SearchFormItemMetaData); } [Fact]