From 53f285353a233fcc28ed8bd332bbdc403239085d Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 27 May 2025 15:51:50 +0800 Subject: [PATCH 1/4] =?UTF-8?q?refactor:=20=E5=88=A0=E9=99=A4=E5=86=97?= =?UTF-8?q?=E4=BD=99=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SelectGeneric/SelectGeneric.razor.cs | 2 +- .../SelectGeneric/SelectGeneric.razor.scss | 241 ------------------ 2 files changed, 1 insertion(+), 242 deletions(-) delete mode 100644 src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.scss diff --git a/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.cs b/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.cs index 713b7849a3d..5eb15f93e8f 100644 --- a/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.cs +++ b/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.cs @@ -9,7 +9,7 @@ namespace BootstrapBlazor.Components; /// -/// Select 组件实现类 +/// Select 泛型组件实现类 /// /// [CascadingTypeParameter(nameof(TValue))] diff --git a/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.scss b/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.scss deleted file mode 100644 index 3e3eee6f65e..00000000000 --- a/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.scss +++ /dev/null @@ -1,241 +0,0 @@ -.select, -.popover-dropdown { - --bb-dropdown-link-pre-active-bg: #{$bb-dropdown-link-pre-active-bg}; -} - -.select { - --bb-select-focus-shadow: #{$bb-select-focus-shadow}; - --bb-select-padding-right: #{$bb-select-padding-right}; - --bb-select-padding: #{$bb-select-padding}; - --bb-select-search-padding: #{$bb-select-search-padding}; - --bb-select-search-margin-bottom: #{$bb-select-search-margin-bottom}; - --bb-select-search-border-color: #{$bb-select-search-border-color}; - --bb-select-search-padding-right: #{$bb-select-search-padding-right}; - --bb-select-search-icon-color: #{$bb-select-search-icon-color}; - --bb-select-search-icon-right: #{$bb-select-search-icon-right}; - --bb-select-search-icon-top: #{$bb-select-search-icon-top}; - --bb-select-search-height: #{$bb-select-search-height}; - --bb-select-append-width: #{$bb-select-append-width}; - --bb-select-append-color: #{$bb-select-append-color}; -} - -.select:not(.cascade) .dropdown-menu { - overflow-x: hidden; - width: 100%; -} - -.cascade, -.select { - --bb-select-dropdown-menu-margin-top: 8px; -} - -.cascade .dropdown-menu, -.selec .dropdown-menu { - margin-block-start: var(--bb-select-dropdown-menu-margin-top) !important; -} - -.select .form-select { - background-image: none; - background-color: var(--bs-body-bg); - border: var(--bs-border-width) solid var(--bs-border-color); - border-radius: var(--bs-border-radius); - padding: var(--bb-select-padding); - padding-inline-end: var(--bb-select-padding-right); - cursor: pointer; -} - -.select .form-select:disabled { - background-color: var(--bs-secondary-bg); -} - -.dropdown-menu { - --bs-dropdown-border-radius: var(--bs-border-radius); - overflow: auto; - max-height: var(--bb-dropdown-max-height); -} - -.dropdown-menu .dropdown-virtual { - overflow-y: auto; - margin: calc(0px - var(--bs-dropdown-padding-y)) var(--bs-dropdown-padding-x); - max-height: calc(var(--bb-dropdown-max-height) - 2px); - padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x); -} - -.dropdown-menu .search + .dropdown-virtual { - max-height: calc(var(--bb-dropdown-max-height) - var(--bb-select-search-height)); -} - -.dropdown-item { - cursor: pointer; -} - -.dropdown-item.preActive { - background-color: var(--bb-dropdown-link-pre-active-bg); -} - -.dropdown-menu-arrow { - width: 0; - height: 0; - border-width: 0 6px 6px; - border-style: solid; - border-color: transparent transparent rgba(0,0,0,.15); - position: absolute; - left: 20px; - margin-block-start: 4px; - z-index: 1001; - display: none; -} - -.dropdown-menu-arrow:after { - content: " "; - width: 0; - height: 0; - border-width: 0 6px 6px; - border-style: solid; - border-color: transparent transparent var(--bs-body-bg); - position: absolute; - top: 1px; - left: -6px; -} - -[data-bs-theme='dark'] .dropdown-menu-arrow:after { - content: none; -} - -.show > .dropdown-menu, -.show > .dropdown-menu-arrow { - display: block; -} - -.form-select:focus { - box-shadow: var(--bb-select-focus-shadow); - border-color: var(--bb-border-focus-color); -} - -.form-select:not(:disabled):hover { - border-color: var(--bb-border-hover-color); -} - -.form-select.show + .form-select-append i { - transform: rotate(0); -} - -.dropdown-menu[data-popper-placement="bottom-start"].show + .dropdown-menu-arrow, -.dropdown-menu[data-bs-popper="none"].show + .dropdown-menu-arrow { - display: block; -} - -.form-select-append { - position: absolute; - height: 100%; - width: var(--bb-select-append-width); - right: 0; - top: 0; - color: var(--bb-select-append-color); - pointer-events: none; - display: flex; - align-items: center; - justify-content: center; -} - -.form-select-append i { - transition: all .3s; - transform: rotate(180deg); -} - -.show > .form-select-append i { - transform: rotate(0); -} - -.select .clear-icon { - position: absolute; - height: 100%; - width: var(--bb-select-append-width); - right: 0; - top: 0; - color: var(--bb-select-append-color); - align-items: center; - justify-content: center; - cursor: pointer; - display: none; -} - -.select:hover .clear-icon { - display: flex; -} - -.select.is-clearable:not(.disabled):hover .form-select-append { - display: none; -} - -.form-select.is-valid:focus, -.was-validated .form-select:valid:focus, -.form-select.is-invalid:focus, -.was-validated .form-select:invalid:focus { - box-shadow: none; -} - -.form-select.is-valid:not([multiple]):not([size]), -.form-select.is-valid:not([multiple])[size="1"], -.was-validated .form-select:valid:not([multiple]):not([size]), -.was-validated .form-select:valid:not([multiple])[size="1"], -.form-select.is-invalid:not([multiple]):not([size]), -.form-select.is-invalid:not([multiple])[size="1"], -.was-validated .form-select:invalid:not([multiple]):not([size]), -.was-validated .form-select:invalid:not([multiple])[size="1"] { - background-position: right -1rem center, center right 1.5rem; - padding-inline-end: var(--bb-select-padding-right); -} - -.arrow-danger { - border-color: transparent transparent var(--bs-danger); -} - -.arrow-success { - border-color: transparent transparent var(--bs-success); -} - -.arrow-primary { - border-color: transparent transparent var(--bs-primary); -} - -.arrow-warning { - border-color: transparent transparent var(--bs-warning); -} - -.arrow-info { - border-color: transparent transparent var(--bs-info); -} - -.dropdown-menu .search { - padding: var(--bb-select-search-padding); - position: relative; - border-block-end: var(--bs-border-width) solid var(--bb-select-search-border-color); - margin-block-end: var(--bb-select-search-margin-bottom); -} - -.dropdown-menu .search.is-fixed { - position: sticky; - top: calc(-1 * var(--bs-dropdown-padding-y)); - background-color: var(--bs-dropdown-bg); -} - -.dropdown-menu .search .search-text { - padding-inline-end: var(--bb-select-search-padding-right); -} - -.dropdown-menu .search .icon { - position: absolute; - right: var(--bb-select-search-icon-right); - top: var(--bb-select-search-icon-top); - color: var(--bb-select-search-icon-color); -} - -.select:not(.multi-select) .dropdown-toggle { - position: relative; -} - -.select .dropdown-toggle:after, -.btn-popover-confirm.dropdown-toggle:after { - content: none; -} From e32bf2b5be92875c042b8773704b12bc2942611a Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 27 May 2025 15:52:10 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=A4=9A?= =?UTF-8?q?=E9=80=89=E6=B3=9B=E5=9E=8B=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SelectGeneric/MultiSelectGeneric.razor | 154 ++++++ .../SelectGeneric/MultiSelectGeneric.razor.cs | 516 ++++++++++++++++++ 2 files changed, 670 insertions(+) create mode 100644 src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor create mode 100644 src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor.cs diff --git a/src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor b/src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor new file mode 100644 index 00000000000..19d7c1eb1b4 --- /dev/null +++ b/src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor @@ -0,0 +1,154 @@ +@namespace BootstrapBlazor.Components +@using Microsoft.AspNetCore.Components.Web.Virtualization +@typeparam TValue +@inherits SelectBase> +@attribute [BootstrapModuleAutoLoader("Select/MultiSelect.razor.js", JSObjectReference = true)] + +@if (IsShowLabel) +{ + +} +
+
+
@PlaceHolder
+
+ @if (DisplayTemplate != null) + { + @DisplayTemplate(SelectedItems) + } + else + { + foreach (var item in SelectedItems) + { + if (ShowCloseButton) + { +
+ + + + @item.Text +
+ } + else + { + @item.Text + } + } + } +
+ @if (!IsSingleLine) + { + + } +
+ @if (GetClearable()) + { + + } +
+ @if (ShowSearch) + { + + } + @if (ShowToolbar) + { +
+ @if (ShowDefaultButtons) + { + @SelectAllText + @ReverseSelectText + @ClearText + } + @ButtonTemplate +
+ } + @if (IsVirtualize) + { + + } + else if (Rows.Count == 0) + { + + } + else + { + + } +
+
+ +@code { + RenderFragment> RenderRow => item => + @ +
+
+ +
+ @if (ItemTemplate != null) + { + @ItemTemplate(item) + } + else if (IsMarkupString) + { + @((MarkupString)item.Text) + } + else + { + @item.Text + } +
+
; + + RenderFragment RenderPlaceHolderRow => context => + @; +} diff --git a/src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor.cs b/src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor.cs new file mode 100644 index 00000000000..e2ae8e1e182 --- /dev/null +++ b/src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor.cs @@ -0,0 +1,516 @@ +// 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 + +using Microsoft.AspNetCore.Components.Web.Virtualization; +using Microsoft.Extensions.Localization; + +namespace BootstrapBlazor.Components; + +/// +/// MultiSelectGeneric component +/// +[ExcludeFromCodeCoverage] +public partial class MultiSelectGeneric +{ + private List> SelectedItems { get; } = []; + + private string? ClassString => CssBuilder.Default("select dropdown multi-select") + .AddClass("is-clearable", IsClearable) + .Build(); + + private string? DropdownMenuClassString => CssBuilder.Default("dropdown-menu") + .AddClass("is-fixed-toolbar", ShowToolbar) + .Build(); + + private string? EditSubmitKeyString => EditSubmitKey == EditSubmitKey.Space ? EditSubmitKey.ToDescriptionString() : null; + + private string? ToggleClassString => CssBuilder.Default("dropdown-toggle scroll") + .AddClass($"border-{Color.ToDescriptionString()}", Color != Color.None && !IsDisabled) + .AddClass("is-fixed", IsFixedHeight) + .AddClass("is-single-line", IsSingleLine) + .AddClass("disabled", IsDisabled) + .AddClass("show", ValidateForm != null && _isToggle) + .AddClass(CssClass).AddClass(ValidCss) + .Build(); + + private string? GetItemClassString(SelectedItem item) => CssBuilder.Default("dropdown-item") + .AddClass("active", GetCheckedState(item)) + .AddClass("disabled", item.IsDisabled) + .Build(); + + private string? PlaceHolderClassString => CssBuilder.Default("multi-select-ph") + .AddClass("d-none", SelectedItems.Count != 0) + .Build(); + + /// + /// 获得/设置 显示部分模板 默认 null + /// + [Parameter] + public RenderFragment>>? DisplayTemplate { get; set; } + + /// + /// 获得/设置 是否显示关闭按钮 默认为 true 显示 + /// + [Parameter] + public bool ShowCloseButton { get; set; } = true; + + /// + /// 获得/设置 关闭按钮图标 默认为 null + /// + [Parameter] + public string? CloseButtonIcon { get; set; } + + /// + /// 获得/设置 是否显示功能按钮 默认为 false 不显示 + /// + [Parameter] + public bool ShowToolbar { get; set; } + + /// + /// 获得/设置 是否显示默认功能按钮 默认为 true 显示 + /// + [Parameter] + public bool ShowDefaultButtons { get; set; } = true; + + /// + /// 获得/设置 是否固定高度 默认 false + /// + [Parameter] + public bool IsFixedHeight { get; set; } + + /// + /// 获得/设置 是否为单行模式 默认 false + /// + [Parameter] + public bool IsSingleLine { get; set; } + + /// + /// 获得/设置 编辑模式下输入选项更新后回调方法 默认 null + /// 返回 实例时输入选项生效,返回 null 时选项不生效进行舍弃操作,建议在回调方法中自行提示 + /// + /// Effective when is set. + [Parameter] + public Func>? OnEditCallback { get; set; } + + /// + /// 获得/设置 编辑提交按键 默认 Enter + /// + [Parameter] + public EditSubmitKey EditSubmitKey { get; set; } + + /// + /// 获得/设置 扩展按钮模板 + /// + [Parameter] + public RenderFragment? ButtonTemplate { get; set; } + + /// + /// 获得/设置 选中项集合发生改变时回调委托方法 + /// + [Parameter] + public Func>, Task>? OnSelectedItemsChanged { get; set; } + + /// + /// Gets or sets the default virtualize items text. + /// + [Parameter] + public string? DefaultVirtualizeItemText { get; set; } + + /// + /// 获得/设置 全选按钮显示文本 + /// + [Parameter] + [NotNull] + public string? SelectAllText { get; set; } + + /// + /// 获得/设置 全选按钮显示文本 + /// + [Parameter] + [NotNull] + public string? ReverseSelectText { get; set; } + + /// + /// 获得/设置 全选按钮显示文本 + /// + [Parameter] + [NotNull] + public string? ClearText { get; set; } + + /// + /// 获得/设置 选项最大数 默认为 0 不限制 + /// + [Parameter] + public int Max { get; set; } + + /// + /// 获得/设置 设置最大值时错误消息文字 + /// + [Parameter] + [NotNull] + public string? MaxErrorMessage { get; set; } + + /// + /// 获得/设置 选项最小数 默认为 0 不限制 + /// + [Parameter] + public int Min { get; set; } + + /// + /// 获得/设置 设置最小值时错误消息文字 + /// + [Parameter] + [NotNull] + public string? MinErrorMessage { get; set; } + + /// + /// Gets or sets the items. + /// + [Parameter] + [NotNull] + public IEnumerable>? Items { get; set; } + + /// + /// Gets or sets the callback method for loading virtualized items. + /// + [Parameter] + [NotNull] + public Func>>>? OnQueryAsync { get; set; } + + /// + /// Gets or sets the callback method when the search text changes. + /// + [Parameter] + public Func>>? OnSearchTextChanged { get; set; } + + /// + /// Gets or sets the item template. + /// + [Parameter] + public RenderFragment>? ItemTemplate { get; set; } + + [Inject] + [NotNull] + private IStringLocalizer>? Localizer { get; set; } + + private string? PlaceholderString => SelectedItems.Count == 0 ? PlaceHolder : null; + + private string? ScrollIntoViewBehaviorString => ScrollIntoViewBehavior == ScrollIntoViewBehavior.Smooth ? null : ScrollIntoViewBehavior.ToDescriptionString(); + + [NotNull] + private Virtualize>? _virtualizeElement = default; + + /// + /// + /// + protected override void OnParametersSet() + { + base.OnParametersSet(); + + PlaceHolder ??= Localizer[nameof(PlaceHolder)]; + SelectAllText ??= Localizer[nameof(SelectAllText)]; + ReverseSelectText ??= Localizer[nameof(ReverseSelectText)]; + ClearText ??= Localizer[nameof(ClearText)]; + MinErrorMessage ??= Localizer[nameof(MinErrorMessage)]; + MaxErrorMessage ??= Localizer[nameof(MaxErrorMessage)]; + NoSearchDataText ??= Localizer[nameof(NoSearchDataText)]; + + DropdownIcon ??= IconTheme.GetIconByKey(ComponentIcons.MultiSelectDropdownIcon); + CloseButtonIcon ??= IconTheme.GetIconByKey(ComponentIcons.MultiSelectCloseIcon); + ClearIcon ??= IconTheme.GetIconByKey(ComponentIcons.MultiSelectClearIcon); + + ResetRules(); + + _itemsCache = null; + } + + /// + /// + /// + /// + protected override void OnAfterRender(bool firstRender) + { + base.OnAfterRender(firstRender); + + _isToggle = false; + } + + /// + /// + /// + /// + protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new + { + ConfirmMethodCallback = nameof(ConfirmSelectedItem), + SearchMethodCallback = nameof(TriggerOnSearch), + ToggleRow = nameof(ToggleRow) + }); + + /// + /// Triggers the search callback method. + /// + /// The search text. + /// A task that represents the asynchronous operation. + [JSInvokable] + public async Task TriggerOnSearch(string searchText) + { + _itemsCache = null; + SearchText = searchText; + await RefreshVirtualizeElement(); + StateHasChanged(); + } + + /// + /// Refreshes the virtualize component. + /// + /// + private async Task RefreshVirtualizeElement() + { + if (IsVirtualize && OnQueryAsync != null) + { + // 通过 ItemProvider 提供数据 + await _virtualizeElement.RefreshDataAsync(); + } + } + + private List>? _itemsCache; + /// + /// Gets the dropdown menu rows. + /// + private List> Rows + { + get + { + _itemsCache ??= string.IsNullOrEmpty(SearchText) ? GetRowsByItems() : GetRowsBySearch(); + return _itemsCache; + } + } + + private List> GetRowsBySearch() + { + var items = OnSearchTextChanged?.Invoke(SearchText) ?? FilterBySearchText(GetRowsByItems()); + return [.. items]; + } + + private IEnumerable> FilterBySearchText(IEnumerable> source) => string.IsNullOrEmpty(SearchText) + ? source + : source.Where(i => i.Text.Contains(SearchText, StringComparison)); + + private int _totalCount; + private ItemsProviderResult> _result; + + private List> GetVirtualItems() => [.. FilterBySearchText(GetRowsByItems())]; + + private async ValueTask>> LoadItems(ItemsProviderRequest request) + { + // 有搜索条件时使用原生请求数量 + // 有总数时请求剩余数量 + var count = !string.IsNullOrEmpty(SearchText) ? request.Count : GetCountByTotal(); + var data = await OnQueryAsync(new() { StartIndex = request.StartIndex, Count = count, SearchText = SearchText }); + + _itemsCache = null; + _totalCount = data.TotalCount; + var items = data.Items ?? []; + _result = new ItemsProviderResult>(items, _totalCount); + return _result; + + int GetCountByTotal() => _totalCount == 0 ? request.Count : Math.Min(request.Count, _totalCount - request.StartIndex); + } + + /// + /// + /// + /// + protected override async Task OnClearValue() + { + await base.OnClearValue(); + + SelectedItems.Clear(); + } + + private bool _isToggle; + + /// + /// + /// + /// + private List> GetRowsByItems() + { + var items = new List>(); + if (_result.Items != null) + { + items.AddRange(_result.Items); + } + else if (Items != null) + { + items.AddRange(Items); + } + return items; + } + + /// + /// 客户端回车回调方法 + /// + /// + /// + [JSInvokable] + public async Task ConfirmSelectedItem(int index) + { + var rows = Rows; + if (index < rows.Count) + { + await ToggleRow(rows[index]); + StateHasChanged(); + } + } + + /// + /// 切换当前选项方法 + /// + /// + [JSInvokable] + public async Task ToggleRow(SelectedItem val) + { + if (!IsDisabled) + { + var item = SelectedItems.FirstOrDefault(i => Equals(i.Value, val.Value)); + if (item != null) + { + SelectedItems.Remove(item); + } + else + { + var d = Rows.FirstOrDefault(i => Equals(i.Value, val.Value)); + if (d != null) + { + SelectedItems.Add(d); + } + } + + _isToggle = true; + // 更新选中值 + await SetValue(); + } + } + + private int _min; + private int _max; + private void ResetRules() + { + if (Max != _max) + { + _max = Max; + Rules.RemoveAll(v => v is MaxValidator); + + if (Max > 0) + { + Rules.Add(new MaxValidator() { Value = Max, ErrorMessage = MaxErrorMessage }); + } + } + + if (Min != _min) + { + _min = Min; + Rules.RemoveAll(v => v is MinValidator); + + if (Min > 0) + { + Rules.Add(new MinValidator() { Value = Min, ErrorMessage = MinErrorMessage }); + } + } + } + + private async Task SetValue() + { + if (ValidateForm == null && (Min > 0 || Max > 0)) + { + var validationContext = new ValidationContext(Value!) { MemberName = FieldIdentifier?.FieldName }; + var validationResults = new List(); + + await ValidatePropertyAsync(CurrentValue, validationContext, validationResults); + ToggleMessage(validationResults); + } + + if (OnSelectedItemsChanged != null) + { + await OnSelectedItemsChanged.Invoke(SelectedItems); + } + + CurrentValue = [.. SelectedItems.Select(i => i.Value)]; + if (!ValueChanged.HasDelegate) + { + StateHasChanged(); + } + } + + /// + /// 清除选择项方法 + /// + /// + public async Task Clear() + { + SelectedItems.Clear(); + await SetValue(); + } + + /// + /// 全选选择项方法 + /// + /// + public async Task SelectAll() + { + SelectedItems.Clear(); + SelectedItems.AddRange(Rows); + await SetValue(); + } + + /// + /// 翻转选择项方法 + /// + /// + public async Task InvertSelect() + { + var items = Rows.Where(item => !SelectedItems.Any(i => Equals(i.Value, item.Value))).ToList(); + SelectedItems.Clear(); + SelectedItems.AddRange(items); + await SetValue(); + } + + private bool GetCheckedState(SelectedItem item) => SelectedItems.Any(i => Equals(i.Value, item.Value)); + + private string? GetCheckedString(SelectedItem item) => GetCheckedState(item) ? "checked" : null; + + private bool CheckCanTrigger(SelectedItem item) + { + var ret = true; + if (Max > 0) + { + ret = SelectedItems.Count < Max || GetCheckedState(item); + } + return ret; + } + + private bool CheckCanSelect(SelectedItem item) + { + var ret = GetCheckedState(item); + if (!ret) + { + ret = CheckCanTrigger(item); + } + return !ret; + } + + /// + /// 客户端检查完成时调用此方法 + /// + /// + protected override void OnValidate(bool? valid) + { + if (valid != null) + { + Color = valid.Value ? Color.Success : Color.Danger; + } + } +} From 459192e09f8a48ba92f9735bd17e5ac488e0b77a Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 27 May 2025 15:52:19 +0800 Subject: [PATCH 3/4] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MultiSelects.razor | 8 ++++++++ .../Components/Samples/MultiSelects.razor.cs | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor b/src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor index 6a6ecb4a7a7..12de9c0a865 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor @@ -351,6 +351,14 @@ private enum MultiSelectEnumFoo + +
+
+ +
+
+
+ diff --git a/src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor.cs index efd54b9ddca..840d57cbb4a 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor.cs @@ -125,6 +125,11 @@ private enum MultiSelectEnumFoo private bool _showToolbar = true; private bool _showSearch = true; + [NotNull] + private List>? FooItems { get; set; } + + private List? _genericValue = null; + private async Task OnEditCallback(string value) { await Task.Delay(100); @@ -188,6 +193,7 @@ protected override void OnInitialized() Items8 = GenerateItems(); TemplateItems = GenerateItems(); EditableItems = GenerateItems(); + FooItems = [.. Foo.GenerateFoo(LocalizerFoo).Select(i => new SelectedItem(i, i.Name!))]; // 初始化数据 DataSource = From 4484c5da209ae5f146036b44dec6ec829b8b541d Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 27 May 2025 15:54:49 +0800 Subject: [PATCH 4/4] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor.Server/Locales/en-US.json | 4 +++- src/BootstrapBlazor.Server/Locales/zh-CN.json | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 208e953d175..4f37e453817 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -3088,7 +3088,9 @@ "MultiSelectVirtualizeDescription": "Component virtual scrolling supports two ways of providing data through Items or OnQueryAsync callback methods", "MultiSelectsAttribute_ShowSearch": "Whether to display the search box", "MultiSelectsAttribute_IsVirtualize": "Wether to enable virtualize", - "MultiSelectsAttribute_DefaultVirtualizeItemText": "The text string corresponding to the first load value when virtual scrolling is turned on is separated by commas" + "MultiSelectsAttribute_DefaultVirtualizeItemText": "The text string corresponding to the first load value when virtual scrolling is turned on is separated by commas", + "MultiSelectGenericTitle": "Generic", + "MultiSelectGenericIntro": "Data source Items supports generics when using SelectedItem<TValue>" }, "BootstrapBlazor.Server.Components.Samples.Radios": { "RadiosTitle": "Radio", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index 2d271a465c8..a88bce61268 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -3088,7 +3088,9 @@ "MultiSelectVirtualizeDescription": "组件虚拟滚动支持两种形式通过 Items 或者 OnQueryAsync 回调方法提供数据", "MultiSelectsAttribute_ShowSearch": "是否显示搜索框", "MultiSelectsAttribute_IsVirtualize": "是否开启虚拟滚动", - "MultiSelectsAttribute_DefaultVirtualizeItemText": "开启虚拟滚动时首次加载 Value 对应的文本字符串用逗号分割" + "MultiSelectsAttribute_DefaultVirtualizeItemText": "开启虚拟滚动时首次加载 Value 对应的文本字符串用逗号分割", + "MultiSelectGenericTitle": "泛型支持", + "MultiSelectGenericIntro": "数据源 Items 使用 SelectedItem<TValue> 时即可支持泛型" }, "BootstrapBlazor.Server.Components.Samples.Radios": { "RadiosTitle": "Radio 单选框",