diff --git a/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor b/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor index d3527ae19d7..2a3838689a0 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor @@ -11,6 +11,29 @@

@((MarkupString)Localizer["SearchTableTips"].Value)

+ +
+

@((MarkupString)Localizer["SearchFormDesc"].Value)

+
@((MarkupString)Localizer["SearchFormTips"].Value)
+
+ + + + + + + + +
+
+ @@ -65,19 +88,22 @@ items = items.Where(options.Searches.GetFilterFunc<Foo>(FilterLogic.Or)); } -
-
+
+
-
+
-
+
-
+
+
+ +
@@ -87,6 +113,7 @@ ShowToolbar="true" IsMultipleSelect="true" ShowExtendButtons="true" ShowSearch="true" ShowResetButton="ShowResetButton" ShowSearchButton="ShowSearchButton" ShowSearchText="ShowSearchText" AddModalTitle="@Localizer["AddModelTitle"]" EditModalTitle="@Localizer["EditModelTitle"]" + UseSearchForm="UseSearchForm" SearchItems="_searchItems" OnQueryAsync="@OnQueryAsync" OnAddAsync="@OnAddAsync" OnSaveAsync="@OnSaveAsync" OnDeleteAsync="@OnDeleteAsync"> @@ -157,25 +184,3 @@ - - -
-
@((MarkupString)Localizer["SearchFormDesc"].Value)
-
- - - - - - - - -
-
diff --git a/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor.cs index f647ca7c1b8..eac28dafe95 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor.cs @@ -34,19 +34,23 @@ public partial class TablesSearch private SearchMode SearchModeValue { get; set; } + private bool UseSearchForm { get; set; } + + private List _searchItems = []; + private bool SearchModeFlag { get => SearchModeValue == SearchMode.Popup; set => SearchModeValue = value ? SearchMode.Popup : SearchMode.Top; } - private ISearchFormItemMetaData _nameSearchFormItemMetaData = new StringSearchMetaData() + private ISearchFormItemMetadata _nameSearchFormItemMetadata = new StringSearchMetadata() { PlaceHolder = "请输入名称搜索(支持模糊匹配)", FilterAction = FilterAction.Contains, }; - private ISearchFormItemMetaData _addressSearchFormItemMetaData = new StringSearchMetaData() + private ISearchFormItemMetadata _addressSearchFormItemMetadata = new StringSearchMetadata() { PlaceHolder = "请输入地址搜索(支持模糊匹配)", FilterAction = FilterAction.Contains, @@ -58,6 +62,7 @@ private bool SearchModeFlag protected override void OnInitialized() { base.OnInitialized(); + Items = Foo.GenerateFoo(FooLocalizer); SearchItems = new List() { @@ -77,6 +82,11 @@ protected override void OnInitialized() Value = Localizer["SelectedItemValue2"].Value }, }; + + _searchItems = [ + new SearchItem(nameof(Foo.Name), typeof(string), FooLocalizer[nameof(Foo.Name)]), + new SearchItem(nameof(Foo.DateTime), typeof(DateTime), FooLocalizer[nameof(Foo.DateTime)]) + ]; } private static Task OnAddAsync() => Task.FromResult(new Foo() { DateTime = DateTime.Now }); diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 01c5e9ecd6d..8900002c188 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -5061,10 +5061,12 @@ "DisplayText2": "Fuzzy Search", "DisplayText3": "Display reset", "DisplayText4": "Display Search", + "DisplayText5": "SearchForm", "EditModelTitle": "Edit Test Data Window", "NamePlaceholder": "Please enter your name within 50 characters", - "SearchFormDesc": "When UseSearchForm is enabled and SearchItems is not provided, it will default to using TableColumn with Searchable=\"true\". You can customize the metadata through the SearchFormItemMetaData property in TableColumn", + "SearchFormDesc": "When UseSearchForm is enabled and SearchItems is not provided, it will default to using TableColumn with Searchable=\"true\". You can customize the metadata through the SearchFormItemMetadata property in TableColumn", "SearchFormIntro": "Enable the search form feature by setting UseSearchForm=\"true\", and configure the search items within the form using SearchItems, suitable for scenarios with custom complex search conditions", + "SearchFormTips": "Enabling UseSearchForm will prevent SearchTemplate, CustomerSearchModel, and CustomerSearchTemplate from taking effect.", "SearchFormTitle": "Search Form", "SearchTableGroupBoxText": "Search Criteria", "SearchTableIntro": "Set ShowSearch to display the query component, customize the search UI by setting the SearchTemplate template", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index 98b254d1041..8f412d54191 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -5061,9 +5061,11 @@ "DisplayText2": "模糊搜索", "DisplayText3": "显示清空", "DisplayText4": "显示搜索", + "DisplayText5": "搜索表单", "EditModelTitle": "编辑测试数据窗口", "NamePlaceholder": "请输入姓名,50字以内", - "SearchFormDesc": "使用 UseSearchForm 开启搜索表单时未提供 SearchItems 默认尝试使用设置 Searchable=\"true\"TableColumn 进行构建,可以通过 TableColumn 中的 SearchFormItemMetaData 属性定制化元数据", + "SearchFormDesc": "使用 UseSearchForm 开启搜索表单时未提供 SearchItems 默认尝试使用设置 Searchable=\"true\"TableColumn 进行构建,可以通过 TableColumn 中的 SearchFormItemMetadata 属性定制化元数据", + "SearchFormTips": "开启 UseSearchFormSearchTemplate CustomerSearchModel CustomerSearchTemplate 均不生效", "SearchFormIntro": "通过设置 UseSearchForm=\"true\" 开启搜索表单功能,通过 SearchItems 配置搜索表单内搜索项,适用于自定义复杂搜索条件的场景", "SearchFormTitle": "搜索表单", "SearchTableGroupBoxText": "搜索条件", diff --git a/src/BootstrapBlazor.Server/wwwroot/css/site.css b/src/BootstrapBlazor.Server/wwwroot/css/site.css index 2e76660c99a..4479a59f0e9 100644 --- a/src/BootstrapBlazor.Server/wwwroot/css/site.css +++ b/src/BootstrapBlazor.Server/wwwroot/css/site.css @@ -398,3 +398,21 @@ code { .icon-list .bb-iconpark-icon { --bb-svg-icon-width: 16px; } + +.bb-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); +} + +.bb-grid-item { + display: flex; + flex-direction: row; + align-items: center; +} + + .bb-grid-item > .form-label { + min-width: 80px; + text-align: right; + margin-right: .5rem; + margin-bottom: 0; + } diff --git a/src/BootstrapBlazor/Attributes/AutoGenerateColumnAttribute.cs b/src/BootstrapBlazor/Attributes/AutoGenerateColumnAttribute.cs index d7d6c0784ef..cb307e7320e 100644 --- a/src/BootstrapBlazor/Attributes/AutoGenerateColumnAttribute.cs +++ b/src/BootstrapBlazor/Attributes/AutoGenerateColumnAttribute.cs @@ -180,7 +180,7 @@ public class AutoGenerateColumnAttribute : AutoGenerateBaseAttribute, ITableColu bool? ITableColumn.Searchable { get => Searchable; set => Searchable = value ?? false; } - ISearchFormItemMetaData? ITableColumn.SearchFormItemMetaData { get; set; } + ISearchFormItemMetadata? ITableColumn.SearchFormItemMetadata { get; set; } bool? ITableColumn.Filterable { get => Filterable; set => Filterable = value ?? false; } diff --git a/src/BootstrapBlazor/Components/Dialog/DialogBase.cs b/src/BootstrapBlazor/Components/Dialog/DialogBase.cs index efd76d67d26..705718a649f 100644 --- a/src/BootstrapBlazor/Components/Dialog/DialogBase.cs +++ b/src/BootstrapBlazor/Components/Dialog/DialogBase.cs @@ -69,16 +69,9 @@ public abstract class DialogBase : BootstrapModuleComponentBase public bool ShowUnsetGroupItemsOnTop { get; set; } /// - /// OnInitialized 方法 - /// OnInitialized Method + /// 通过模型标签获得所有搜索列集合 + /// Gets all searchable columns by model attributes /// - protected override void OnInitialized() - { - base.OnInitialized(); - - if (Model == null) - { - throw new InvalidOperationException("Model value not set to null"); - } - } + /// + protected IEnumerable GetItemsByColumns() => Utility.GenerateColumns(item => item.GetSearchable()); } diff --git a/src/BootstrapBlazor/Components/Dialog/EditDialog.razor.cs b/src/BootstrapBlazor/Components/Dialog/EditDialog.razor.cs index 8c02b419e85..8a5b2038403 100644 --- a/src/BootstrapBlazor/Components/Dialog/EditDialog.razor.cs +++ b/src/BootstrapBlazor/Components/Dialog/EditDialog.razor.cs @@ -165,6 +165,11 @@ protected override void OnParametersSet() { base.OnParametersSet(); + if (Model == null) + { + throw new InvalidOperationException($"参数 {nameof(Model)} 未赋值; {nameof(Model)} can not be null."); + } + CloseButtonIcon ??= IconTheme.GetIconByKey(ComponentIcons.DialogCloseButtonIcon); SaveButtonIcon ??= IconTheme.GetIconByKey(ComponentIcons.DialogSaveButtonIcon); @@ -173,6 +178,11 @@ protected override void OnParametersSet() CloseConfirmTitle ??= Localizer[nameof(CloseConfirmTitle)]; CloseConfirmContent ??= Localizer[nameof(CloseConfirmContent)]; + + if (BodyTemplate == null) + { + Items ??= GetItemsByColumns(); + } } private async Task OnClosingCallback() diff --git a/src/BootstrapBlazor/Components/Dialog/SearchDialog.razor b/src/BootstrapBlazor/Components/Dialog/SearchDialog.razor index 5b61cea04c3..b7e6b419316 100644 --- a/src/BootstrapBlazor/Components/Dialog/SearchDialog.razor +++ b/src/BootstrapBlazor/Components/Dialog/SearchDialog.razor @@ -1,15 +1,23 @@ -@namespace BootstrapBlazor.Components +@namespace BootstrapBlazor.Components @typeparam TModel @inherits DialogBase -@if (BodyTemplate != null) +@if (UseSearchForm) +{ + + + @RenderButtons + + +} +else if (BodyTemplate != null) {
@BodyTemplate.Invoke(Model)
} else @@ -17,8 +25,7 @@ else - - + @RenderButtons diff --git a/src/BootstrapBlazor/Components/Dialog/SearchDialog.razor.cs b/src/BootstrapBlazor/Components/Dialog/SearchDialog.razor.cs index 3e09c49f490..217ff345c80 100644 --- a/src/BootstrapBlazor/Components/Dialog/SearchDialog.razor.cs +++ b/src/BootstrapBlazor/Components/Dialog/SearchDialog.razor.cs @@ -59,6 +59,34 @@ public partial class SearchDialog [Parameter] public string? SearchIcon { get; set; } + /// + /// 获得/设置 是否使用 SearchForm 组件进行搜索条件编辑 默认 false 不使用 + /// Gets or sets Whether to use SearchForm component for editing search conditions. Default is false + /// + [Parameter] + public bool UseSearchForm { get; set; } + + /// + /// 获得/设置 搜索表单项集合 + /// Gets or sets Search Form Items collection + /// + [Parameter] + public List? SearchItems { get; set; } + + /// + /// 获得/设置 过滤器改变回调事件 Func 版本 + /// Gets or sets the filter changed callback event Func version + /// + [Parameter] + public Func? OnChanged { get; set; } + + /// + /// 获得/设置 搜索表单本地化配置项 + /// Gets or sets Search Form Localization Options + /// + [Parameter] + public SearchFormLocalizerOptions? SearchFormLocalizerOptions { get; set; } + [Inject] [NotNull] private IStringLocalizer>? Localizer { get; set; } @@ -68,8 +96,7 @@ public partial class SearchDialog private IIconTheme? IconTheme { get; set; } /// - /// OnParametersSet 方法 - /// OnParametersSet Method + /// /// protected override void OnParametersSet() { @@ -80,5 +107,42 @@ protected override void OnParametersSet() ClearIcon ??= IconTheme.GetIconByKey(ComponentIcons.SearchDialogClearIcon); SearchIcon ??= IconTheme.GetIconByKey(ComponentIcons.SearchDialogSearchIcon); + + if (UseSearchForm) + { + return; + } + + if (BodyTemplate != null) + { + return; + } + + Items ??= GetItemsByColumns(); } + + private async Task OnSearchFormFilterChanged(FilterKeyValueAction action) + { + // 通知父组件过滤器改变事件,此时并没有触发 OnSearchClick 搜索事件,父组件可以在 OnChanged 事件中获取当前过滤器状态并决定是否触发搜索事件 + if (OnChanged != null) + { + await OnChanged(action); + } + } + + private RenderFragment RenderButtons => builder => + { + builder.OpenComponent(0); + builder.AddAttribute(10, nameof(DialogCloseButton.Icon), ClearIcon); + builder.AddAttribute(20, nameof(DialogCloseButton.Text), ResetButtonText); + builder.AddAttribute(30, nameof(DialogCloseButton.OnClickWithoutRender), OnResetSearchClick); + builder.CloseComponent(); + + builder.OpenComponent(100); + builder.AddAttribute(101, nameof(DialogCloseButton.Color), Color.Primary); + builder.AddAttribute(110, nameof(DialogCloseButton.Icon), SearchIcon); + builder.AddAttribute(120, nameof(DialogCloseButton.Text), QueryButtonText); + builder.AddAttribute(130, nameof(DialogCloseButton.OnClickWithoutRender), OnSearchClick); + builder.CloseComponent(); + }; } diff --git a/src/BootstrapBlazor/Components/Dialog/SearchDialogOption.cs b/src/BootstrapBlazor/Components/Dialog/SearchDialogOption.cs index bbb2bc8f246..2f9b5b02a9e 100644 --- a/src/BootstrapBlazor/Components/Dialog/SearchDialogOption.cs +++ b/src/BootstrapBlazor/Components/Dialog/SearchDialogOption.cs @@ -92,4 +92,28 @@ public SearchDialogOption() /// Gets or sets Search Callback Delegate /// public Func? OnSearchClick { get; set; } + + /// + /// 获得/设置 是否使用 SearchForm 组件进行搜索条件编辑 默认 false 不使用 + /// Gets or sets Whether to use SearchForm component for editing search conditions. Default is false + /// + public bool UseSearchForm { get; set; } + + /// + /// 获得/设置 搜索表单项集合 + /// Gets or sets Search Form Items collection + /// + public List? SearchItems { get; set; } + + /// + /// 获得/设置 过滤器改变回调事件 Func 版本 + /// Gets or sets the filter changed callback event Func version + /// + public Func? OnFilterChanged { get; set; } + + /// + /// 获得/设置 搜索表单本地化配置项 + /// Gets or sets Search Form Localization Options + /// + public SearchFormLocalizerOptions? SearchFormLocalizerOptions { get; set; } } diff --git a/src/BootstrapBlazor/Components/SearchForm/ISearchItem.cs b/src/BootstrapBlazor/Components/SearchForm/ISearchItem.cs index b2e4b8389b1..831ea646ed2 100644 --- a/src/BootstrapBlazor/Components/SearchForm/ISearchItem.cs +++ b/src/BootstrapBlazor/Components/SearchForm/ISearchItem.cs @@ -68,7 +68,7 @@ public interface ISearchItem /// 获得/设置 搜索元数据 /// Gets or sets the search metadata /// - ISearchFormItemMetaData? MetaData { get; set; } + ISearchFormItemMetadata? Metadata { get; set; } /// /// 获得 过滤器实例 @@ -79,4 +79,10 @@ public interface ISearchItem /// Filter instance /// FilterKeyValueAction? GetFilter(); + + /// + /// 重置对象搜索值 + /// Resets the object search values + /// + void Reset(); } diff --git a/src/BootstrapBlazor/Components/SearchForm/SearchForm.razor.cs b/src/BootstrapBlazor/Components/SearchForm/SearchForm.razor.cs index da4413a6bee..8241074c84a 100644 --- a/src/BootstrapBlazor/Components/SearchForm/SearchForm.razor.cs +++ b/src/BootstrapBlazor/Components/SearchForm/SearchForm.razor.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone +using Microsoft.Extensions.Localization; + namespace BootstrapBlazor.Components; /// @@ -11,28 +13,12 @@ namespace BootstrapBlazor.Components; /// public partial class SearchForm : IShowLabel { - /// - /// 获得/设置 过滤器实例 - /// Gets or sets the filter instance - /// - [Parameter] - [EditorRequired] - [NotNull] - public FilterKeyValueAction? Filter { get; set; } - - /// - /// 获得/设置 过滤器改变回调事件 - /// Gets or sets the filter changed callback event - /// - [Parameter] - public EventCallback FilterChanged { get; set; } - /// /// 获得/设置 过滤器改变回调事件 Func 版本 /// Gets or sets the filter changed callback event Func version /// [Parameter] - public Func? OnFilterChanged { get; set; } + public Func? OnChanged { get; set; } /// /// 获得/设置 每行显示组件数量 默认为 null @@ -106,6 +92,17 @@ public partial class SearchForm : IShowLabel [Parameter] public EditorFormGroupType GroupType { get; set; } + /// + /// 获得/设置 搜索表单本地化配置项 + /// Gets or sets Search Form Localization Options + /// + [Parameter] + public SearchFormLocalizerOptions? SearchFormLocalizerOptions { get; set; } + + [Inject] + [NotNull] + private IStringLocalizer? SearchFormLocalizer { get; set; } + private string? ClassString => CssBuilder.Default("bb-editor bb-search-form") .AddClass("bb-editor-group-row-header", GroupType == EditorFormGroupType.RowHeader) .AddClassFromAttributes(AdditionalAttributes) @@ -138,48 +135,38 @@ protected override void OnParametersSet() Items ??= []; } - /// - /// 获得当前搜索表单过滤结果 - /// Gets the current search form filter result - /// - public FilterKeyValueAction GetFilterKeyValueAction() + private RenderFragment AutoGenerateTemplate(ISearchItem item) { - var action = new FilterKeyValueAction() - { - Filters = [] - }; - - foreach (var item in Items) + item.ShowLabel ??= ShowLabel ?? true; + item.ShowLabelTooltip ??= ShowLabelTooltip; + item.Metadata ??= item.BuildSearchMetadata(GetSearchOptions()); + item.Metadata.ValueChanged ??= async () => { - var filter = item.GetFilter(); - if (filter != null) + if (OnChanged != null) { - action.Filters.Add(filter); + var filter = Items.ToFilter(); + await OnChanged.Invoke(filter); } - } + }; - return action; + return item.CreateSearchItemComponentByMetadata(); } - private RenderFragment AutoGenerateTemplate(ISearchItem item) + private SearchFormLocalizerOptions? _options; + + private SearchFormLocalizerOptions GetSearchOptions() { - item.ShowLabel ??= ShowLabel; - item.ShowLabelTooltip ??= ShowLabelTooltip; - item.MetaData?.ValueChanged ??= async () => + _options ??= SearchFormLocalizerOptions ?? new SearchFormLocalizerOptions() { - var action = GetFilterKeyValueAction(); - - if (FilterChanged.HasDelegate) - { - await FilterChanged.InvokeAsync(action); - } - if (OnFilterChanged != null) - { - await OnFilterChanged.Invoke(action); - } + SelectAllText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.SelectAllText)], + BooleanAllText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.BooleanAllText)], + BooleanTrueText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.BooleanTrueText)], + BooleanFalseText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.BooleanFalseText)], + NumberStartValueLabelText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.NumberStartValueLabelText)], + NumberEndValueLabelText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.NumberEndValueLabelText)] }; - return item.CreateRenderFragment(); + return _options.Value; } private string? GetCssString(ISearchItem item) diff --git a/src/BootstrapBlazor/Components/SearchForm/SearchItem.cs b/src/BootstrapBlazor/Components/SearchForm/SearchItem.cs index 6cc8596a880..5a33c25c5b2 100644 --- a/src/BootstrapBlazor/Components/SearchForm/SearchItem.cs +++ b/src/BootstrapBlazor/Components/SearchForm/SearchItem.cs @@ -6,8 +6,8 @@ namespace BootstrapBlazor.Components; /// -/// EditorItem 搜索表单渲染项实体类 -/// EditorItem form field class +/// SearchItem 搜索表单渲染项实体类 +/// SearchItem form field class /// /// /// @@ -60,13 +60,17 @@ public class SearchItem(string fieldName, Type fieldType, string? fieldText = nu public int Cols { get; set; } /// - /// + /// /// - public ISearchFormItemMetaData? MetaData { get; set; } + public ISearchFormItemMetadata? Metadata { get; set; } /// /// /// - /// - public FilterKeyValueAction? GetFilter() => MetaData?.GetFilter(FieldName); + public FilterKeyValueAction? GetFilter() => Metadata?.GetFilter(FieldName); + + /// + /// + /// + public void Reset() => Metadata?.Reset(); } diff --git a/src/BootstrapBlazor/Components/Searches/CheckboxListSearchMetaData.cs b/src/BootstrapBlazor/Components/Searches/CheckboxListSearchMetadata.cs similarity index 87% rename from src/BootstrapBlazor/Components/Searches/CheckboxListSearchMetaData.cs rename to src/BootstrapBlazor/Components/Searches/CheckboxListSearchMetadata.cs index 36ca1990cd1..a22bbe78031 100644 --- a/src/BootstrapBlazor/Components/Searches/CheckboxListSearchMetaData.cs +++ b/src/BootstrapBlazor/Components/Searches/CheckboxListSearchMetadata.cs @@ -9,7 +9,7 @@ namespace BootstrapBlazor.Components; /// CheckboxList 搜索类型元数据类 /// CheckboxList search meta data class /// -public class CheckboxListSearchMetaData : MultipleSelectSearchMetaData +public class CheckboxListSearchMetadata : MultipleSelectSearchMetadata { } diff --git a/src/BootstrapBlazor/Components/Searches/DateTimeRangeSearchMetaData.cs b/src/BootstrapBlazor/Components/Searches/DateTimeRangeSearchMetadata.cs similarity index 92% rename from src/BootstrapBlazor/Components/Searches/DateTimeRangeSearchMetaData.cs rename to src/BootstrapBlazor/Components/Searches/DateTimeRangeSearchMetadata.cs index 071fdfda0b2..3d52e3f8493 100644 --- a/src/BootstrapBlazor/Components/Searches/DateTimeRangeSearchMetaData.cs +++ b/src/BootstrapBlazor/Components/Searches/DateTimeRangeSearchMetadata.cs @@ -9,7 +9,7 @@ namespace BootstrapBlazor.Components; /// 时间区间搜索元数据类 /// DateTime range search meta data class /// -public class DateTimeRangeSearchMetaData : SearchMetaDataBase +public class DateTimeRangeSearchMetadata : SearchMetadataBase { /// /// 获得/设置 搜索值 @@ -72,4 +72,12 @@ public async Task ValueChangedHandler(DateTimeRangeValue? value) await ValueChanged(); } } + + /// + /// + /// + public override void Reset() + { + Value = null; + } } diff --git a/src/BootstrapBlazor/Components/Searches/DateTimeSearchMetaData.cs b/src/BootstrapBlazor/Components/Searches/DateTimeSearchMetadata.cs similarity index 90% rename from src/BootstrapBlazor/Components/Searches/DateTimeSearchMetaData.cs rename to src/BootstrapBlazor/Components/Searches/DateTimeSearchMetadata.cs index 0c85edfd2b2..5e526678f3a 100644 --- a/src/BootstrapBlazor/Components/Searches/DateTimeSearchMetaData.cs +++ b/src/BootstrapBlazor/Components/Searches/DateTimeSearchMetadata.cs @@ -9,7 +9,7 @@ namespace BootstrapBlazor.Components; /// 时间搜索元数据类 /// DateTime search meta data class /// -public class DateTimeSearchMetaData : SearchMetaDataBase +public class DateTimeSearchMetadata : SearchMetadataBase { /// /// 获得/设置 搜索值 @@ -56,4 +56,12 @@ public async Task ValueChangedHandler(DateTime? value) await ValueChanged(); } } + + /// + /// + /// + public override void Reset() + { + Value = null; + } } diff --git a/src/BootstrapBlazor/Components/Searches/ISearchFormItemMetaData.cs b/src/BootstrapBlazor/Components/Searches/ISearchFormItemMetadata.cs similarity index 92% rename from src/BootstrapBlazor/Components/Searches/ISearchFormItemMetaData.cs rename to src/BootstrapBlazor/Components/Searches/ISearchFormItemMetadata.cs index aaffbc6037f..03557144c1c 100644 --- a/src/BootstrapBlazor/Components/Searches/ISearchFormItemMetaData.cs +++ b/src/BootstrapBlazor/Components/Searches/ISearchFormItemMetadata.cs @@ -11,7 +11,7 @@ namespace BootstrapBlazor.Components; /// 搜索元数据接口 /// Search metadata interface /// -public interface ISearchFormItemMetaData +public interface ISearchFormItemMetadata { /// /// 获得/设置 搜索值变化回调 @@ -52,4 +52,10 @@ public interface ISearchFormItemMetaData /// Gets or sets the callback to get the filter instance, called by the method. Setting this callback allows customizing the logic for obtaining the filter instance. /// Func? GetFilterCallback { get; set; } + + /// + /// 重置方法 + /// Reset method + /// + void Reset(); } diff --git a/src/BootstrapBlazor/Components/Searches/MultipleSelectSearchMetaData.cs b/src/BootstrapBlazor/Components/Searches/MultipleSelectSearchMetadata.cs similarity index 93% rename from src/BootstrapBlazor/Components/Searches/MultipleSelectSearchMetaData.cs rename to src/BootstrapBlazor/Components/Searches/MultipleSelectSearchMetadata.cs index 85bbb7e9639..499406fb7ef 100644 --- a/src/BootstrapBlazor/Components/Searches/MultipleSelectSearchMetaData.cs +++ b/src/BootstrapBlazor/Components/Searches/MultipleSelectSearchMetadata.cs @@ -9,10 +9,10 @@ namespace BootstrapBlazor.Components; /// 多选类型搜索元数据类 /// Multiple select type search metadata class /// -public class MultipleSelectSearchMetaData : SelectSearchMetaData +public class MultipleSelectSearchMetadata : SelectSearchMetadata { /// - /// + /// /// public override FilterKeyValueAction? GetFilter(string fieldName) { diff --git a/src/BootstrapBlazor/Components/Searches/MultipleStringSearchMetaData.cs b/src/BootstrapBlazor/Components/Searches/MultipleStringSearchMetadata.cs similarity index 92% rename from src/BootstrapBlazor/Components/Searches/MultipleStringSearchMetaData.cs rename to src/BootstrapBlazor/Components/Searches/MultipleStringSearchMetadata.cs index 2e0eb2d0572..8a41f158b61 100644 --- a/src/BootstrapBlazor/Components/Searches/MultipleStringSearchMetaData.cs +++ b/src/BootstrapBlazor/Components/Searches/MultipleStringSearchMetadata.cs @@ -9,10 +9,10 @@ namespace BootstrapBlazor.Components; /// 多个字符串搜索元数据类 /// Multiple string search meta data class /// -public class MultipleStringSearchMetaData : StringSearchMetaData +public class MultipleStringSearchMetadata : StringSearchMetadata { /// - /// + /// /// public override FilterKeyValueAction? GetFilter(string fieldName) { diff --git a/src/BootstrapBlazor/Components/Searches/NumberSearchMetaData.cs b/src/BootstrapBlazor/Components/Searches/NumberSearchMetadata.cs similarity index 95% rename from src/BootstrapBlazor/Components/Searches/NumberSearchMetaData.cs rename to src/BootstrapBlazor/Components/Searches/NumberSearchMetadata.cs index ed8de2a9cd9..c0c2bc5029f 100644 --- a/src/BootstrapBlazor/Components/Searches/NumberSearchMetaData.cs +++ b/src/BootstrapBlazor/Components/Searches/NumberSearchMetadata.cs @@ -9,7 +9,7 @@ namespace BootstrapBlazor.Components; /// 数字类型搜索元数据类 /// Number type search metadata class /// -public class NumberSearchMetaData : SearchMetaDataBase +public class NumberSearchMetadata : SearchMetadataBase { /// /// 获得/设置 搜索开始值 @@ -129,4 +129,13 @@ public async Task EndValueChangedHandler(string? value) await ValueChanged(); } } + + /// + /// + /// + public override void Reset() + { + StartValue = null; + EndValue = null; + } } diff --git a/src/BootstrapBlazor/Components/Searches/SearchMetaDataBase.cs b/src/BootstrapBlazor/Components/Searches/SearchMetadataBase.cs similarity index 80% rename from src/BootstrapBlazor/Components/Searches/SearchMetaDataBase.cs rename to src/BootstrapBlazor/Components/Searches/SearchMetadataBase.cs index 7a54bb3dea2..be00a6a3949 100644 --- a/src/BootstrapBlazor/Components/Searches/SearchMetaDataBase.cs +++ b/src/BootstrapBlazor/Components/Searches/SearchMetadataBase.cs @@ -11,7 +11,7 @@ namespace BootstrapBlazor.Components; /// 搜索元数据基类 /// Search meta data base class /// -public abstract class SearchMetaDataBase : ISearchFormItemMetaData +public abstract class SearchMetadataBase : ISearchFormItemMetadata { /// /// 获得/设置 占位符文本 @@ -20,29 +20,29 @@ public abstract class SearchMetaDataBase : ISearchFormItemMetaData public string? PlaceHolder { get; set; } /// - /// + /// /// public Func? ValueChanged { get; set; } /// - /// + /// /// [JsonConverter(typeof(JsonStringEnumConverter))] public FilterLogic FilterLogic { get; set; } /// - /// + /// /// [JsonConverter(typeof(JsonStringEnumConverter))] public FilterAction FilterAction { get; set; } /// - /// + /// /// public Func? GetFilterCallback { get; set; } /// - /// + /// /// public abstract FilterKeyValueAction? GetFilter(string fieldName); @@ -70,4 +70,9 @@ public abstract class SearchMetaDataBase : ISearchFormItemMetaData } return null; } + + /// + /// + /// + public abstract void Reset(); } diff --git a/src/BootstrapBlazor/Components/Searches/SelectSearchMetaData.cs b/src/BootstrapBlazor/Components/Searches/SelectSearchMetadata.cs similarity index 92% rename from src/BootstrapBlazor/Components/Searches/SelectSearchMetaData.cs rename to src/BootstrapBlazor/Components/Searches/SelectSearchMetadata.cs index 63c6dd2ccf8..b21c6164046 100644 --- a/src/BootstrapBlazor/Components/Searches/SelectSearchMetaData.cs +++ b/src/BootstrapBlazor/Components/Searches/SelectSearchMetadata.cs @@ -9,7 +9,7 @@ namespace BootstrapBlazor.Components; /// 选择类型搜索元数据类 /// Select type search metadata class /// -public class SelectSearchMetaData : StringSearchMetaData +public class SelectSearchMetadata : StringSearchMetadata { /// /// 获得/设置 选择项集合 diff --git a/src/BootstrapBlazor/Components/Searches/StringSearchMetaData.cs b/src/BootstrapBlazor/Components/Searches/StringSearchMetadata.cs similarity index 87% rename from src/BootstrapBlazor/Components/Searches/StringSearchMetaData.cs rename to src/BootstrapBlazor/Components/Searches/StringSearchMetadata.cs index 2c2c7900393..3ae4cd720aa 100644 --- a/src/BootstrapBlazor/Components/Searches/StringSearchMetaData.cs +++ b/src/BootstrapBlazor/Components/Searches/StringSearchMetadata.cs @@ -9,7 +9,7 @@ namespace BootstrapBlazor.Components; /// 字符串搜索元数据类 /// String search meta data class /// -public class StringSearchMetaData : SearchMetaDataBase +public class StringSearchMetadata : SearchMetadataBase { /// /// 获得/设置 搜索值 @@ -18,7 +18,7 @@ public class StringSearchMetaData : SearchMetaDataBase public string? Value { get; set; } /// - /// + /// /// public override FilterKeyValueAction? GetFilter(string fieldName) { @@ -56,4 +56,12 @@ public async Task ValueChangedHandler(string? value) await ValueChanged(); } } + + /// + /// + /// + public override void Reset() + { + Value = null; + } } diff --git a/src/BootstrapBlazor/Components/Table/ITableColumn.cs b/src/BootstrapBlazor/Components/Table/ITableColumn.cs index e3e2da6ba9b..60e109953a8 100644 --- a/src/BootstrapBlazor/Components/Table/ITableColumn.cs +++ b/src/BootstrapBlazor/Components/Table/ITableColumn.cs @@ -51,7 +51,7 @@ public interface ITableColumn : IEditorItem /// 获得/设置 搜索元数据 /// Gets or sets the search metadata /// - ISearchFormItemMetaData? SearchFormItemMetaData { get; set; } + ISearchFormItemMetadata? SearchFormItemMetadata { get; set; } /// /// 获得/设置 列宽 diff --git a/src/BootstrapBlazor/Components/Table/InternalTableColumn.cs b/src/BootstrapBlazor/Components/Table/InternalTableColumn.cs index 5a0ac686ae1..d36ff2dc3b9 100644 --- a/src/BootstrapBlazor/Components/Table/InternalTableColumn.cs +++ b/src/BootstrapBlazor/Components/Table/InternalTableColumn.cs @@ -35,9 +35,9 @@ class InternalTableColumn(string fieldName, Type fieldType, string? fieldText = public bool? Searchable { get; set; } /// - /// + /// /// - public ISearchFormItemMetaData? SearchFormItemMetaData { get; set; } + public ISearchFormItemMetadata? SearchFormItemMetadata { get; set; } /// /// diff --git a/src/BootstrapBlazor/Components/Table/Table.razor b/src/BootstrapBlazor/Components/Table/Table.razor index afec765852c..d36ae847447 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor +++ b/src/BootstrapBlazor/Components/Table/Table.razor @@ -1101,7 +1101,7 @@ @if (UseSearchForm) { + OnChanged="OnSearchFormFilterChanged"> } else if (CustomerSearchModel != null && CustomerSearchTemplate != null) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.Search.cs b/src/BootstrapBlazor/Components/Table/Table.razor.Search.cs index 0269069f4f4..7dcfd1ee53d 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.Search.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.Search.cs @@ -115,8 +115,8 @@ public partial class Table public SearchMode SearchMode { get; set; } /// - /// 获得/设置 是否使用搜索表单 默认为 false - /// Gets or sets Whether to use search form. Default false + /// 获得/设置 是否使用搜索表单 默认为 false 开启本功能后 CustomerSearchTemplate 与 SearchTemplate 均不生效 + /// Gets or sets Whether to use search form. Default false. When enabled, both CustomerSearchTemplate and SearchTemplate are disabled /// [Parameter] public bool UseSearchForm { get; set; } @@ -164,44 +164,38 @@ public partial class Table public Func? OnResetSearchAsync { get; set; } private FilterKeyValueAction? _searchFilter; - private IEnumerable? _searchItems; + private List? _searchItems; + private FilterKeyValueAction? _advanceSearchFilter; - private IEnumerable SearchFormItems + private List SearchFormItems { get { - if (SearchFormLocalizerOptions is null) - { - SearchFormLocalizerOptions = new SearchFormLocalizerOptions() - { - SelectAllText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.SelectAllText)], - BooleanAllText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.BooleanAllText)], - BooleanTrueText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.BooleanTrueText)], - BooleanFalseText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.BooleanFalseText)], - NumberStartValueLabelText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.NumberStartValueLabelText)], - NumberEndValueLabelText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.NumberEndValueLabelText)] - }; - } - _searchItems ??= GetSearchItems(SearchFormLocalizerOptions.Value); + _searchItems ??= GetSearchFormItems(); return _searchItems; } } - private IEnumerable GetSearchItems(SearchFormLocalizerOptions options) + private List GetSearchFormItems() { if (SearchItems != null) { - // TODO: 增加内部创建默认 ISearchItemMetaData 逻辑,减少用户使用成本 - //foreach (var item in SearchItems) - //{ - // // 创建默认 ISearchItemMetaData - // item.MetaData ??= column.BuildSearchMetaData(options); - //} - - return SearchItems; + return SearchItems.ToList(); } - return GetSearchColumns().Select(i => i.ParseSearchItem(options)).ToList(); + if (SearchFormLocalizerOptions is null) + { + SearchFormLocalizerOptions = new SearchFormLocalizerOptions() + { + SelectAllText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.SelectAllText)], + BooleanAllText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.BooleanAllText)], + BooleanTrueText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.BooleanTrueText)], + BooleanFalseText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.BooleanFalseText)], + NumberStartValueLabelText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.NumberStartValueLabelText)], + NumberEndValueLabelText = SearchFormLocalizer[nameof(Components.SearchFormLocalizerOptions.NumberEndValueLabelText)] + }; + } + return GetSearchColumns().Select(i => i.ParseSearchItem(SearchFormLocalizerOptions.Value)).ToList(); } private Task OnSearchFormFilterChanged(FilterKeyValueAction action) @@ -217,7 +211,22 @@ private Task OnSearchFormFilterChanged(FilterKeyValueAction action) protected async Task ResetSearchClick() { await ToggleLoading(true); - if (CustomerSearchModel != null) + if (UseSearchForm) + { + _searchFilter = null; + _advanceSearchFilter = null; + + // 重置 SearchItems 中的搜索条件值 + if (_searchItems != null) + { + foreach (var item in _searchItems) + { + item.Reset(); + } + } + _searchItems = null; + } + else if (CustomerSearchModel != null) { CustomerSearchModel.Reset(); } @@ -230,7 +239,6 @@ protected async Task ResetSearchClick() Utility.Reset(SearchModel, CreateSearchModel()); } - _searchItems = null; await SearchClick(); await ToggleLoading(false); } @@ -283,7 +291,11 @@ protected async Task SearchClick() /// protected async Task ShowSearchDialog() { - if (CustomerSearchModel != null && CustomerSearchTemplate != null) + if (UseSearchForm) + { + await DialogService.ShowSearchDialog(CreateSearchFormDialog()); + } + else if (CustomerSearchModel != null && CustomerSearchTemplate != null) { await DialogService.ShowSearchDialog(CreateCustomerModelDialog()); } @@ -292,39 +304,65 @@ protected async Task ShowSearchDialog() await DialogService.ShowSearchDialog(CreateModelDialog()); } - SearchDialogOption CreateModelDialog() => new() + SearchDialogOption CreateCustomerModelDialog() => new() { - Class = "modal-dialog-table", IsScrolling = ScrollingDialogContent, Title = SearchModalTitle, - Model = SearchModel, - DialogBodyTemplate = SearchTemplate, + Model = CustomerSearchModel, + DialogBodyTemplate = CustomerSearchTemplate, OnResetSearchClick = ResetSearchClick, OnSearchClick = SearchClick, RowType = SearchDialogRowType, ItemsPerRow = SearchDialogItemsPerRow, - LabelAlign = SearchDialogLabelAlign, Size = SearchDialogSize, - Items = Columns.Where(i => i.GetSearchable()), + LabelAlign = SearchDialogLabelAlign, IsDraggable = SearchDialogIsDraggable, ShowMaximizeButton = SearchDialogShowMaximizeButton, ShowUnsetGroupItemsOnTop = ShowUnsetGroupItemsOnTop }; - SearchDialogOption CreateCustomerModelDialog() => new() + SearchDialogOption CreateSearchFormDialog() => new() { + Class = "modal-dialog-table modal-dialog-search-form", IsScrolling = ScrollingDialogContent, Title = SearchModalTitle, - Model = CustomerSearchModel, - DialogBodyTemplate = CustomerSearchTemplate, + DialogBodyTemplate = SearchTemplate, OnResetSearchClick = ResetSearchClick, OnSearchClick = SearchClick, RowType = SearchDialogRowType, ItemsPerRow = SearchDialogItemsPerRow, + LabelAlign = SearchDialogLabelAlign, Size = SearchDialogSize, + IsDraggable = SearchDialogIsDraggable, + ShowMaximizeButton = SearchDialogShowMaximizeButton, + ShowUnsetGroupItemsOnTop = ShowUnsetGroupItemsOnTop, + SearchFormLocalizerOptions = SearchFormLocalizerOptions, + UseSearchForm = true, + SearchItems = SearchFormItems, + OnFilterChanged = action => + { + _advanceSearchFilter = action; + return Task.CompletedTask; + } + }; + + SearchDialogOption CreateModelDialog() => new() + { + Class = "modal-dialog-table", + IsScrolling = ScrollingDialogContent, + Title = SearchModalTitle, + Model = SearchModel, + DialogBodyTemplate = SearchTemplate, + OnResetSearchClick = ResetSearchClick, + OnSearchClick = SearchClick, + RowType = SearchDialogRowType, + ItemsPerRow = SearchDialogItemsPerRow, LabelAlign = SearchDialogLabelAlign, + Size = SearchDialogSize, + Items = Columns.Where(i => i.GetSearchable()), IsDraggable = SearchDialogIsDraggable, - ShowMaximizeButton = SearchDialogShowMaximizeButton + ShowMaximizeButton = SearchDialogShowMaximizeButton, + ShowUnsetGroupItemsOnTop = ShowUnsetGroupItemsOnTop }; } @@ -349,6 +387,11 @@ protected IEnumerable GetCustomerSearches() /// protected List GetAdvanceSearches() { + if (UseSearchForm) + { + return _advanceSearchFilter.ToSearches(); + } + var searches = new List(); if (ShowAdvancedSearch && SearchMode == SearchMode.Popup && CustomerSearchModel == null) { diff --git a/src/BootstrapBlazor/Components/Table/TableColumn.cs b/src/BootstrapBlazor/Components/Table/TableColumn.cs index f7a67a40ae3..24c9ad56a52 100644 --- a/src/BootstrapBlazor/Components/Table/TableColumn.cs +++ b/src/BootstrapBlazor/Components/Table/TableColumn.cs @@ -154,7 +154,7 @@ public class TableColumn : BootstrapComponentBase, ITableColumn /// /// [Parameter] - public ISearchFormItemMetaData? SearchFormItemMetaData { get; set; } + public ISearchFormItemMetadata? SearchFormItemMetadata { get; set; } /// /// diff --git a/src/BootstrapBlazor/Extensions/DialogServiceExtensions.cs b/src/BootstrapBlazor/Extensions/DialogServiceExtensions.cs index 82791a4a4e7..78ec2d5eed7 100644 --- a/src/BootstrapBlazor/Extensions/DialogServiceExtensions.cs +++ b/src/BootstrapBlazor/Extensions/DialogServiceExtensions.cs @@ -46,7 +46,7 @@ public static async Task ShowSearchDialog(this DialogService service, Se { [nameof(SearchDialog.ShowUnsetGroupItemsOnTop)] = option.ShowUnsetGroupItemsOnTop, [nameof(SearchDialog.ShowLabel)] = option.ShowLabel, - [nameof(SearchDialog.Items)] = option.Items ?? Utility.GenerateColumns(item => item.GetSearchable()), + [nameof(SearchDialog.Items)] = option.Items, [nameof(SearchDialog.OnResetSearchClick)] = new Func(async () => { if (option.OnResetSearchClick != null) @@ -67,7 +67,11 @@ public static async Task ShowSearchDialog(this DialogService service, Se [nameof(SearchDialog.ResetButtonText)] = option.ResetButtonText, [nameof(SearchDialog.QueryButtonText)] = option.QueryButtonText, [nameof(SearchDialog.Model)] = option.Model, - [nameof(SearchDialog.BodyTemplate)] = option.DialogBodyTemplate + [nameof(SearchDialog.BodyTemplate)] = option.DialogBodyTemplate, + [nameof(SearchDialog.UseSearchForm)] = option.UseSearchForm, + [nameof(SearchDialog.SearchItems)] = option.SearchItems, + [nameof(SearchDialog.OnChanged)] = option.OnFilterChanged, + [nameof(SearchDialog.SearchFormLocalizerOptions)] = option.SearchFormLocalizerOptions }; option.Component = BootstrapDynamicComponent.CreateComponent>(parameters); await service.Show(option, dialog); diff --git a/src/BootstrapBlazor/Extensions/ISearchItemExtensions.cs b/src/BootstrapBlazor/Extensions/ISearchItemExtensions.cs index 0af92952b2b..196bb72e892 100644 --- a/src/BootstrapBlazor/Extensions/ISearchItemExtensions.cs +++ b/src/BootstrapBlazor/Extensions/ISearchItemExtensions.cs @@ -13,56 +13,107 @@ namespace BootstrapBlazor.Components; /// public static class ISearchItemExtensions { - /// - /// 创建 搜索项的 RenderFragment 实例 - /// Creates a RenderFragment instance for the search item - /// - /// - public static RenderFragment CreateRenderFragment(this ISearchItem item) => builder => + extension(ISearchItem item) { - var metaData = item.MetaData; - switch (metaData) + /// + /// 通过 数据类型推断 实例 + /// + /// + /// + public ISearchFormItemMetadata BuildSearchMetadata(SearchFormLocalizerOptions options) { - case NumberSearchMetaData numberSearchMetaData: - builder.AddNumberSearchComponent(item, numberSearchMetaData); - break; - case DateTimeSearchMetaData datetimeSearchMetaData: - builder.AddDateTimeSearchComponent(item, datetimeSearchMetaData); - break; - case DateTimeRangeSearchMetaData datetimeRangeSearchMetaData: - builder.AddDateTimeRangeSearchComponent(item, datetimeRangeSearchMetaData); - break; - case CheckboxListSearchMetaData checkboxListSearchMetaData: - builder.AddCheckboxListSearchComponent(item, checkboxListSearchMetaData); - break; - case MultipleSelectSearchMetaData multipleSelectSearchMetaData: - builder.AddMultipleSelectSearchComponent(item, multipleSelectSearchMetaData); - break; - case SelectSearchMetaData selectSearchMetaData: - builder.AddSelectSearchComponent(item, selectSearchMetaData); - break; - case StringSearchMetaData stringSearchMetaData: - builder.AddStringSearchComponent(item, stringSearchMetaData); - break; + var fieldType = Nullable.GetUnderlyingType(item.PropertyType) ?? item.PropertyType; + ISearchFormItemMetadata? metaData = null; + if (fieldType.IsEnum) + { + metaData = new SelectSearchMetadata() + { + Items = fieldType.ToSelectList(new SelectedItem() { Value = "", Text = options.SelectAllText }), + }; + } + else if (fieldType.IsNumberWithDotSeparator()) + { + metaData = new NumberSearchMetadata() + { + StartValueLabelText = options.NumberStartValueLabelText, + EndValueLabelText = options.NumberEndValueLabelText, + ValueType = fieldType + }; + } + else if (fieldType.IsBoolean()) + { + metaData = new SelectSearchMetadata() + { + Items = new List() + { + new SelectedItem() { Value = "", Text = options.BooleanAllText }, + new SelectedItem() { Value = "True", Text = options.BooleanTrueText }, + new SelectedItem() { Value = "False", Text = options.BooleanFalseText } + } + }; + } + else if (fieldType.IsDateTime()) + { + metaData = new DateTimeRangeSearchMetadata(); + } + else + { + metaData = new StringSearchMetadata() { FilterAction = FilterAction.Contains }; + } + + return metaData; } - }; + + /// + /// 创建 搜索项的 RenderFragment 实例 + /// Creates a RenderFragment instance for the search item + /// + public RenderFragment CreateSearchItemComponentByMetadata() => builder => + { + var metaData = item.Metadata; + switch (metaData) + { + case NumberSearchMetadata numberSearchMetadata: + builder.AddNumberSearchComponent(item, numberSearchMetadata); + break; + case DateTimeSearchMetadata datetimeSearchMetadata: + builder.AddDateTimeSearchComponent(item, datetimeSearchMetadata); + break; + case DateTimeRangeSearchMetadata datetimeRangeSearchMetadata: + builder.AddDateTimeRangeSearchComponent(item, datetimeRangeSearchMetadata); + break; + case CheckboxListSearchMetadata checkboxListSearchMetadata: + builder.AddCheckboxListSearchComponent(item, checkboxListSearchMetadata); + break; + case MultipleSelectSearchMetadata multipleSelectSearchMetadata: + builder.AddMultipleSelectSearchComponent(item, multipleSelectSearchMetadata); + break; + case SelectSearchMetadata selectSearchMetadata: + builder.AddSelectSearchComponent(item, selectSearchMetadata); + break; + case StringSearchMetadata stringSearchMetadata: + builder.AddStringSearchComponent(item, stringSearchMetadata); + break; + } + }; + } extension(RenderTreeBuilder builder) { - private void AddStringSearchComponent(ISearchItem item, StringSearchMetaData stringSearchMetaData) + private void AddStringSearchComponent(ISearchItem item, StringSearchMetadata stringSearchMetadata) { builder.OpenComponent>(0); - builder.AddAttribute(10, nameof(BootstrapInput<>.Value), stringSearchMetaData.Value); - builder.AddAttribute(20, nameof(BootstrapInput<>.OnValueChanged), stringSearchMetaData.ValueChangedHandler); + builder.AddAttribute(10, nameof(BootstrapInput<>.Value), stringSearchMetadata.Value); + builder.AddAttribute(20, nameof(BootstrapInput<>.OnValueChanged), stringSearchMetadata.ValueChangedHandler); builder.AddAttribute(30, nameof(BootstrapInput<>.ShowLabel), item.ShowLabel ?? true); builder.AddAttribute(40, nameof(BootstrapInput<>.ShowLabelTooltip), item.ShowLabelTooltip); builder.AddAttribute(50, nameof(BootstrapInput<>.DisplayText), item.Text); builder.AddAttribute(60, nameof(BootstrapInput<>.SkipValidate), true); - builder.AddAttribute(70, nameof(BootstrapInput<>.PlaceHolder), stringSearchMetaData.PlaceHolder); + builder.AddAttribute(70, nameof(BootstrapInput<>.PlaceHolder), stringSearchMetadata.PlaceHolder); builder.CloseComponent(); } - private void AddNumberSearchComponent(ISearchItem item, NumberSearchMetaData numberSearchMetaData) + private void AddNumberSearchComponent(ISearchItem item, NumberSearchMetadata numberSearchMetadata) { if (item.ShowLabel ?? true) { @@ -76,74 +127,74 @@ private void AddNumberSearchComponent(ISearchItem item, NumberSearchMetaData num builder.AddAttribute(1, nameof(BootstrapInputGroup.ChildContent), new RenderFragment(builder => { builder.OpenComponent(10); - builder.AddAttribute(11, nameof(BootstrapInputGroupLabel.DisplayText), numberSearchMetaData.StartValueLabelText); + builder.AddAttribute(11, nameof(BootstrapInputGroupLabel.DisplayText), numberSearchMetadata.StartValueLabelText); builder.CloseComponent(); builder.OpenComponent>(20); - builder.AddAttribute(21, nameof(BootstrapInput<>.Value), numberSearchMetaData.StartValue); - builder.AddAttribute(22, nameof(BootstrapInput<>.OnValueChanged), numberSearchMetaData.StartValueChangedHandler); + builder.AddAttribute(21, nameof(BootstrapInput<>.Value), numberSearchMetadata.StartValue); + builder.AddAttribute(22, nameof(BootstrapInput<>.OnValueChanged), numberSearchMetadata.StartValueChangedHandler); builder.AddAttribute(23, nameof(BootstrapInput<>.SkipValidate), true); builder.CloseComponent(); builder.OpenComponent(30); - builder.AddAttribute(31, nameof(BootstrapInputGroupLabel.DisplayText), numberSearchMetaData.EndValueLabelText); + builder.AddAttribute(31, nameof(BootstrapInputGroupLabel.DisplayText), numberSearchMetadata.EndValueLabelText); builder.CloseComponent(); builder.OpenComponent>(40); - builder.AddAttribute(41, nameof(BootstrapInput<>.Value), numberSearchMetaData.EndValue); - builder.AddAttribute(42, nameof(BootstrapInput<>.OnValueChanged), numberSearchMetaData.EndValueChangedHandler); + builder.AddAttribute(41, nameof(BootstrapInput<>.Value), numberSearchMetadata.EndValue); + builder.AddAttribute(42, nameof(BootstrapInput<>.OnValueChanged), numberSearchMetadata.EndValueChangedHandler); builder.AddAttribute(43, nameof(BootstrapInput<>.SkipValidate), true); builder.CloseComponent(); })); builder.CloseComponent(); } - private void AddSelectSearchComponent(ISearchItem item, SelectSearchMetaData selectSearchMetaData) + private void AddSelectSearchComponent(ISearchItem item, SelectSearchMetadata selectSearchMetadata) { builder.OpenComponent>(0); - builder.AddAttribute(10, nameof(Select<>.Value), selectSearchMetaData.Value); - builder.AddAttribute(20, nameof(Select<>.OnValueChanged), selectSearchMetaData.ValueChangedHandler); + builder.AddAttribute(10, nameof(Select<>.Value), selectSearchMetadata.Value); + builder.AddAttribute(20, nameof(Select<>.OnValueChanged), selectSearchMetadata.ValueChangedHandler); builder.AddAttribute(30, nameof(Select<>.ShowLabel), item.ShowLabel ?? true); builder.AddAttribute(40, nameof(Select<>.ShowLabelTooltip), item.ShowLabelTooltip); builder.AddAttribute(50, nameof(Select<>.DisplayText), item.Text); - builder.AddAttribute(60, nameof(Select<>.Items), selectSearchMetaData.Items); - builder.AddAttribute(70, nameof(Select<>.PlaceHolder), selectSearchMetaData.PlaceHolder); + builder.AddAttribute(60, nameof(Select<>.Items), selectSearchMetadata.Items); + builder.AddAttribute(70, nameof(Select<>.PlaceHolder), selectSearchMetadata.PlaceHolder); builder.AddAttribute(80, nameof(Select<>.SkipValidate), true); builder.CloseComponent(); } - private void AddMultipleSelectSearchComponent(ISearchItem item, MultipleSelectSearchMetaData multipleSelectSearchMetaData) + private void AddMultipleSelectSearchComponent(ISearchItem item, MultipleSelectSearchMetadata multipleSelectSearchMetadata) { builder.OpenComponent>(0); - builder.AddAttribute(10, nameof(MultiSelect<>.Value), multipleSelectSearchMetaData.Value); - builder.AddAttribute(20, nameof(MultiSelect<>.OnValueChanged), multipleSelectSearchMetaData.ValueChangedHandler); + builder.AddAttribute(10, nameof(MultiSelect<>.Value), multipleSelectSearchMetadata.Value); + builder.AddAttribute(20, nameof(MultiSelect<>.OnValueChanged), multipleSelectSearchMetadata.ValueChangedHandler); builder.AddAttribute(30, nameof(MultiSelect<>.ShowLabel), item.ShowLabel ?? true); builder.AddAttribute(40, nameof(MultiSelect<>.ShowLabelTooltip), item.ShowLabelTooltip); builder.AddAttribute(50, nameof(MultiSelect<>.DisplayText), item.Text); - builder.AddAttribute(60, nameof(MultiSelect<>.Items), multipleSelectSearchMetaData.Items); - builder.AddAttribute(70, nameof(MultiSelect<>.PlaceHolder), multipleSelectSearchMetaData.PlaceHolder); + builder.AddAttribute(60, nameof(MultiSelect<>.Items), multipleSelectSearchMetadata.Items); + builder.AddAttribute(70, nameof(MultiSelect<>.PlaceHolder), multipleSelectSearchMetadata.PlaceHolder); builder.AddAttribute(80, nameof(MultiSelect<>.SkipValidate), true); builder.CloseComponent(); } - private void AddCheckboxListSearchComponent(ISearchItem item, CheckboxListSearchMetaData checkboxListSearchMetaData) + private void AddCheckboxListSearchComponent(ISearchItem item, CheckboxListSearchMetadata checkboxListSearchMetadata) { builder.OpenComponent>(0); - builder.AddAttribute(10, nameof(CheckboxList<>.Value), checkboxListSearchMetaData.Value); - builder.AddAttribute(20, nameof(CheckboxList<>.OnValueChanged), checkboxListSearchMetaData.ValueChangedHandler); + builder.AddAttribute(10, nameof(CheckboxList<>.Value), checkboxListSearchMetadata.Value); + builder.AddAttribute(20, nameof(CheckboxList<>.OnValueChanged), checkboxListSearchMetadata.ValueChangedHandler); builder.AddAttribute(30, nameof(CheckboxList<>.ShowLabel), item.ShowLabel ?? true); builder.AddAttribute(40, nameof(CheckboxList<>.ShowLabelTooltip), item.ShowLabelTooltip); builder.AddAttribute(50, nameof(CheckboxList<>.DisplayText), item.Text); - builder.AddAttribute(60, nameof(CheckboxList<>.Items), checkboxListSearchMetaData.Items); + builder.AddAttribute(60, nameof(CheckboxList<>.Items), checkboxListSearchMetadata.Items); builder.AddAttribute(70, nameof(CheckboxList<>.SkipValidate), true); builder.CloseComponent(); } - private void AddDateTimeRangeSearchComponent(ISearchItem item, DateTimeRangeSearchMetaData datetimeRangeSearchMetaData) + private void AddDateTimeRangeSearchComponent(ISearchItem item, DateTimeRangeSearchMetadata datetimeRangeSearchMetadata) { builder.OpenComponent(0); - builder.AddAttribute(10, nameof(DateTimeRange.Value), datetimeRangeSearchMetaData.Value); - builder.AddAttribute(20, nameof(DateTimeRange.OnValueChanged), datetimeRangeSearchMetaData.ValueChangedHandler); + builder.AddAttribute(10, nameof(DateTimeRange.Value), datetimeRangeSearchMetadata.Value); + builder.AddAttribute(20, nameof(DateTimeRange.OnValueChanged), datetimeRangeSearchMetadata.ValueChangedHandler); builder.AddAttribute(30, nameof(DateTimeRange.ShowLabel), item.ShowLabel ?? true); builder.AddAttribute(40, nameof(DateTimeRange.ShowLabelTooltip), item.ShowLabelTooltip); builder.AddAttribute(50, nameof(DateTimeRange.DisplayText), item.Text); @@ -151,17 +202,48 @@ private void AddDateTimeRangeSearchComponent(ISearchItem item, DateTimeRangeSear builder.CloseComponent(); } - private void AddDateTimeSearchComponent(ISearchItem item, DateTimeSearchMetaData datetimeSearchMetaData) + private void AddDateTimeSearchComponent(ISearchItem item, DateTimeSearchMetadata datetimeSearchMetadata) { builder.OpenComponent>(0); - builder.AddAttribute(10, nameof(DateTimePicker<>.Value), datetimeSearchMetaData.Value); - builder.AddAttribute(20, nameof(DateTimePicker<>.OnValueChanged), datetimeSearchMetaData.ValueChangedHandler); + builder.AddAttribute(10, nameof(DateTimePicker<>.Value), datetimeSearchMetadata.Value); + builder.AddAttribute(20, nameof(DateTimePicker<>.OnValueChanged), datetimeSearchMetadata.ValueChangedHandler); builder.AddAttribute(30, nameof(DateTimePicker<>.ShowLabel), item.ShowLabel ?? true); builder.AddAttribute(40, nameof(DateTimePicker<>.ShowLabelTooltip), item.ShowLabelTooltip); builder.AddAttribute(50, nameof(DateTimePicker<>.DisplayText), item.Text); builder.AddAttribute(60, nameof(DateTimePicker<>.SkipValidate), true); - builder.AddAttribute(70, nameof(DateTimePicker<>.DatePlaceHolderText), datetimeSearchMetaData.PlaceHolder); + builder.AddAttribute(70, nameof(DateTimePicker<>.DatePlaceHolderText), datetimeSearchMetadata.PlaceHolder); builder.CloseComponent(); } } + + extension(IEnumerable? items) + { + /// + /// 将 ISearchItem 集合转换为 FilterKeyValueAction 实例 + /// Converts a collection of ISearchItem to an instance of FilterKeyValueAction + /// + public FilterKeyValueAction ToFilter() + { + var action = new FilterKeyValueAction() + { + Filters = [] + }; + + if (items == null) + { + return action; + } + + foreach (var item in items) + { + var filter = item.GetFilter(); + if (filter != null) + { + action.Filters.Add(filter); + } + } + + return action; + } + } } diff --git a/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs b/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs index 7534e364570..915c583bb87 100644 --- a/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs +++ b/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs @@ -29,47 +29,47 @@ public static ISearchItem ParseSearchItem(this ITableColumn column, SearchFormLo GroupName = column.GroupName, GroupOrder = column.GroupOrder, Order = column.Order, - MetaData = column.BuildSearchMetaData(options) + Metadata = column.BuildSearchMetadata(options) }; return item; } - private static ISearchFormItemMetaData BuildSearchMetaData(this ITableColumn column, SearchFormLocalizerOptions options) + private static ISearchFormItemMetadata BuildSearchMetadata(this ITableColumn column, SearchFormLocalizerOptions options) { // 自定义搜索项逻辑 - if (column.SearchFormItemMetaData is not null) + if (column.SearchFormItemMetadata is not null) { - return column.SearchFormItemMetaData; + return column.SearchFormItemMetadata; } - ISearchFormItemMetaData? metaData = null; + ISearchFormItemMetadata? metaData = null; var fieldType = column.PropertyType; var type = Nullable.GetUnderlyingType(fieldType) ?? fieldType; if (Utility.IsCheckboxList(type, column.ComponentType)) { - metaData = new CheckboxListSearchMetaData() + metaData = new CheckboxListSearchMetadata() { Items = column.Items }; } else if (column.IsLookup()) { - metaData = new SelectSearchMetaData() + metaData = new SelectSearchMetadata() { Items = column.Items }; } else if (type.IsEnum) { - metaData = new SelectSearchMetaData() + metaData = new SelectSearchMetadata() { Items = type.ToSelectList(new SelectedItem() { Value = "", Text = options.SelectAllText }), }; } else if (fieldType.IsNumberWithDotSeparator()) { - metaData = new NumberSearchMetaData() + metaData = new NumberSearchMetadata() { StartValueLabelText = options.NumberStartValueLabelText, EndValueLabelText = options.NumberEndValueLabelText, @@ -78,7 +78,7 @@ private static ISearchFormItemMetaData BuildSearchMetaData(this ITableColumn col } else if (fieldType.IsBoolean()) { - metaData = new SelectSearchMetaData() + metaData = new SelectSearchMetadata() { Items = new List() { @@ -90,11 +90,11 @@ private static ISearchFormItemMetaData BuildSearchMetaData(this ITableColumn col } else if (fieldType.IsDateTime()) { - metaData = new DateTimeRangeSearchMetaData(); + metaData = new DateTimeRangeSearchMetadata(); } else { - metaData = new StringSearchMetaData() { FilterAction = FilterAction.Contains }; + metaData = new StringSearchMetadata() { FilterAction = FilterAction.Contains }; } return metaData; @@ -202,7 +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; + if (col.SearchFormItemMetadata != null) dest.SearchFormItemMetadata = col.SearchFormItemMetadata; } /// diff --git a/src/BootstrapBlazor/Utils/Utility.cs b/src/BootstrapBlazor/Utils/Utility.cs index b9559902117..8835c168b7d 100644 --- a/src/BootstrapBlazor/Utils/Utility.cs +++ b/src/BootstrapBlazor/Utils/Utility.cs @@ -580,7 +580,7 @@ internal static IEnumerable OrderFunc(this List cols /// 筛选条件 /// Filter condition /// - public static IEnumerable GenerateColumns(Func predicate) => GetTableColumns().Where(predicate); + public static IEnumerable GenerateColumns(Func predicate) => GetTableColumns().Where(predicate).ToList(); /// /// RenderTreeBuilder 扩展方法 通过 IEditorItem 与 model 创建 Display 组件 diff --git a/test/UnitTest/Attributes/AutoGenerateClassTest.cs b/test/UnitTest/Attributes/AutoGenerateClassTest.cs index a4c5ef3155f..c8cda49e09e 100644 --- a/test/UnitTest/Attributes/AutoGenerateClassTest.cs +++ b/test/UnitTest/Attributes/AutoGenerateClassTest.cs @@ -210,8 +210,8 @@ public void AutoGenerateColumn_Ok() attrInterface.IsRequiredWhenEdit = true; Assert.True(attrInterface.IsRequiredWhenEdit); - attrInterface.SearchFormItemMetaData = new StringSearchMetaData(); - Assert.NotNull(attrInterface.SearchFormItemMetaData); + attrInterface.SearchFormItemMetadata = new StringSearchMetadata(); + Assert.NotNull(attrInterface.SearchFormItemMetadata); var attrEditor = (IEditorItem)attr; attrEditor.Items = null; diff --git a/test/UnitTest/Components/EditDialogTest.cs b/test/UnitTest/Components/EditDialogTest.cs new file mode 100644 index 00000000000..1b25a7ac2aa --- /dev/null +++ b/test/UnitTest/Components/EditDialogTest.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace UnitTest.Components; + +public class EditDialogTest : BootstrapBlazorTestBase +{ + [Fact] + public void Items_Ok() + { + var foo = new Foo(); + var cut = Context.Render>(pb => + { + pb.Add(a => a.Model, foo); + }); + + cut.Contains("bb-editor"); + } +} diff --git a/test/UnitTest/Components/InternalTableColumnTest.cs b/test/UnitTest/Components/InternalTableColumnTest.cs index bd7445f72d0..496369adb1e 100644 --- a/test/UnitTest/Components/InternalTableColumnTest.cs +++ b/test/UnitTest/Components/InternalTableColumnTest.cs @@ -87,7 +87,7 @@ public void InternalTableColumn_Ok() SetValue("IsRequiredWhenEdit", true); SetValue("LookupService", null); SetValue("IgnoreWhenExport", true); - SetValue("SearchFormItemMetaData", new StringSearchMetaData()); + SetValue("SearchFormItemMetadata", new StringSearchMetadata()); void SetValue(string propertyName, object? val) => type!.GetProperty(propertyName)!.SetValue(instance, val); } diff --git a/test/UnitTest/Components/SearchDialogTest.cs b/test/UnitTest/Components/SearchDialogTest.cs new file mode 100644 index 00000000000..6627fc78168 --- /dev/null +++ b/test/UnitTest/Components/SearchDialogTest.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace UnitTest.Components; + +public class SearchDialogTest : BootstrapBlazorTestBase +{ + [Fact] + public async Task UseSearchForm_Ok() + { + FilterKeyValueAction? filter = null; + bool reset = false; + bool search = false; + var foo = new Foo(); + var cut = Context.Render>(pb => + { + pb.Add(a => a.UseSearchForm, true); + pb.Add(a => a.SearchItems, new List() + { + new SearchItem("Name", typeof(string), "姓名"), + new SearchItem("Address", typeof(string), "地址") + { + Metadata = new StringSearchMetadata() + } + }); + pb.Add(a => a.OnChanged, action => + { + filter = action; + return Task.CompletedTask; + }); + pb.Add(a => a.OnResetSearchClick, () => + { + reset = true; + return Task.CompletedTask; + }); + pb.Add(a => a.OnSearchClick, () => + { + search = true; + return Task.CompletedTask; + }); + }); + + // 测试更新搜索值 + var input = cut.Find("input"); + await cut.InvokeAsync(() => input.Change("test-value")); + Assert.NotNull(filter); + + Assert.Single(filter.Filters); + Assert.Equal("test-value", filter.Filters[0].FieldValue); + + var buttons = cut.FindComponents(); + Assert.Equal(2, buttons.Count); + + // 测试重置按钮 + var resetButton = buttons[0]; + await cut.InvokeAsync(() => resetButton.Instance.OnClickWithoutRender!.Invoke()); + Assert.True(reset); + + // 测试搜索按钮 + var searchButton = buttons[1]; + await cut.InvokeAsync(() => searchButton.Instance.OnClickWithoutRender!.Invoke()); + Assert.True(search); + } +} diff --git a/test/UnitTest/Components/SearchFormItemMetaDataTest.cs b/test/UnitTest/Components/SearchFormItemMetadataTest.cs similarity index 92% rename from test/UnitTest/Components/SearchFormItemMetaDataTest.cs rename to test/UnitTest/Components/SearchFormItemMetadataTest.cs index 6c05486dae0..4ca7e2f8846 100644 --- a/test/UnitTest/Components/SearchFormItemMetaDataTest.cs +++ b/test/UnitTest/Components/SearchFormItemMetadataTest.cs @@ -5,13 +5,13 @@ namespace UnitTest.Components; -public class SearchFormItemMetaDataTest +public class SearchFormItemMetadataTest { [Fact] - public async Task StringSearchFormItemMetaData_Ok() + public async Task StringSearchFormItemMetadata_Ok() { var valueChanged = false; - var meta = new StringSearchMetaData() + var meta = new StringSearchMetadata() { FilterAction = FilterAction.Contains, FilterLogic = FilterLogic.And, @@ -67,9 +67,9 @@ public async Task StringSearchFormItemMetaData_Ok() } [Fact] - public void MultipleStringSearchFormItemMetaData_Ok() + public void MultipleStringSearchFormItemMetadata_Ok() { - var meta = new MultipleStringSearchMetaData(); + var meta = new MultipleStringSearchMetadata(); var action = meta.GetFilter("fieldKey"); Assert.Null(action); @@ -103,9 +103,9 @@ public void MultipleStringSearchFormItemMetaData_Ok() } [Fact] - public void SelectSearchFormItemMetaData_Ok() + public void SelectSearchFormItemMetadata_Ok() { - var meta = new SelectSearchMetaData() + var meta = new SelectSearchMetadata() { Items = new List() { @@ -125,9 +125,9 @@ public void SelectSearchFormItemMetaData_Ok() } [Fact] - public void MultipleSelectSearchFormItemMetaData_Ok() + public void MultipleSelectSearchFormItemMetadata_Ok() { - var meta = new MultipleSelectSearchMetaData() + var meta = new MultipleSelectSearchMetadata() { Items = new List() { @@ -168,9 +168,9 @@ public void MultipleSelectSearchFormItemMetaData_Ok() } [Fact] - public async Task NumberSearchFormItemMetaData_Ok() + public async Task NumberSearchFormItemMetadata_Ok() { - var meta = new NumberSearchMetaData() + var meta = new NumberSearchMetadata() { StartValue = "10", StartValueLabelText = "Start", @@ -256,9 +256,9 @@ public async Task NumberSearchFormItemMetaData_Ok() } [Fact] - public void NumberSearchFormItemMetaData_ValueType() + public void NumberSearchFormItemMetadata_ValueType() { - var meta = new NumberSearchMetaData() + var meta = new NumberSearchMetadata() { StartValue = "10", EndValue = "20", @@ -277,9 +277,9 @@ public void NumberSearchFormItemMetaData_ValueType() } [Fact] - public async Task DateTimeSearchFormItemMetaData_Ok() + public async Task DateTimeSearchFormItemMetadata_Ok() { - var meta = new DateTimeSearchMetaData(); + var meta = new DateTimeSearchMetadata(); var valueChanged = false; meta.ValueChanged = () => @@ -316,9 +316,9 @@ public async Task DateTimeSearchFormItemMetaData_Ok() } [Fact] - public async Task DateTimeRangeSearchFormItemMetaData_Ok() + public async Task DateTimeRangeSearchFormItemMetadata_Ok() { - var meta = new DateTimeRangeSearchMetaData(); + var meta = new DateTimeRangeSearchMetadata(); var valueChanged = false; meta.ValueChanged = () => diff --git a/test/UnitTest/Components/SearchFormTest.cs b/test/UnitTest/Components/SearchFormTest.cs index 488b26ff700..8e251f6b01e 100644 --- a/test/UnitTest/Components/SearchFormTest.cs +++ b/test/UnitTest/Components/SearchFormTest.cs @@ -11,7 +11,7 @@ public class SearchFormTest : BootstrapBlazorTestBase public async Task Filter_Ok() { var filterKeyValueAction = new FilterKeyValueAction(); - var stringSearchMetaData = new StringSearchMetaData() + var stringSearchMetadata = new StringSearchMetadata() { PlaceHolder = "placeholder-val", Value = "foo-name-value" @@ -19,16 +19,11 @@ public async Task Filter_Ok() var searchItem = new SearchItem(nameof(Foo.Name), typeof(string), "Name") { Cols = 6 }; var cut = Context.Render(pb => { - pb.Add(a => a.Filter, filterKeyValueAction); - pb.Add(a => a.OnFilterChanged, action => + pb.Add(a => a.OnChanged, action => { filterKeyValueAction = action; return Task.CompletedTask; }); - pb.Add(a => a.FilterChanged, EventCallback.Factory.Create(this, v => - { - filterKeyValueAction = v; - })); pb.Add(a => a.ItemsPerRow, 4); pb.Add(a => a.RowType, RowType.Inline); pb.Add(a => a.LabelAlign, Alignment.Right); @@ -46,24 +41,50 @@ public async Task Filter_Ok() cut.Contains("col-sm-6"); cut.Contains("form-inline-end"); - searchItem.MetaData = stringSearchMetaData; + searchItem.Metadata = stringSearchMetadata; cut.Render(); cut.Contains("placeholder-val"); cut.Contains("foo-name-value"); // 改变搜索项值 - await stringSearchMetaData.ValueChangedHandler("test1"); + await stringSearchMetadata.ValueChangedHandler("test1"); Assert.Single(filterKeyValueAction.Filters); Assert.Equal("test1", filterKeyValueAction.Filters[0].FieldValue); + } + + [Fact] + public void SearchFormLocalizerOptions_Ok() + { + var searchFormLocalizerOptions = new SearchFormLocalizerOptions() + { + NumberStartValueLabelText = "Start-Text", + NumberEndValueLabelText = "End-Text" + }; + var cut = Context.Render(pb => + { + pb.Add(a => a.SearchFormLocalizerOptions, searchFormLocalizerOptions); + pb.Add(a => a.Items, new List() + { + new SearchItem(nameof(Foo.Count), typeof(int), nameof(Foo.Count)) + }); + }); + + cut.Contains("Start-Text"); + cut.Contains("End-Text"); + } - var searchForm = cut.Instance; - Assert.NotNull(searchForm.Filter); + [Fact] + public void ToFilter_Ok() + { + List? items = null; + var filter = items.ToFilter(); + Assert.NotNull(filter); } [Fact] public void LabelAlign_Ok() { - var stringSearchMetaData = new StringSearchMetaData() + var stringSearchMetadata = new StringSearchMetadata() { PlaceHolder = "placeholder-val", Value = "foo-name-value" @@ -79,11 +100,11 @@ public void LabelAlign_Ok() Text = "Name-Updated", GroupName = "Group1", GroupOrder = 1, - MetaData = stringSearchMetaData + Metadata = stringSearchMetadata }, new SearchItem(nameof(Foo.Address), typeof(string), "Address") { - MetaData = stringSearchMetaData + Metadata = stringSearchMetadata } }); }); @@ -109,11 +130,11 @@ public void Group_Ok() { GroupName = "Group1", GroupOrder= 1, - MetaData = new StringSearchMetaData() + Metadata = new StringSearchMetadata() }, new SearchItem(nameof(Foo.Address), typeof(string), "Address") { - MetaData = new StringSearchMetaData() + Metadata = new StringSearchMetadata() } }); }); @@ -142,37 +163,35 @@ public void Buttons_Ok() } [Fact] - public void MetaData_Ok() + public void Metadata_Ok() { - var filterKeyValueAction = new FilterKeyValueAction(); var cut = Context.Render(pb => { - pb.Add(a => a.Filter, filterKeyValueAction); pb.Add(a => a.Items, new List() { new SearchItem(nameof(Foo.Count), typeof(string), nameof(Foo.Count)) { - MetaData = new NumberSearchMetaData() + Metadata = new NumberSearchMetadata() }, new SearchItem(nameof(Foo.DateTime), typeof(string), nameof(Foo.DateTime)) { - MetaData = new DateTimeSearchMetaData() + Metadata = new DateTimeSearchMetadata() }, new SearchItem(nameof(Foo.DateTime), typeof(string), nameof(Foo.DateTime)) { - MetaData = new DateTimeRangeSearchMetaData() + Metadata = new DateTimeRangeSearchMetadata() }, new SearchItem(nameof(Foo.Education), typeof(string), nameof(Foo.Education)) { - MetaData = new SelectSearchMetaData() + Metadata = new SelectSearchMetadata() }, new SearchItem(nameof(Foo.Education), typeof(string), nameof(Foo.Education)) { - MetaData = new MultipleSelectSearchMetaData() + Metadata = new MultipleSelectSearchMetadata() }, new SearchItem(nameof(Foo.Education), typeof(string), nameof(Foo.Education)) { - MetaData = new CheckboxListSearchMetaData() + Metadata = new CheckboxListSearchMetadata() } }); }); @@ -185,8 +204,70 @@ public void GetFilter_Ok() var action = item.GetFilter(); Assert.Null(action); - item.MetaData = new StringSearchMetaData() { Value = "test" }; + item.Metadata = new StringSearchMetadata() { Value = "test" }; action = item.GetFilter(); Assert.NotNull(action); } + + [Fact] + public void BuildSearchMetadata_Enum_Ok() + { + var options = new SearchFormLocalizerOptions(); + var item = new SearchItem("Name", typeof(EnumEducation)); + + item.Metadata = item.BuildSearchMetadata(options); + Assert.IsType(item.Metadata); + item.Reset(); + } + + [Fact] + public void BuildSearchMetadata_Number_Ok() + { + var options = new SearchFormLocalizerOptions(); + var item = new SearchItem("Name", typeof(int)); + + item.Metadata = item.BuildSearchMetadata(options); + Assert.IsType(item.Metadata); + item.Reset(); + } + + [Fact] + public void BuildSearchMetadata_Bool_Ok() + { + var options = new SearchFormLocalizerOptions(); + var item = new SearchItem("Name", typeof(bool)); + + item.Metadata = item.BuildSearchMetadata(options); + Assert.IsType(item.Metadata); + item.Reset(); + } + + [Fact] + public void BuildSearchMetadata_DateTimeRange_Ok() + { + var options = new SearchFormLocalizerOptions(); + var item = new SearchItem("Name", typeof(DateTime)); + + item.Metadata = item.BuildSearchMetadata(options); + Assert.IsType(item.Metadata); + item.Reset(); + } + + [Fact] + public void BuildSearchMetadata_DateTime_Ok() + { + var item = new SearchItem("Name", typeof(DateTime)) { Metadata = new DateTimeSearchMetadata() }; + + Assert.IsType(item.Metadata); + item.Reset(); + } + + [Fact] + public void Reset_Ok() + { + var item = new SearchItem("Name", typeof(string)); + + // Metadata 为空时不报错 + item.Reset(); + } } diff --git a/test/UnitTest/Components/TableDialogTest.cs b/test/UnitTest/Components/TableDialogTest.cs index a67c0333005..8d35dce4099 100644 --- a/test/UnitTest/Components/TableDialogTest.cs +++ b/test/UnitTest/Components/TableDialogTest.cs @@ -256,9 +256,10 @@ public async Task EditAsync_Ok() var searchButton = cut.Find(".fa-magnifying-glass-plus"); await cut.InvokeAsync(() => searchButton.Click()); - cut.WaitForAssertion(() => cut.Find(".fa-magnifying-glass")); + await cut.WaitForAssertionAsync(() => cut.Find(".fa-magnifying-glass")); var queryButton = cut.Find(".fa-magnifying-glass"); await cut.InvokeAsync(() => queryButton.Click()); + await cut.InvokeAsync(() => modal.Instance.CloseCallback()); table.Render(pb => { @@ -271,9 +272,10 @@ public async Task EditAsync_Ok() searchButton = cut.Find(".fa-magnifying-glass-plus"); await cut.InvokeAsync(() => searchButton.Click()); - cut.WaitForAssertion(() => cut.Find(".fa-magnifying-glass")); + await cut.WaitForAssertionAsync(() => cut.Find(".fa-magnifying-glass")); queryButton = cut.Find(".fa-magnifying-glass"); await cut.InvokeAsync(() => queryButton.Click()); + await cut.InvokeAsync(() => modal.Instance.CloseCallback()); table = cut.FindComponent>(); table.Render(pb => @@ -291,9 +293,72 @@ public async Task EditAsync_Ok() searchButton = cut.Find(".fa-magnifying-glass-plus"); await cut.InvokeAsync(() => searchButton.Click()); - cut.WaitForAssertion(() => cut.Find(".fa-magnifying-glass")); + await cut.WaitForAssertionAsync(() => cut.Find(".fa-magnifying-glass")); queryButton = cut.Find(".fa-magnifying-glass"); await cut.InvokeAsync(() => queryButton.Click()); + await cut.InvokeAsync(() => modal.Instance.CloseCallback()); + + // 开启 UseSearchForm 优先级最高 + FilterKeyValueAction? filter = null; + table.Render(pb => + { + pb.Add(a => a.UseSearchForm, true); + pb.Add(a => a.SearchItems, new List() + { + new SearchItem("Name", typeof(string), "Name"), + new SearchItem("Address", typeof(string), "Address") + { + Metadata = new StringSearchMetadata() { PlaceHolder = "Address-Placeholder" } + } + }); + pb.Add(a => a.OnQueryAsync, options => + { + filter = options.ToFilter(); + return Task.FromResult(new QueryData() + { + Items = items, + TotalCount = items.Count, + IsAdvanceSearch = true, + IsSearch = true, + IsFiltered = true, + IsSorted = true + }); + }); + }); + // 弹出高级搜索弹窗内部使用 SearchForm 组件,测试 SearchForm 组件的功能 + searchButton = cut.Find(".fa-magnifying-glass-plus"); + await cut.InvokeAsync(() => searchButton.Click()); + await cut.WaitForAssertionAsync(() => cut.Find(".fa-magnifying-glass")); + + // 查找高级搜索弹窗组件 + var searchDialog = cut.FindComponent>(); + Assert.NotNull(searchDialog); + searchDialog.Contains("Address-Placeholder"); + + // 更新搜索条件值 + var searchItem = searchDialog.FindComponent>(); + Assert.NotNull(searchItem.Instance.OnValueChanged); + await cut.InvokeAsync(() => searchItem.Instance.OnValueChanged("Test_Name")); + + // 测试点击搜索按钮 + searchButton = cut.Find(".fa-magnifying-glass"); + await cut.InvokeAsync(() => searchButton.Click()); + // 关闭弹窗 + await cut.InvokeAsync(() => modal.Instance.CloseCallback()); + Assert.NotNull(filter); + Assert.Single(filter.Filters); + + // 测试点击重置按钮 + searchButton = cut.Find(".fa-magnifying-glass-plus"); + await cut.InvokeAsync(() => searchButton.Click()); + await cut.WaitForAssertionAsync(() => cut.Find(".fa-magnifying-glass")); + + var resetButton = cut.Find(".fa-trash-can"); + await cut.InvokeAsync(() => resetButton.Click()); + // 关闭弹窗 + await cut.InvokeAsync(() => modal.Instance.CloseCallback()); + Assert.NotNull(filter); + Assert.Empty(filter.Filters); } [Fact] @@ -349,7 +414,8 @@ public async Task EditDialog_Ok() await cut.InvokeAsync(() => closeButton.Click()); // 关闭 Swal 确认弹窗 - var swalModal = cut.FindComponents().Last(); + var count = cut.FindComponents().Count; + var swalModal = cut.FindComponents()[count - 1]; await cut.InvokeAsync(() => swalModal.Instance.CloseCallback()); } diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index 2a7dc14e032..bc8db5e329a 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -641,7 +641,7 @@ public async Task UseSearchForm_Ok() builder.AddAttribute(1, "Field", true); builder.AddAttribute(2, "FieldExpression", Utility.GenerateValueExpression(foo, nameof(Foo.Complete), typeof(bool))); builder.AddAttribute(3, nameof(ITableColumn.Searchable), true); - builder.AddAttribute(3, nameof(ITableColumn.SearchFormItemMetaData), new StringSearchMetaData()); + builder.AddAttribute(3, nameof(ITableColumn.SearchFormItemMetadata), new StringSearchMetadata()); builder.CloseComponent(); }); }); @@ -679,7 +679,7 @@ public async Task SearchItems_Ok() pb.Add(a => a.UseSearchForm, true); pb.Add(a => a.SearchItems, new List() { - new SearchItem("Name", typeof(string), "名称") { MetaData = new StringSearchMetaData() } + new SearchItem("Name", typeof(string), "名称") { Metadata = new StringSearchMetadata() } }); pb.Add(a => a.SearchMode, SearchMode.Top); pb.Add(a => a.OnQueryAsync, OnQueryAsync(localizer)); diff --git a/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs b/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs index 458191b5e88..89b2257256b 100644 --- a/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs +++ b/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs @@ -115,7 +115,7 @@ public void CopyValue_Ok() IsRequiredWhenAdd = true, IsRequiredWhenEdit = true, - SearchFormItemMetaData = new StringSearchMetaData() + SearchFormItemMetadata = new StringSearchMetadata() }; col.CopyValue(attr); Assert.NotNull(col.ComponentType); @@ -186,7 +186,7 @@ public void CopyValue_Ok() Assert.Equal("test-key", col.LookupServiceKey); Assert.Equal(true, col.LookupServiceData); - Assert.NotNull(col.SearchFormItemMetaData); + Assert.NotNull(col.SearchFormItemMetadata); } [Fact]