From 3151b1b806843fe031d6982faa1a0e443a59df78 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 17 Mar 2026 08:26:38 +0800 Subject: [PATCH 01/12] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=20Internal?= =?UTF-8?q?Table=20=E5=86=85=E9=83=A8=E6=8B=B7=E8=B4=9D=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs | 1 + 1 file changed, 1 insertion(+) 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; } /// From 614a4d15aec9137082174e1949895777a944162f Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 17 Mar 2026 08:26:45 +0800 Subject: [PATCH 02/12] =?UTF-8?q?test:=20=E6=9B=B4=E6=96=B0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Extensions/ITableColumnExtensionsTest.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs b/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs index 6a02b90fc6b..f29a95efb88 100644 --- a/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs +++ b/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs @@ -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] From fdecf5ce68656b3c0ceca77bfcde5c13a496d202 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 18 Mar 2026 08:26:47 +0800 Subject: [PATCH 03/12] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/Table/TablesSearch.razor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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))); From 1fe0c6215fb2b7054b801dd8738094a15d0f4f5f Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 18 Mar 2026 08:31:45 +0800 Subject: [PATCH 04/12] =?UTF-8?q?test:=20=E6=8F=90=E9=AB=98=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=A6=86=E7=9B=96=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Attributes/AutoGenerateClassTest.cs | 3 +++ 1 file changed, 3 insertions(+) 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); From bbbbe016267c3a93ff950ac1217acd6455233545 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 18 Mar 2026 08:34:59 +0800 Subject: [PATCH 05/12] =?UTF-8?q?test:=20=E6=8F=90=E9=AB=98=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=A6=86=E7=9B=96=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Components/SearchFormTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/UnitTest/Components/SearchFormTest.cs b/test/UnitTest/Components/SearchFormTest.cs index 2581f88fa74..93f56a9761e 100644 --- a/test/UnitTest/Components/SearchFormTest.cs +++ b/test/UnitTest/Components/SearchFormTest.cs @@ -73,6 +73,7 @@ public void LabelAlign_Ok() { new SearchItem(nameof(Foo.Name), typeof(string), "Name") { + Text = "Name-Updated", GroupName = "Group1", MetaData = stringSearchMetaData }, From 023bd5760b348fa960982b4bfc8443b77e1c3413 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 18 Mar 2026 09:21:57 +0800 Subject: [PATCH 06/12] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=20SearchFormIt?= =?UTF-8?q?emMetaData=20=E4=BB=A3=E7=A0=81=E8=A6=86=E7=9B=96=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Components/InternalTableColumnTest.cs | 1 + 1 file changed, 1 insertion(+) 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); } From 9de15c6fea30d84e206bf6846ebeae17bd6bd3c1 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 18 Mar 2026 09:22:10 +0800 Subject: [PATCH 07/12] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=20GroupOrder?= =?UTF-8?q?=20=E8=A6=86=E7=9B=96=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Components/SearchFormTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/UnitTest/Components/SearchFormTest.cs b/test/UnitTest/Components/SearchFormTest.cs index 93f56a9761e..2fb366c0adc 100644 --- a/test/UnitTest/Components/SearchFormTest.cs +++ b/test/UnitTest/Components/SearchFormTest.cs @@ -75,6 +75,7 @@ public void LabelAlign_Ok() { Text = "Name-Updated", GroupName = "Group1", + GroupOrder = 1, MetaData = stringSearchMetaData }, new SearchItem(nameof(Foo.Address), typeof(string), "Address") From 1812133675dc23253f8a9f283def10b6671b26e7 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 18 Mar 2026 09:22:21 +0800 Subject: [PATCH 08/12] =?UTF-8?q?doc:=20=E4=BB=A3=E7=A0=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Extensions/ITableColumnExtensionsTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs b/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs index f29a95efb88..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); From 6404734de994211601a4107da7a64d5c3b42d894 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 18 Mar 2026 09:40:35 +0800 Subject: [PATCH 09/12] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=20SearchIt?= =?UTF-8?q?ems=20=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Table/Table.razor.Search.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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; From fe1c6fb7f6abe21362ce239ca6d469a3a16ff6ec Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 18 Mar 2026 09:40:52 +0800 Subject: [PATCH 10/12] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=20SearchItems?= =?UTF-8?q?=20=E4=BB=A3=E7=A0=81=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Components/TableTest.cs | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) 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() { From 4b5d20cbb2fdf029ebd41c779efc1169b7b21ead Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 18 Mar 2026 10:04:07 +0800 Subject: [PATCH 11/12] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=20Order=20?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/SearchForm/SearchForm.razor.cs | 4 ++-- test/UnitTest/Components/SearchFormTest.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) 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/test/UnitTest/Components/SearchFormTest.cs b/test/UnitTest/Components/SearchFormTest.cs index 2fb366c0adc..1340291b2e0 100644 --- a/test/UnitTest/Components/SearchFormTest.cs +++ b/test/UnitTest/Components/SearchFormTest.cs @@ -95,7 +95,7 @@ public void LabelAlign_Ok() } [Fact] - public void ShowUnsetGroupItemsOnTop_Ok() + public void Group_Ok() { var cut = Context.Render(pb => { @@ -105,6 +105,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") @@ -113,6 +114,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] From 403eb52db5d98234f33034c4d3143c2a507a6a60 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 18 Mar 2026 15:14:55 +0800 Subject: [PATCH 12/12] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Components/SearchFormTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/UnitTest/Components/SearchFormTest.cs b/test/UnitTest/Components/SearchFormTest.cs index 1340291b2e0..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]