diff --git a/src/admin/Bootstrap.Admin/Components/AdminLayoutComponentBase.cs b/src/admin/Bootstrap.Admin/Components/AdminLayoutComponentBase.cs index 5fa8b27d..f0570f3f 100644 --- a/src/admin/Bootstrap.Admin/Components/AdminLayoutComponentBase.cs +++ b/src/admin/Bootstrap.Admin/Components/AdminLayoutComponentBase.cs @@ -22,11 +22,6 @@ namespace Bootstrap.Admin.Components [CascadingParameter(Name = "Default")] public DefaultLayout RootLayout { get; protected set; } = new DefaultLayout(); - /// - /// Toast 组件实例 - /// - protected Toast Toast { get; set; } = new Toast(); - /// /// /// @@ -44,21 +39,11 @@ namespace Bootstrap.Admin.Components /// protected override void OnAfterRender(bool firstRender) { - if (firstRender) RootLayout.JSRuntime.EnableAnimation(); - var requestUrl = RootLayout.NavigationManager?.Uri ?? ""; var path = new Uri(requestUrl).PathAndQuery; var menus = DataAccess.MenuHelper.RetrieveAllMenus(RootLayout.UserName); var menu = menus.FirstOrDefault(menu => path.Contains(menu.Url.ToBlazorMenuUrl())); if (menu != null) TabSet?.AddTab(menu); } - - /// - /// - /// - /// - /// - /// - public void ShowMessage(string title, string text, ToastCategory cate = ToastCategory.Success) => Toast.ShowMessage(title, text, cate); } } diff --git a/src/admin/Bootstrap.Admin/Components/BootstrapAdminDataAnnotationsValidator.cs b/src/admin/Bootstrap.Admin/Components/BootstrapAdminDataAnnotationsValidator.cs index c2fcdf3e..7aafc043 100644 --- a/src/admin/Bootstrap.Admin/Components/BootstrapAdminDataAnnotationsValidator.cs +++ b/src/admin/Bootstrap.Admin/Components/BootstrapAdminDataAnnotationsValidator.cs @@ -28,16 +28,12 @@ namespace Bootstrap.Admin.Components { if (CurrentEditContext == null) { - throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading " + - $"parameter of type {nameof(EditContext)}. For example, you can use {nameof(DataAnnotationsValidator)} " + - $"inside an EditForm."); + throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading parameter of type {nameof(EditContext)}. For example, you can use {nameof(DataAnnotationsValidator)} inside an EditForm."); } if (EditForm == null) { - throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading " + - $"parameter of type {nameof(LgbEditFormBase)}. For example, you can use {nameof(BootstrapAdminDataAnnotationsValidator)} " + - $"inside an EditForm."); + throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading parameter of type {nameof(LgbEditFormBase)}. For example, you can use {nameof(BootstrapAdminDataAnnotationsValidator)} inside an EditForm."); } CurrentEditContext.AddBootstrapAdminDataAnnotationsValidation(EditForm); diff --git a/src/admin/Bootstrap.Admin/Components/BootstrapComponentBase.cs b/src/admin/Bootstrap.Admin/Components/BootstrapComponentBase.cs index ea9acf9f..cad1a8df 100644 --- a/src/admin/Bootstrap.Admin/Components/BootstrapComponentBase.cs +++ b/src/admin/Bootstrap.Admin/Components/BootstrapComponentBase.cs @@ -9,11 +9,6 @@ namespace Bootstrap.Admin.Components /// public class BootstrapComponentBase : ComponentBase { - /// - /// - /// - [CascadingParameter(Name = "Admin")] - protected AdminLayout Layout { get; set; } = new AdminLayout(); /// /// @@ -26,13 +21,10 @@ namespace Bootstrap.Admin.Components /// [Inject] protected IJSRuntime? JSRuntime { get; set; } - /// /// /// - /// - /// - /// - protected void ShowMessage(string title, string text, ToastCategory cate = ToastCategory.Success) => Layout.ShowMessage(title, text, cate); + [CascadingParameter(Name = "Admin")] + protected AdminLayout Layout { get; set; } = new AdminLayout(); } } diff --git a/src/admin/Bootstrap.Admin/Components/DefaultLayoutComponentBase.cs b/src/admin/Bootstrap.Admin/Components/DefaultLayoutComponentBase.cs index 44558682..47ef15c8 100644 --- a/src/admin/Bootstrap.Admin/Components/DefaultLayoutComponentBase.cs +++ b/src/admin/Bootstrap.Admin/Components/DefaultLayoutComponentBase.cs @@ -39,16 +39,6 @@ namespace Bootstrap.Admin.Components /// public NavigatorBarModel Model { get; set; } = new NavigatorBarModel(""); - /// - /// - /// - public SideBar? SideBar { get; set; } - - /// - /// - /// - public Header? Header { get; set; } - /// /// /// @@ -95,23 +85,22 @@ namespace Bootstrap.Admin.Components } } + /// + /// 设置参数方法 + /// + protected override void OnParametersSet() + { + RequestUrl = new UriBuilder(NavigationManager?.Uri ?? "").Path; + Model = new NavigatorBarModel(UserName, RequestUrl.ToMvcMenuUrl()); + } + /// /// /// /// protected override void OnAfterRender(bool firstRender) { - if (!firstRender) ResetSideBar(); - } - - /// - /// 更新侧边栏方法 - /// - public void ResetSideBar() - { - RequestUrl = new UriBuilder(NavigationManager?.Uri ?? "").Path; - Model = new NavigatorBarModel(UserName, RequestUrl.ToMvcMenuUrl()); - SideBar?.Update(Model); + if (firstRender) JSRuntime.InitDocument(); } } } diff --git a/src/admin/Bootstrap.Admin/Components/DictsBase.cs b/src/admin/Bootstrap.Admin/Components/DictsBase.cs index 3f13a848..0b98fa7b 100644 --- a/src/admin/Bootstrap.Admin/Components/DictsBase.cs +++ b/src/admin/Bootstrap.Admin/Components/DictsBase.cs @@ -59,5 +59,11 @@ namespace Bootstrap.Admin.Components /// /// protected bool Delete(IEnumerable items) => DataAccess.DictHelper.Delete(items.Select(item => item.Id ?? "")); + + /// + /// + /// + /// + protected override bool ShouldRender() => false; } } diff --git a/src/admin/Bootstrap.Admin/Components/EditPageBase.cs b/src/admin/Bootstrap.Admin/Components/EditPageBase.cs index ff7951ad..7c7a3fed 100644 --- a/src/admin/Bootstrap.Admin/Components/EditPageBase.cs +++ b/src/admin/Bootstrap.Admin/Components/EditPageBase.cs @@ -8,7 +8,7 @@ namespace Bootstrap.Admin.Components /// /// 可编辑页面组件包含查询与数据表格 /// - public class EditPageBase : BootstrapComponentBase + public class EditPageBase : ComponentBase { /// /// diff --git a/src/admin/Bootstrap.Admin/Components/LgbEditFormBase.cs b/src/admin/Bootstrap.Admin/Components/LgbEditFormBase.cs index fa7e785d..f1fdab90 100644 --- a/src/admin/Bootstrap.Admin/Components/LgbEditFormBase.cs +++ b/src/admin/Bootstrap.Admin/Components/LgbEditFormBase.cs @@ -12,6 +12,12 @@ namespace Bootstrap.Admin.Components /// public class LgbEditFormBase : ComponentBase { + /// + /// + /// + [Parameter] + public string Id { get; set; } = ""; + /// /// Gets or sets a collection of additional attributes that will be applied to the created form element. /// @@ -54,14 +60,14 @@ namespace Bootstrap.Admin.Components /// /// 验证组件缓存 静态全局提高性能 /// - private static ConcurrentDictionary<(Type ModelType, string FieldName), IValidateComponent> _validatorCache = new ConcurrentDictionary<(Type, string), IValidateComponent>(); + private static ConcurrentDictionary<(LgbEditFormBase EditForm, Type ModelType, string FieldName), IValidateComponent> _validatorCache = new ConcurrentDictionary<(LgbEditFormBase, Type, string), IValidateComponent>(); /// /// 添加数据验证组件到 EditForm 中 /// /// /// - public void AddValidator((Type ModelType, string FieldName) key, IValidateComponent comp) => _validatorCache.AddOrUpdate(key, k => comp, (k, c) => c = comp); + public void AddValidator((LgbEditFormBase EditForm, Type ModelType, string FieldName) key, IValidateComponent comp) => _validatorCache.AddOrUpdate(key, k => comp, (k, c) => c = comp); /// /// EditModel 数据模型验证方法 @@ -74,7 +80,7 @@ namespace Bootstrap.Admin.Components // 遍历所有可验证组件进行数据验证 foreach (var key in _validatorCache) { - if (key.Key.ModelType == context.ObjectType) + if (key.Key.EditForm == this && key.Key.ModelType == context.ObjectType) { if (BootstrapAdminEditContextDataAnnotationsExtensions.TryGetValidatableProperty(new FieldIdentifier(model, key.Key.FieldName), out var propertyInfo)) { @@ -100,20 +106,11 @@ namespace Bootstrap.Admin.Components /// public void ValidateProperty(object? propertyValue, ValidationContext context, List results) { - if (_validatorCache.TryGetValue((context.ObjectType, context.MemberName), out var validator)) + if (_validatorCache.TryGetValue((this, context.ObjectType, context.MemberName), out var validator)) { validator.ValidateProperty(propertyValue, context, results); validator.ToggleMessage(results, true); } } - - /// - /// - /// - /// - protected override void OnAfterRender(bool firstRender) - { - base.OnAfterRender(firstRender); - } } } diff --git a/src/admin/Bootstrap.Admin/Components/LgbInputText.cs b/src/admin/Bootstrap.Admin/Components/LgbInputText.cs deleted file mode 100644 index f908a77e..00000000 --- a/src/admin/Bootstrap.Admin/Components/LgbInputText.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Bootstrap.Admin.Extensions; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components.Rendering; -using Microsoft.JSInterop; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; - -namespace Bootstrap.Admin.Components -{ - /// - /// LgbInputText 组件 - /// - public class LgbInputText : InputText, IValidateComponent - { - /// - /// - /// - [Inject] - protected IJSRuntime? JSRuntime { get; set; } - - /// - /// - /// - [CascadingParameter] - public LgbEditFormBase? EditForm { get; set; } - - /// - /// - /// - [Parameter] - public string Id { get; set; } = ""; - - /// - /// - /// - [Parameter] - public RenderFragment? ChildContent { get; set; } - - /// - /// - /// - protected string ErrorMessage { get; set; } = ""; - - /// - /// - /// - protected string ValidCss { get; set; } = ""; - - private string _tooltipMethod = ""; - /// - /// - /// - /// - /// - /// - public void ValidateProperty(object? propertyValue, ValidationContext context, List results) - { - Rules.ToList().ForEach(validator => validator.Validate(propertyValue, context, results)); - } - - /// - /// 显示/隐藏验证结果方法 - /// - /// - /// 是否对本属性进行数据验证 - public void ToggleMessage(IEnumerable results, bool validProperty) - { - if (results.Where(r => r.MemberNames.Any(m => m == FieldIdentifier.FieldName)).Any()) - { - ErrorMessage = results.First().ErrorMessage; - ValidCss = "is-invalid"; - - // 控件自身数据验证时显示 tooltip - // EditForm 数据验证时调用 tooltip('enable') 保证 tooltip 组件生成 - // 调用 tooltip('hide') 后导致鼠标悬停时 tooltip 无法正常显示 - _tooltipMethod = validProperty ? "show" : "enable"; - } - else - { - ErrorMessage = ""; - ValidCss = "is-valid"; - _tooltipMethod = "dispose"; - } - } - - /// - /// - /// - protected override void OnInitialized() - { - EditForm?.AddValidator((FieldIdentifier.Model.GetType(), FieldIdentifier.FieldName), this); - } - - /// - /// - /// - /// - protected override void OnAfterRender(bool firstRender) - { - if (!string.IsNullOrEmpty(_tooltipMethod) && !string.IsNullOrEmpty(Id)) - { - JSRuntime.Tooltip(Id, _tooltipMethod); - _tooltipMethod = ""; - } - } - - /// - /// - /// - public ICollection Rules { get; } = new HashSet(); - - /// - /// - /// - /// - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - builder.OpenElement(0, "input"); - builder.AddMultipleAttributes(1, AdditionalAttributes); - builder.AddAttribute(2, "class", $"{CssClass} {ValidCss}"); - builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValue)); - builder.AddAttribute(4, "oninput", EventCallback.Factory.CreateBinder(this, __value => CurrentValueAsString = __value, CurrentValueAsString)); - if (!string.IsNullOrEmpty(ErrorMessage)) builder.AddAttribute(5, "data-original-title", ErrorMessage); - if (!string.IsNullOrEmpty(Id)) builder.AddAttribute(6, "id", Id); - builder.OpenComponent>(7); - builder.AddAttribute(8, "IsFixed", true); - builder.AddAttribute(9, "Value", this); - builder.AddAttribute(10, "ChildContent", ChildContent); - builder.CloseComponent(); - builder.CloseElement(); - } - } -} diff --git a/src/admin/Bootstrap.Admin/Components/LgbInputTextBase.cs b/src/admin/Bootstrap.Admin/Components/LgbInputTextBase.cs new file mode 100644 index 00000000..1e00c0d4 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/LgbInputTextBase.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; + +namespace Bootstrap.Admin.Components +{ + /// + /// LgbInputText 组件 + /// + public class LgbInputTextBase : ValidateInputBase + { + /// + /// + /// + /// + /// + /// + /// + protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage) + { + result = value; + validationErrorMessage = ""; + return true; + } + + /// + /// + /// + protected int? MaxLength + { + get + { + if (Rules.Count == 0 && + AdditionalAttributes != null && + AdditionalAttributes.TryGetValue("maxlength", out var maxlength) && + int.TryParse(Convert.ToString(maxlength), out int ml)) + { + return ml; + } + return (Rules.FirstOrDefault(r => r is StringLengthValidator) as StringLengthValidator)?.Length; + } + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/PaginationBase.cs b/src/admin/Bootstrap.Admin/Components/PaginationBase.cs index 8fd977fe..dcf1e03c 100644 --- a/src/admin/Bootstrap.Admin/Components/PaginationBase.cs +++ b/src/admin/Bootstrap.Admin/Components/PaginationBase.cs @@ -56,6 +56,7 @@ namespace Bootstrap.Admin.Components protected void MovePrev() { if (PageIndex > 1) OnPageClick(PageIndex - 1, PageItems); + else OnPageClick(PageCount, PageItems); } /// @@ -64,6 +65,7 @@ namespace Bootstrap.Admin.Components protected void MoveNext() { if (PageIndex < PageCount) OnPageClick(PageIndex + 1, PageItems); + else OnPageClick(1, PageItems); } /// @@ -95,14 +97,21 @@ namespace Bootstrap.Admin.Components /// /// /// - /// - /// - /// - public void Update(int totalCount, int pageIndex, int pageItems) + protected int StartPageIndex { get; set; } + + /// + /// + /// + protected int EndPageIndex { get; set; } + + /// + /// + /// + protected override void OnParametersSet() { - TotalCount = totalCount; - PageIndex = pageIndex; - PageItems = pageItems; + // 计算 分页开始页码与结束页码 + StartPageIndex = Math.Max(1, PageIndex - 4); + EndPageIndex = Math.Min(PageCount, PageIndex + 5); } } } diff --git a/src/admin/Bootstrap.Admin/Components/QueryBase.cs b/src/admin/Bootstrap.Admin/Components/QueryBase.cs index 1e46e068..413b2bf6 100644 --- a/src/admin/Bootstrap.Admin/Components/QueryBase.cs +++ b/src/admin/Bootstrap.Admin/Components/QueryBase.cs @@ -11,6 +11,12 @@ namespace Bootstrap.Admin.Components private readonly string _defaultTitle = "查询条件"; private readonly string _defaultText = "查询"; + /// + /// + /// + [Parameter] + public string Id { get; set; } = ""; + /// /// 查询组件标题 默认为 查询条件 /// @@ -37,12 +43,6 @@ namespace Bootstrap.Admin.Components public TItem QueryModel { get; set; } #nullable restore - /// - /// - /// - [Parameter] - public EventCallback QueryModelChanged { get; set; } - /// /// 查询按钮回调方法 /// @@ -57,5 +57,7 @@ namespace Bootstrap.Admin.Components if (string.IsNullOrEmpty(Title)) Title = _defaultTitle; if (string.IsNullOrEmpty(Text)) Text = _defaultText; } + + protected override void OnAfterRender(bool firstRender) => base.OnAfterRender(firstRender); } } diff --git a/src/admin/Bootstrap.Admin/Components/QueryInputTextBase.cs b/src/admin/Bootstrap.Admin/Components/QueryInputTextBase.cs new file mode 100644 index 00000000..527e5c05 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/QueryInputTextBase.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Components; + +namespace Bootstrap.Admin.Components +{ + /// + /// + /// + public class QueryInputTextBase : LgbInputTextBase + { + + } +} diff --git a/src/admin/Bootstrap.Admin/Components/RequiredValidator.cs b/src/admin/Bootstrap.Admin/Components/RequiredValidator.cs index e2e5907b..6b7308b4 100644 --- a/src/admin/Bootstrap.Admin/Components/RequiredValidator.cs +++ b/src/admin/Bootstrap.Admin/Components/RequiredValidator.cs @@ -9,11 +9,19 @@ namespace Bootstrap.Admin.Components /// public class RequiredValidator : ValidatorComponentBase { + /// + /// + /// + public RequiredValidator() + { + ErrorMessage = "这是必填字段"; + } + /// /// 获得/设置 是否允许空字符串 默认 false 不允许 /// [Parameter] - public bool AllowEmptyString { get; set; } = false; + public bool AllowEmptyString { get; set; } /// /// diff --git a/src/admin/Bootstrap.Admin/Components/SelectBase.cs b/src/admin/Bootstrap.Admin/Components/SelectBase.cs index 89ce3eb8..2830231a 100644 --- a/src/admin/Bootstrap.Admin/Components/SelectBase.cs +++ b/src/admin/Bootstrap.Admin/Components/SelectBase.cs @@ -8,20 +8,8 @@ namespace Bootstrap.Admin.Components /// /// Select 组件基类 /// - public class SelectBase : ComponentBase + public class SelectBase : ValidateInputBase { - /// - /// 获得/设置 控件 ID - /// - [Parameter] - public string Id { get; set; } = ""; - - /// - /// 获得/设置 背景显示文字 - /// - [Parameter] - public string PlaceHolder { get; set; } = "请选择 ..."; - /// /// 当前选择项实例 /// @@ -33,38 +21,24 @@ namespace Bootstrap.Admin.Components [Parameter] public List Items { get; set; } = new List(); -#nullable disable - private TItem _value; /// /// /// - [Parameter] - public TItem SelectedValue + protected override void OnParametersSet() { - get { return _value; } - set + Items.ForEach(t => { - _value = value; - Items.ForEach(t => - { - t.Active = t.Value == _value.ToString(); - if (t.Active) SelectedItem = t; - }); - } + t.Active = t.Value == Value?.ToString(); + if (t.Active) SelectedItem = t; + }); } -#nullable restore - - /// - /// - /// - [Parameter] - public EventCallback SelectedValueChanged { get; set; } /// /// /// protected override void OnInitialized() { + base.OnInitialized(); if (!SelectedItem.Active) { SelectedItem = Items.FirstOrDefault(item => item.Active) ?? Items.First(); @@ -75,16 +49,15 @@ namespace Bootstrap.Admin.Components /// /// [Parameter] - public Action SelectedItemChanged { get; set; } = new Action(v => { }); + public Action? SelectedItemChanged { get; set; } /// /// /// public void ItemClickCallback(SelectedItem item) { - SelectedItemChanged(item); SelectedItem = item; - StateHasChanged(); + CurrentValueAsString = item.Value; } } } diff --git a/src/admin/Bootstrap.Admin/Components/SideBarBase.cs b/src/admin/Bootstrap.Admin/Components/SideBarBase.cs index ce48cf72..03921d0b 100644 --- a/src/admin/Bootstrap.Admin/Components/SideBarBase.cs +++ b/src/admin/Bootstrap.Admin/Components/SideBarBase.cs @@ -15,12 +15,9 @@ namespace Bootstrap.Admin.Components public NavigatorBarModel Model { get; set; } = new NavigatorBarModel(""); /// - /// 视图更新方法 + /// 侧边栏绑定 Model 改变事件 /// - public void Update(NavigatorBarModel model) - { - Model = model; - StateHasChanged(); - } + [Parameter] + public EventCallback ModelChanged { get; set; } } } diff --git a/src/admin/Bootstrap.Admin/Components/StringLengthValidator.cs b/src/admin/Bootstrap.Admin/Components/StringLengthValidator.cs index d2c1b971..14adc8ee 100644 --- a/src/admin/Bootstrap.Admin/Components/StringLengthValidator.cs +++ b/src/admin/Bootstrap.Admin/Components/StringLengthValidator.cs @@ -9,11 +9,20 @@ namespace Bootstrap.Admin.Components /// public class StringLengthValidator : ValidatorComponentBase { + private int _length = 50; /// /// /// [Parameter] - public int Length { get; set; } + public int Length + { + get { return _length; } + set + { + _length = value; + ErrorMessage = $"不可为空,{_length}字以内"; + } + } /// /// diff --git a/src/admin/Bootstrap.Admin/Components/TableBase.cs b/src/admin/Bootstrap.Admin/Components/TableBase.cs index 04ce2f23..817ab362 100644 --- a/src/admin/Bootstrap.Admin/Components/TableBase.cs +++ b/src/admin/Bootstrap.Admin/Components/TableBase.cs @@ -10,7 +10,7 @@ namespace Bootstrap.Admin.Components /// /// 表格组件类 /// - public class TableBase : BootstrapComponentBase + public class TableBase : ComponentBase { /// /// @@ -245,6 +245,19 @@ namespace Bootstrap.Admin.Components EditModal?.Toggle(); } + /// + /// Toast 组件实例 + /// + protected Toast? Toast { get; set; } + + /// + /// + /// + /// + /// + /// + protected void ShowMessage(string title, string text, ToastCategory cate = ToastCategory.Success) => Toast?.ShowMessage(title, text, cate); + /// /// /// @@ -322,5 +335,7 @@ namespace Bootstrap.Admin.Components } ShowMessage("删除数据", "删除数据" + (result ? "成功" : "失败"), result ? ToastCategory.Success : ToastCategory.Error); } + + protected override void OnAfterRender(bool firstRender) => base.OnAfterRender(firstRender); } } diff --git a/src/admin/Bootstrap.Admin/Components/ValidateInputBase.cs b/src/admin/Bootstrap.Admin/Components/ValidateInputBase.cs new file mode 100644 index 00000000..f38b61eb --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/ValidateInputBase.cs @@ -0,0 +1,187 @@ +using Bootstrap.Admin.Extensions; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Linq; + +namespace Bootstrap.Admin.Components +{ + /// + /// + /// + public abstract class ValidateInputBase : InputBase, IValidateComponent + { + /// + /// + /// + [Inject] + protected IJSRuntime? JSRuntime { get; set; } + + /// + /// + /// + [CascadingParameter] + public LgbEditFormBase? EditForm { get; set; } + + /// + /// + /// + public string Id + { + get { return $"{EditForm?.Id}_{FieldIdentifier.FieldName}"; } + } + + /// + /// + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// + /// + protected string? PlaceHolder + { + get + { + if (AdditionalAttributes != null && + AdditionalAttributes.TryGetValue("placeholder", out var ph) && + !string.IsNullOrEmpty(Convert.ToString(ph))) + { + return ph.ToString(); + } + return null; + } + } + + /// + /// + /// + protected string ErrorMessage { get; set; } = ""; + + /// + /// + /// + protected string ValidCss { get; set; } = ""; + + /// + /// + /// + protected string DisplayName { get; set; } = "-"; + + /// + /// + /// + protected override void OnInitialized() + { + EditForm?.AddValidator((EditForm, FieldIdentifier.Model.GetType(), FieldIdentifier.FieldName), this); + DisplayName = FieldIdentifier.GetDisplayName(); + } + + /// + /// + /// + /// + protected override void OnAfterRender(bool firstRender) + { + if (!string.IsNullOrEmpty(_tooltipMethod) && !string.IsNullOrEmpty(Id)) + { + JSRuntime.Tooltip(Id, _tooltipMethod); + _tooltipMethod = ""; + } + } + + /// + /// + /// + public ICollection Rules { get; } = new HashSet(); + + private string _tooltipMethod = ""; + /// + /// + /// + /// + /// + /// + public void ValidateProperty(object? propertyValue, ValidationContext context, List results) + { + Rules.ToList().ForEach(validator => validator.Validate(propertyValue, context, results)); + } + + /// + /// 显示/隐藏验证结果方法 + /// + /// + /// 是否对本属性进行数据验证 + public void ToggleMessage(IEnumerable results, bool validProperty) + { + if (Rules.Any()) + { + var messages = results.Where(item => item.MemberNames.Any(m => m == FieldIdentifier.FieldName)); + if (messages.Any()) + { + ErrorMessage = messages.First().ErrorMessage; + ValidCss = "is-invalid"; + + // 控件自身数据验证时显示 tooltip + // EditForm 数据验证时调用 tooltip('enable') 保证 tooltip 组件生成 + // 调用 tooltip('hide') 后导致鼠标悬停时 tooltip 无法正常显示 + _tooltipMethod = validProperty ? "show" : "enable"; + } + else + { + ErrorMessage = ""; + ValidCss = "is-valid"; + _tooltipMethod = "dispose"; + } + } + } + + /// + /// + /// + /// + /// + /// + /// + protected override bool TryParseValueFromString(string value, out TItem result, out string? validationErrorMessage) + { + if (typeof(TItem) == typeof(string)) + { + result = (TItem)(object)value; + validationErrorMessage = null; + return true; + } + else if (typeof(TItem).IsEnum) + { + var success = BindConverter.TryConvertTo(value, CultureInfo.CurrentCulture, out var parsedValue); + if (success) + { + result = parsedValue; + validationErrorMessage = null; + return true; + } + else + { +#nullable disable + result = default; +#nullable restore + validationErrorMessage = $"The {FieldIdentifier.FieldName} field is not valid."; + return false; + } + } + else if (typeof(TItem).IsValueType) + { + result = (TItem)Convert.ChangeType(value, typeof(TItem)); + validationErrorMessage = null; + return true; + } + + throw new InvalidOperationException($"{GetType()} does not support the type '{typeof(TItem)}'."); + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/ValidatorComponentBase.cs b/src/admin/Bootstrap.Admin/Components/ValidatorComponentBase.cs index 58946c55..a1528270 100644 --- a/src/admin/Bootstrap.Admin/Components/ValidatorComponentBase.cs +++ b/src/admin/Bootstrap.Admin/Components/ValidatorComponentBase.cs @@ -20,7 +20,7 @@ namespace Bootstrap.Admin.Components /// /// [CascadingParameter] - public LgbInputText? Input { get; set; } + public LgbInputTextBase? Input { get; set; } /// /// 初始化方法 @@ -30,7 +30,7 @@ namespace Bootstrap.Admin.Components if (Input == null) { throw new InvalidOperationException($"{nameof(ValidatorComponentBase)} requires a cascading " + - $"parameter of type {nameof(LgbInputText)}. For example, you can use {nameof(ValidatorComponentBase)} " + + $"parameter of type {nameof(LgbInputTextBase)}. For example, you can use {nameof(ValidatorComponentBase)} " + $"inside an LgbInputText."); } diff --git a/src/admin/Bootstrap.Admin/Extensions/DisplayNamesExtensions.cs b/src/admin/Bootstrap.Admin/Extensions/DisplayNamesExtensions.cs new file mode 100644 index 00000000..e75e64ba --- /dev/null +++ b/src/admin/Bootstrap.Admin/Extensions/DisplayNamesExtensions.cs @@ -0,0 +1,46 @@ +using Bootstrap.Security; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Concurrent; + +namespace Microsoft.AspNetCore.Builder +{ + /// + /// + /// + public static class DisplayNamesExtensions + { + + private static ConcurrentDictionary<(Type ModelType, string FieldName), string> _displayNameCache = new ConcurrentDictionary<(Type, string), string>(); + + /// + /// 向系统中加入实体类显示名称字典 + /// + /// + /// + public static IServiceCollection AddDisplayNames(this IServiceCollection services) + { + _displayNameCache.TryAdd((typeof(BootstrapDict), nameof(BootstrapDict.Category)), "字典标签"); + _displayNameCache.TryAdd((typeof(BootstrapDict), nameof(BootstrapDict.Name)), "字典名称"); + _displayNameCache.TryAdd((typeof(BootstrapDict), nameof(BootstrapDict.Code)), "字典代码"); + _displayNameCache.TryAdd((typeof(BootstrapDict), nameof(BootstrapDict.Define)), "字典类型"); + return services; + } + + /// + /// + /// + /// + /// + /// + public static bool TryGetValue((Type ModelType, string FieldName) cacheKey, out string? displayName) => _displayNameCache.TryGetValue(cacheKey, out displayName); + + /// + /// + /// + /// + /// + /// + public static string GetOrAdd((Type ModelType, string FieldName) cacheKey, Func<(Type, string), string> valueFactory) => _displayNameCache.GetOrAdd(cacheKey, valueFactory); + } +} diff --git a/src/admin/Bootstrap.Admin/Extensions/FieldIdentifierExtensions.cs b/src/admin/Bootstrap.Admin/Extensions/FieldIdentifierExtensions.cs new file mode 100644 index 00000000..b6713818 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Extensions/FieldIdentifierExtensions.cs @@ -0,0 +1,38 @@ +using Bootstrap.Admin.Components; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Components.Forms; +using System.ComponentModel; + +namespace Bootstrap.Admin.Extensions +{ + /// + /// + /// + public static class FieldIdentifierExtensions + { + /// + /// + /// + /// + /// + public static string GetDisplayName(this FieldIdentifier fieldIdentifier) + { + var cacheKey = (fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName); + if (!DisplayNamesExtensions.TryGetValue(cacheKey, out var dn)) + { + if (BootstrapAdminEditContextDataAnnotationsExtensions.TryGetValidatableProperty(fieldIdentifier, out var propertyInfo)) + { + var displayNameAttribute = propertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true); + if (displayNameAttribute.Length > 0) + { + dn = ((DisplayNameAttribute)displayNameAttribute[0]).DisplayName; + + // add display name into cache + DisplayNamesExtensions.GetOrAdd((fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName), key => dn); + } + } + } + return dn ?? "未设置"; + } + } +} diff --git a/src/admin/Bootstrap.Admin/Extensions/JSRuntimeExtensions.cs b/src/admin/Bootstrap.Admin/Extensions/JSRuntimeExtensions.cs index 3ab6eb1b..9f564768 100644 --- a/src/admin/Bootstrap.Admin/Extensions/JSRuntimeExtensions.cs +++ b/src/admin/Bootstrap.Admin/Extensions/JSRuntimeExtensions.cs @@ -45,7 +45,7 @@ namespace Bootstrap.Admin.Extensions /// 启用动画 /// /// - public static void EnableAnimation(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.initDocument"); + public static void InitDocument(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.initDocument"); /// /// 修复 Modal 组件 diff --git a/src/admin/Bootstrap.Admin/Pages/Admin/Dicts.razor b/src/admin/Bootstrap.Admin/Pages/Admin/Dicts.razor index 3367aa90..76b4b623 100644 --- a/src/admin/Bootstrap.Admin/Pages/Admin/Dicts.razor +++ b/src/admin/Bootstrap.Admin/Pages/Admin/Dicts.razor @@ -2,18 +2,9 @@ -
- - -
-
- - -
-
- - -
+ + - -
- - - - - -
-
- - -
+ + + + + + + + + + + + +
diff --git a/src/admin/Bootstrap.Admin/Shared/AdminLayout.razor b/src/admin/Bootstrap.Admin/Shared/AdminLayout.razor index b79862f8..8b7e3939 100644 --- a/src/admin/Bootstrap.Admin/Shared/AdminLayout.razor +++ b/src/admin/Bootstrap.Admin/Shared/AdminLayout.razor @@ -5,5 +5,4 @@
- diff --git a/src/admin/Bootstrap.Admin/Shared/DefaultLayout.razor b/src/admin/Bootstrap.Admin/Shared/DefaultLayout.razor index 0b62ac6e..66f77fa8 100644 --- a/src/admin/Bootstrap.Admin/Shared/DefaultLayout.razor +++ b/src/admin/Bootstrap.Admin/Shared/DefaultLayout.razor @@ -1,8 +1,8 @@ @inherits DefaultLayoutComponentBase -
- +
+ @Body
diff --git a/src/admin/Bootstrap.Admin/Shared/EditPage.razor b/src/admin/Bootstrap.Admin/Shared/EditPage.razor index da1b5c79..53c715c3 100644 --- a/src/admin/Bootstrap.Admin/Shared/EditPage.razor +++ b/src/admin/Bootstrap.Admin/Shared/EditPage.razor @@ -1,7 +1,7 @@ @typeparam TItem @inherits EditPageBase - + @QueryBody?.Invoke(context)
diff --git a/src/admin/Bootstrap.Admin/Shared/LgbInputText.razor b/src/admin/Bootstrap.Admin/Shared/LgbInputText.razor new file mode 100644 index 00000000..02de98c0 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Shared/LgbInputText.razor @@ -0,0 +1,9 @@ +@inherits LgbInputTextBase + +
+ + + + @ChildContent + +
diff --git a/src/admin/Bootstrap.Admin/Shared/Pagination.razor b/src/admin/Bootstrap.Admin/Shared/Pagination.razor index 8af3c6e4..bf3dd975 100644 --- a/src/admin/Bootstrap.Admin/Shared/Pagination.razor +++ b/src/admin/Bootstrap.Admin/Shared/Pagination.razor @@ -17,7 +17,7 @@
  • - @for (int i = 1; i <= PageCount; i++) + @for (int i = StartPageIndex; i <= EndPageIndex; i++) { } diff --git a/src/admin/Bootstrap.Admin/Shared/Query.razor b/src/admin/Bootstrap.Admin/Shared/Query.razor index 6f0c4da4..857eb630 100644 --- a/src/admin/Bootstrap.Admin/Shared/Query.razor +++ b/src/admin/Bootstrap.Admin/Shared/Query.razor @@ -4,13 +4,13 @@
    @Title
    -
    +
    @ChildContent?.Invoke(QueryModel)
    - +
    -
    +
    diff --git a/src/admin/Bootstrap.Admin/Shared/Select.razor b/src/admin/Bootstrap.Admin/Shared/Select.razor index 4cd98b2b..447ac43a 100644 --- a/src/admin/Bootstrap.Admin/Shared/Select.razor +++ b/src/admin/Bootstrap.Admin/Shared/Select.razor @@ -1,15 +1,18 @@ @typeparam TItem @inherits SelectBase -