Skip to content

Commit 518b85b

Browse files
authored
feat(Table): advance search dialog support UseSearchForm parameter (#7783)
* refactor: 调整 UseSearchForm 优先级 * feat: 增加 BuildSearchMetaData 扩展方法 * refactor: 移除 Model 不可为空限制 * feat(SearchDialog): 增加 UseSearchForm 参数 * feat: 增加 Reset 方法用于清空搜索条件 * refactor: 调整参数名称 * refactor: 增加参数传递逻辑 * feat: 表格高级搜索支持 UseSearchForm 参数 * doc: 更新示例 * test: 更新单元测试 * refactor: 重构 Items 赋值逻辑 * refactor: 提升 UseSearchForm 优先级 * test: 增加 EditDialog 单元测试 * refactor: 更改为虚方法 * feat: 增加 Reset 方法 * refactor: 代码格式化 * test: 提高代码覆盖率 * refactor: 重构代码增加 RenderButtons 方法 * feat(SearchForm): 内置自动创建 MetaData 实例逻辑 * feat: 增加 ToFilter 扩展方法 * test: 增加 Reset 方法单元测试 * feat: SearchForm 增加 SearchFormLocalizerOptions 参数 * refactor: 方法 BuildSearchMetaData 不返回空值 * refactor: 逻辑下沉到 SearchForm 中简化表格内代码 * test: 增加 SearchForm 单元测试 * test: 增加 SearchDialog 单元测试 * refactor: 增加单元测试 * test: 增加单元测试 * test: 更新单元测试 * refactor: 重构扩展方法 CreateSearchItemComponentByMetadata * doc: 更新示例 * refactor: 重构 Meta 文件名 * refactor: 重命名类型 * refactor: 属性重命名 * refactor: 重命名
1 parent d3661a7 commit 518b85b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+840
-301
lines changed

src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,29 @@
1111
<p>@((MarkupString)Localizer["SearchTableTips"].Value)</p>
1212
</Tips>
1313

14+
<DemoBlock Title="@Localizer["SearchFormTitle"]"
15+
Introduction="@Localizer["SearchFormIntro"]"
16+
Name="SearchForm">
17+
<section ignore>
18+
<p>@((MarkupString)Localizer["SearchFormDesc"].Value)</p>
19+
<div>@((MarkupString)Localizer["SearchFormTips"].Value)</div>
20+
</section>
21+
<Table TItem="Foo"
22+
IsPagination="true" PageItemsSource="@PageItemsSource" SearchMode="SearchMode.Top"
23+
IsStriped="true" IsBordered="true"
24+
ShowSearch="true" UseSearchForm="true"
25+
ShowToolbar="true" IsMultipleSelect="true" ShowExtendButtons="true"
26+
OnQueryAsync="@OnQueryAsync" OnAddAsync="@OnAddAsync" OnSaveAsync="@OnSaveAsync" OnDeleteAsync="@OnDeleteAsync">
27+
<TableColumns>
28+
<TableColumn @bind-Field="@context.DateTime" Width="180" Searchable="true" />
29+
<TableColumn @bind-Field="@context.Name" Searchable="true" SearchFormItemMetadata="_nameSearchFormItemMetadata" />
30+
<TableColumn @bind-Field="@context.Address" Searchable="true" SearchFormItemMetadata="_addressSearchFormItemMetadata" />
31+
<TableColumn @bind-Field="@context.Count" Searchable="true" />
32+
<TableColumn @bind-Field="@context.Education" Searchable="true" />
33+
</TableColumns>
34+
</Table>
35+
</DemoBlock>
36+
1437
<DemoBlock Title="@Localizer["SearchTableTitle"]"
1538
Introduction="@Localizer["SearchTableIntro"]"
1639
Name="SearchTable">
@@ -65,19 +88,22 @@
6588
items = items.Where(options.Searches.GetFilterFunc&lt;Foo&gt;(FilterLogic.Or));
6689
}</Pre>
6790
<GroupBox Title="@Localizer["AutoGenerateSearchGroupBoxTitle"]" class="mb-3">
68-
<div class="row g-3 form-inline">
69-
<div class="col-12 col-sm-3">
91+
<div class="bb-grid">
92+
<div class="bb-grid-item">
7093
<Switch DisplayText="@Localizer["DisplayText1"]" ShowLabel="true" @bind-Value="SearchModeFlag" />
7194
</div>
72-
<div class="col-12 col-sm-3">
95+
<div class="bb-grid-item">
7396
<Switch DisplayText="@Localizer["DisplayText2"]" ShowLabel="true" @bind-Value="ShowSearchText" />
7497
</div>
75-
<div class="col-12 col-sm-3">
98+
<div class="bb-grid-item">
7699
<Switch DisplayText="@Localizer["DisplayText3"]" ShowLabel="true" @bind-Value="ShowResetButton" />
77100
</div>
78-
<div class="col-12 col-sm-3">
101+
<div class="bb-grid-item">
79102
<Switch DisplayText="@Localizer["DisplayText4"]" ShowLabel="true" @bind-Value="ShowSearchButton" IsDisabled="SearchModeFlag" />
80103
</div>
104+
<div class="bb-grid-item">
105+
<Switch DisplayText="@Localizer["DisplayText5"]" ShowLabel="true" @bind-Value="UseSearchForm" />
106+
</div>
81107
</div>
82108
</GroupBox>
83109
</section>
@@ -87,6 +113,7 @@
87113
ShowToolbar="true" IsMultipleSelect="true" ShowExtendButtons="true"
88114
ShowSearch="true" ShowResetButton="ShowResetButton" ShowSearchButton="ShowSearchButton" ShowSearchText="ShowSearchText"
89115
AddModalTitle="@Localizer["AddModelTitle"]" EditModalTitle="@Localizer["EditModelTitle"]"
116+
UseSearchForm="UseSearchForm" SearchItems="_searchItems"
90117
OnQueryAsync="@OnQueryAsync" OnAddAsync="@OnAddAsync" OnSaveAsync="@OnSaveAsync" OnDeleteAsync="@OnDeleteAsync">
91118
<TableColumns>
92119
<TableColumn @bind-Field="@context.DateTime" Width="180" Filterable="true" />
@@ -157,25 +184,3 @@
157184
</CustomerSearchTemplate>
158185
</Table>
159186
</DemoBlock>
160-
161-
<DemoBlock Title="@Localizer["SearchFormTitle"]"
162-
Introduction="@Localizer["SearchFormIntro"]"
163-
Name="SearchForm">
164-
<section ignore>
165-
<div>@((MarkupString)Localizer["SearchFormDesc"].Value)</div>
166-
</section>
167-
<Table TItem="Foo"
168-
IsPagination="true" PageItemsSource="@PageItemsSource" SearchMode="SearchMode.Top"
169-
IsStriped="true" IsBordered="true"
170-
ShowSearch="true" UseSearchForm="true"
171-
ShowToolbar="true" IsMultipleSelect="true" ShowExtendButtons="true"
172-
OnQueryAsync="@OnQueryAsync" OnAddAsync="@OnAddAsync" OnSaveAsync="@OnSaveAsync" OnDeleteAsync="@OnDeleteAsync">
173-
<TableColumns>
174-
<TableColumn @bind-Field="@context.DateTime" Width="180" Searchable="true" />
175-
<TableColumn @bind-Field="@context.Name" Searchable="true" SearchFormItemMetaData="_nameSearchFormItemMetaData" />
176-
<TableColumn @bind-Field="@context.Address" Searchable="true" SearchFormItemMetaData="_addressSearchFormItemMetaData" />
177-
<TableColumn @bind-Field="@context.Count" Searchable="true" />
178-
<TableColumn @bind-Field="@context.Education" Searchable="true" />
179-
</TableColumns>
180-
</Table>
181-
</DemoBlock>

src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,23 @@ public partial class TablesSearch
3434

3535
private SearchMode SearchModeValue { get; set; }
3636

37+
private bool UseSearchForm { get; set; }
38+
39+
private List<ISearchItem> _searchItems = [];
40+
3741
private bool SearchModeFlag
3842
{
3943
get => SearchModeValue == SearchMode.Popup;
4044
set => SearchModeValue = value ? SearchMode.Popup : SearchMode.Top;
4145
}
4246

43-
private ISearchFormItemMetaData _nameSearchFormItemMetaData = new StringSearchMetaData()
47+
private ISearchFormItemMetadata _nameSearchFormItemMetadata = new StringSearchMetadata()
4448
{
4549
PlaceHolder = "请输入名称搜索(支持模糊匹配)",
4650
FilterAction = FilterAction.Contains,
4751
};
4852

49-
private ISearchFormItemMetaData _addressSearchFormItemMetaData = new StringSearchMetaData()
53+
private ISearchFormItemMetadata _addressSearchFormItemMetadata = new StringSearchMetadata()
5054
{
5155
PlaceHolder = "请输入地址搜索(支持模糊匹配)",
5256
FilterAction = FilterAction.Contains,
@@ -58,6 +62,7 @@ private bool SearchModeFlag
5862
protected override void OnInitialized()
5963
{
6064
base.OnInitialized();
65+
6166
Items = Foo.GenerateFoo(FooLocalizer);
6267
SearchItems = new List<SelectedItem>()
6368
{
@@ -77,6 +82,11 @@ protected override void OnInitialized()
7782
Value = Localizer["SelectedItemValue2"].Value
7883
},
7984
};
85+
86+
_searchItems = [
87+
new SearchItem(nameof(Foo.Name), typeof(string), FooLocalizer[nameof(Foo.Name)]),
88+
new SearchItem(nameof(Foo.DateTime), typeof(DateTime), FooLocalizer[nameof(Foo.DateTime)])
89+
];
8090
}
8191

8292
private static Task<Foo> OnAddAsync() => Task.FromResult(new Foo() { DateTime = DateTime.Now });

src/BootstrapBlazor.Server/Locales/en-US.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5061,10 +5061,12 @@
50615061
"DisplayText2": "Fuzzy Search",
50625062
"DisplayText3": "Display reset",
50635063
"DisplayText4": "Display Search",
5064+
"DisplayText5": "SearchForm",
50645065
"EditModelTitle": "Edit Test Data Window",
50655066
"NamePlaceholder": "Please enter your name within 50 characters",
5066-
"SearchFormDesc": "When <code>UseSearchForm</code> is enabled and <code>SearchItems</code> is not provided, it will default to using <code>TableColumn</code> with <code>Searchable=\"true\"</code>. You can customize the metadata through the <code>SearchFormItemMetaData</code> property in <code>TableColumn</code>",
5067+
"SearchFormDesc": "When <code>UseSearchForm</code> is enabled and <code>SearchItems</code> is not provided, it will default to using <code>TableColumn</code> with <code>Searchable=\"true\"</code>. You can customize the metadata through the <code>SearchFormItemMetadata</code> property in <code>TableColumn</code>",
50675068
"SearchFormIntro": "Enable the search form feature by setting <code>UseSearchForm=\"true\"</code>, and configure the search items within the form using <code>SearchItems</code>, suitable for scenarios with custom complex search conditions",
5069+
"SearchFormTips": "Enabling <code>UseSearchForm</code> will prevent <code>SearchTemplate</code>, <code>CustomerSearchModel</code>, and <code>CustomerSearchTemplate</code> from taking effect.",
50685070
"SearchFormTitle": "Search Form",
50695071
"SearchTableGroupBoxText": "Search Criteria",
50705072
"SearchTableIntro": "Set <code>ShowSearch</code> to display the query component, customize the search UI by setting the <code>SearchTemplate</code> template",

src/BootstrapBlazor.Server/Locales/zh-CN.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5061,9 +5061,11 @@
50615061
"DisplayText2": "模糊搜索",
50625062
"DisplayText3": "显示清空",
50635063
"DisplayText4": "显示搜索",
5064+
"DisplayText5": "搜索表单",
50645065
"EditModelTitle": "编辑测试数据窗口",
50655066
"NamePlaceholder": "请输入姓名,50字以内",
5066-
"SearchFormDesc": "使用 <code>UseSearchForm</code> 开启搜索表单时未提供 <code>SearchItems</code> 默认尝试使用设置 <code>Searchable=\"true\"</code> 的 <code>TableColumn</code> 进行构建,可以通过 <code>TableColumn</code> 中的 <code>SearchFormItemMetaData</code> 属性定制化元数据",
5067+
"SearchFormDesc": "使用 <code>UseSearchForm</code> 开启搜索表单时未提供 <code>SearchItems</code> 默认尝试使用设置 <code>Searchable=\"true\"</code> 的 <code>TableColumn</code> 进行构建,可以通过 <code>TableColumn</code> 中的 <code>SearchFormItemMetadata</code> 属性定制化元数据",
5068+
"SearchFormTips": "开启 <code>UseSearchForm</code> 后 <code>SearchTemplate</code> <code>CustomerSearchModel</code> <code>CustomerSearchTemplate</code> 均不生效",
50675069
"SearchFormIntro": "通过设置 <code>UseSearchForm=\"true\"</code> 开启搜索表单功能,通过 <code>SearchItems</code> 配置搜索表单内搜索项,适用于自定义复杂搜索条件的场景",
50685070
"SearchFormTitle": "搜索表单",
50695071
"SearchTableGroupBoxText": "搜索条件",

src/BootstrapBlazor.Server/wwwroot/css/site.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,3 +398,21 @@ code {
398398
.icon-list .bb-iconpark-icon {
399399
--bb-svg-icon-width: 16px;
400400
}
401+
402+
.bb-grid {
403+
display: grid;
404+
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
405+
}
406+
407+
.bb-grid-item {
408+
display: flex;
409+
flex-direction: row;
410+
align-items: center;
411+
}
412+
413+
.bb-grid-item > .form-label {
414+
min-width: 80px;
415+
text-align: right;
416+
margin-right: .5rem;
417+
margin-bottom: 0;
418+
}

src/BootstrapBlazor/Attributes/AutoGenerateColumnAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public class AutoGenerateColumnAttribute : AutoGenerateBaseAttribute, ITableColu
180180

181181
bool? ITableColumn.Searchable { get => Searchable; set => Searchable = value ?? false; }
182182

183-
ISearchFormItemMetaData? ITableColumn.SearchFormItemMetaData { get; set; }
183+
ISearchFormItemMetadata? ITableColumn.SearchFormItemMetadata { get; set; }
184184

185185
bool? ITableColumn.Filterable { get => Filterable; set => Filterable = value ?? false; }
186186

src/BootstrapBlazor/Components/Dialog/DialogBase.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,9 @@ public abstract class DialogBase<TModel> : BootstrapModuleComponentBase
6969
public bool ShowUnsetGroupItemsOnTop { get; set; }
7070

7171
/// <summary>
72-
/// <para lang="zh">OnInitialized 方法</para>
73-
/// <para lang="en">OnInitialized Method</para>
72+
/// <para lang="zh">通过模型标签获得所有搜索列集合</para>
73+
/// <para lang="en">Gets all searchable columns by model attributes</para>
7474
/// </summary>
75-
protected override void OnInitialized()
76-
{
77-
base.OnInitialized();
78-
79-
if (Model == null)
80-
{
81-
throw new InvalidOperationException("Model value not set to null");
82-
}
83-
}
75+
/// <returns></returns>
76+
protected IEnumerable<IEditorItem> GetItemsByColumns() => Utility.GenerateColumns<TModel>(item => item.GetSearchable());
8477
}

src/BootstrapBlazor/Components/Dialog/EditDialog.razor.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ protected override void OnParametersSet()
165165
{
166166
base.OnParametersSet();
167167

168+
if (Model == null)
169+
{
170+
throw new InvalidOperationException($"参数 {nameof(Model)} 未赋值; {nameof(Model)} can not be null.");
171+
}
172+
168173
CloseButtonIcon ??= IconTheme.GetIconByKey(ComponentIcons.DialogCloseButtonIcon);
169174
SaveButtonIcon ??= IconTheme.GetIconByKey(ComponentIcons.DialogSaveButtonIcon);
170175

@@ -173,6 +178,11 @@ protected override void OnParametersSet()
173178

174179
CloseConfirmTitle ??= Localizer[nameof(CloseConfirmTitle)];
175180
CloseConfirmContent ??= Localizer[nameof(CloseConfirmContent)];
181+
182+
if (BodyTemplate == null)
183+
{
184+
Items ??= GetItemsByColumns();
185+
}
176186
}
177187

178188
private async Task<bool> OnClosingCallback()

src/BootstrapBlazor/Components/Dialog/SearchDialog.razor

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
1-
@namespace BootstrapBlazor.Components
1+
@namespace BootstrapBlazor.Components
22
@typeparam TModel
33
@inherits DialogBase<TModel>
44

5-
@if (BodyTemplate != null)
5+
@if (UseSearchForm)
6+
{
7+
<SearchForm Items="SearchItems" RowType="RowType" ItemsPerRow="ItemsPerRow" LabelAlign="LabelAlign" ShowLabel="ShowLabel" ShowUnsetGroupItemsOnTop="ShowUnsetGroupItemsOnTop"
8+
SearchFormLocalizerOptions="SearchFormLocalizerOptions" OnChanged="OnSearchFormFilterChanged">
9+
<Buttons>
10+
@RenderButtons
11+
</Buttons>
12+
</SearchForm>
13+
}
14+
else if (BodyTemplate != null)
615
{
716
<div class="form-body">
817
@BodyTemplate.Invoke(Model)
918
</div>
1019
<div class="form-footer">
11-
<DialogCloseButton Icon="@ClearIcon" Text="@ResetButtonText" OnClickWithoutRender="@OnResetSearchClick" />
12-
<DialogCloseButton Color="Color.Primary" Icon="@SearchIcon" Text="@QueryButtonText" OnClickWithoutRender="@OnSearchClick" />
20+
@RenderButtons
1321
</div>
1422
}
1523
else
1624
{
1725
<CascadingValue Value="true" IsFixed="true" Name="IsSearch">
1826
<EditorForm TModel="TModel" Model="Model" Items="Items" RowType="RowType" ItemsPerRow="ItemsPerRow" LabelAlign="LabelAlign" ShowLabel="ShowLabel" ShowUnsetGroupItemsOnTop="ShowUnsetGroupItemsOnTop">
1927
<Buttons>
20-
<DialogCloseButton Icon="@ClearIcon" Text="@ResetButtonText" OnClickWithoutRender="@OnResetSearchClick" />
21-
<DialogCloseButton Color="Color.Primary" Icon="@SearchIcon" Text="@QueryButtonText" OnClickWithoutRender="@OnSearchClick" />
28+
@RenderButtons
2229
</Buttons>
2330
</EditorForm>
2431
</CascadingValue>

src/BootstrapBlazor/Components/Dialog/SearchDialog.razor.cs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,34 @@ public partial class SearchDialog<TModel>
5959
[Parameter]
6060
public string? SearchIcon { get; set; }
6161

62+
/// <summary>
63+
/// <para lang="zh">获得/设置 是否使用 SearchForm 组件进行搜索条件编辑 默认 false 不使用</para>
64+
/// <para lang="en">Gets or sets Whether to use SearchForm component for editing search conditions. Default is false</para>
65+
/// </summary>
66+
[Parameter]
67+
public bool UseSearchForm { get; set; }
68+
69+
/// <summary>
70+
/// <para lang="zh">获得/设置 搜索表单项集合</para>
71+
/// <para lang="en">Gets or sets Search Form Items collection</para>
72+
/// </summary>
73+
[Parameter]
74+
public List<ISearchItem>? SearchItems { get; set; }
75+
76+
/// <summary>
77+
/// <para lang="zh">获得/设置 过滤器改变回调事件 Func 版本</para>
78+
/// <para lang="en">Gets or sets the filter changed callback event Func version</para>
79+
/// </summary>
80+
[Parameter]
81+
public Func<FilterKeyValueAction, Task>? OnChanged { get; set; }
82+
83+
/// <summary>
84+
/// <para lang="zh">获得/设置 搜索表单本地化配置项</para>
85+
/// <para lang="en">Gets or sets Search Form Localization Options</para>
86+
/// </summary>
87+
[Parameter]
88+
public SearchFormLocalizerOptions? SearchFormLocalizerOptions { get; set; }
89+
6290
[Inject]
6391
[NotNull]
6492
private IStringLocalizer<SearchDialog<TModel>>? Localizer { get; set; }
@@ -68,8 +96,7 @@ public partial class SearchDialog<TModel>
6896
private IIconTheme? IconTheme { get; set; }
6997

7098
/// <summary>
71-
/// <para lang="zh">OnParametersSet 方法</para>
72-
/// <para lang="en">OnParametersSet Method</para>
99+
/// <inheritdoc/>
73100
/// </summary>
74101
protected override void OnParametersSet()
75102
{
@@ -80,5 +107,42 @@ protected override void OnParametersSet()
80107

81108
ClearIcon ??= IconTheme.GetIconByKey(ComponentIcons.SearchDialogClearIcon);
82109
SearchIcon ??= IconTheme.GetIconByKey(ComponentIcons.SearchDialogSearchIcon);
110+
111+
if (UseSearchForm)
112+
{
113+
return;
114+
}
115+
116+
if (BodyTemplate != null)
117+
{
118+
return;
119+
}
120+
121+
Items ??= GetItemsByColumns();
83122
}
123+
124+
private async Task OnSearchFormFilterChanged(FilterKeyValueAction action)
125+
{
126+
// 通知父组件过滤器改变事件,此时并没有触发 OnSearchClick 搜索事件,父组件可以在 OnChanged 事件中获取当前过滤器状态并决定是否触发搜索事件
127+
if (OnChanged != null)
128+
{
129+
await OnChanged(action);
130+
}
131+
}
132+
133+
private RenderFragment RenderButtons => builder =>
134+
{
135+
builder.OpenComponent<DialogCloseButton>(0);
136+
builder.AddAttribute(10, nameof(DialogCloseButton.Icon), ClearIcon);
137+
builder.AddAttribute(20, nameof(DialogCloseButton.Text), ResetButtonText);
138+
builder.AddAttribute(30, nameof(DialogCloseButton.OnClickWithoutRender), OnResetSearchClick);
139+
builder.CloseComponent();
140+
141+
builder.OpenComponent<DialogCloseButton>(100);
142+
builder.AddAttribute(101, nameof(DialogCloseButton.Color), Color.Primary);
143+
builder.AddAttribute(110, nameof(DialogCloseButton.Icon), SearchIcon);
144+
builder.AddAttribute(120, nameof(DialogCloseButton.Text), QueryButtonText);
145+
builder.AddAttribute(130, nameof(DialogCloseButton.OnClickWithoutRender), OnSearchClick);
146+
builder.CloseComponent();
147+
};
84148
}

0 commit comments

Comments
 (0)