From 93af3948be78153b613a0cf2bfc10fdd65aeb264 Mon Sep 17 00:00:00 2001 From: Argo Date: Tue, 3 Dec 2019 01:42:19 +0800 Subject: [PATCH] =?UTF-8?q?!49=20=E5=A2=9E=E5=8A=A0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=9ABlazor=20=E6=A1=86=E6=9E=B6=E4=B8=8B=E5=AD=97=E5=85=B8?= =?UTF-8?q?=E8=A1=A8=E7=BB=B4=E6=8A=A4=E5=8A=9F=E8=83=BD=E5=AE=8C=E6=88=90?= =?UTF-8?q?=20*=20feat:=20Query=20=E7=BB=84=E4=BB=B6=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20*=20refactor:=20Dicts=20=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=B8=8D=E7=94=A8=E7=9A=84=E5=BC=95=E7=94=A8?= =?UTF-8?q?=20*=20refactor:=20EditPage=20=E7=BB=84=E4=BB=B6=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=20Id=20=E4=BF=9D=E6=8A=A4=20*=20doc:=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=A8=8B=E5=BA=8F=E6=B3=A8=E9=87=8A=20*=20feat:=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=AA=8C=E8=AF=81=E6=8E=A7=E4=BB=B6=E6=89=A9?= =?UTF-8?q?=E5=B1=95=20*=20refactor:=20IValidateComponent=20=E6=9B=B4?= =?UTF-8?q?=E6=94=B9=E6=96=B9=E6=B3=95=E5=90=8D=20*=20refactor:=20?= =?UTF-8?q?=E9=87=8D=E6=9E=84=20checkbox=20=E9=80=89=E4=B8=AD=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=20*=20fix:=20=E6=9F=A5=E8=AF=A2=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E6=9D=A1=E4=BB=B6=E4=B8=8D=E7=94=9F=E6=95=88?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20*=20fix:=20=E4=BF=AE=E5=A4=8D=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=8C=BA=E5=9F=9F=E6=8C=89=E9=92=AE=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E4=B8=8D=E8=83=BD=E5=88=87=E6=8D=A2=E5=88=B0=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=97=AE=E9=A2=98=20*=20feat:=20=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E4=BF=9D=E5=AD=98=E5=8A=9F=E8=83=BD=20*=20refactor:?= =?UTF-8?q?=20Save=20=E6=96=B9=E6=B3=95=E5=8F=82=E6=95=B0=E6=9B=B4?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=AE=9E=E4=BD=93=E7=B1=BB=20*=20refactor:?= =?UTF-8?q?=20=E4=BC=98=E5=8C=96=E6=AF=8F=E9=A1=B5=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=AE=97=E6=B3=95=20*=20fix:=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=96=B0=E5=BB=BA=E6=95=B0=E6=8D=AE=E9=A1=B9=E6=97=B6?= =?UTF-8?q?=E6=B8=85=E9=99=A4=E5=B7=B2=E9=80=89=E6=95=B0=E6=8D=AE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20*=20feat:=20=E5=AE=8C=E5=96=84=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20*=20feat:=20=E6=9F=A5=E8=AF=A2=E5=90=8E?= =?UTF-8?q?=E6=B8=85=E6=A5=9A=E5=B7=B2=E9=80=89=E6=9D=A1=E7=9B=AE=20*=20fe?= =?UTF-8?q?at:=20=E4=BF=AE=E5=A4=8D=E7=BC=96=E8=BE=91=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20*=20feat:=20=E5=A2=9E=E5=8A=A0=E8=A1=8C?= =?UTF-8?q?=E5=8F=B7=E8=AE=BE=E7=BD=AE=20*=20refactor:=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E5=88=86=E9=A1=B5=E7=BB=84=E4=BB=B6=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=20Table=20=E7=BB=84=E4=BB=B6=E5=AF=B9=E5=85=B6=E5=BC=95?= =?UTF-8?q?=E7=94=A8=20*=20refactor:=20=E9=87=8D=E6=9E=84=20Dicts=20?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=BB=84=E4=BB=B6=E5=8C=96=20*=20feat:=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20Validate=20=E5=8A=9F=E8=83=BD=20*=20refact?= =?UTF-8?q?or:=20=E6=9F=A5=E8=AF=A2=E6=96=B9=E6=B3=95=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=8F=82=E6=95=B0=E5=80=BC=20*=20feat:=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=A0=E9=99=A4=E7=A1=AE=E8=AE=A4=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20*=20style:=20=E5=BE=AE=E8=B0=83=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E5=86=85=E6=8C=89=E9=92=AE=E9=AB=98=E5=BA=A6=20*=20style:=20?= =?UTF-8?q?=E5=BE=AE=E8=B0=83=20gear=20=E6=A0=B7=E5=BC=8F=20*=20fix:=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A1=A8=E6=A0=BC=E6=95=B0=E6=8D=AE=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=90=8E=E4=BB=8D=E7=84=B6=E5=8F=AF=E7=BC=96=E8=BE=91?= =?UTF-8?q?=20*=20feat:=20=E5=A2=9E=E5=8A=A0=20Table=20=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E5=86=85=E6=8C=89=E9=92=AE=E7=BB=84=20*=20refactor:=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=A0=E9=99=A4=E6=95=B0=E6=8D=AE=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E6=88=90=E5=8A=9F=E6=8F=90=E7=A4=BA=20*=20feat:=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20Toast=20=E7=BB=84=E4=BB=B6=20*=20feat:=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20Alert=20=E6=8E=A7=E4=BB=B6=20*=20feat:=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BF=9D=E5=AD=98=E5=8A=9F=E8=83=BD=20*=20fe?= =?UTF-8?q?at:=20=E8=A1=A8=E6=A0=BC=E9=A6=96=E5=88=97=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=B8=BA=20checkbox=20=E5=8A=9F=E8=83=BD=20*=20feat:=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=80=89=E6=8B=A9=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/AdminLayoutComponentBase.cs | 13 + .../Bootstrap.Admin/Components/AlertBase.cs | 61 ++++ .../BootstrapAdminDataAnnotationsValidator.cs | 46 +++ ...minEditContextDataAnnotationsExtensions.cs | 105 +++++++ .../Components/BootstrapComponentBase.cs | 8 + .../Components/CheckBoxState.cs | 49 +++ .../Components/CheckboxBase.cs | 79 +++++ src/admin/Bootstrap.Admin/Components/Color.cs | 67 +++++ .../Bootstrap.Admin/Components/DictsBase.cs | 63 ++++ .../Components/EditPageBase.cs | 153 ++++++++++ .../Components/IValidateComponent.cs | 26 ++ .../Components/LgbEditFormBase.cs | 119 ++++++++ .../Components/LgbInputText.cs | 135 +++++++++ .../Bootstrap.Admin/Components/ModalBase.cs | 91 +++++- .../Components/PaginationBase.cs | 76 +++-- .../Bootstrap.Admin/Components/Placement.cs | 84 ++++++ .../Bootstrap.Admin/Components/QueryBase.cs | 61 ++++ .../Bootstrap.Admin/Components/QueryData.cs | 31 ++ .../Components/RequiredValidator.cs | 33 ++ .../Bootstrap.Admin/Components/SelectBase.cs | 87 +++--- .../Components/SelectItemBase.cs | 40 +-- .../Components/SelectedItem.cs | 23 ++ .../Components/StringLengthValidator.cs | 30 ++ .../Components/SubmitModalBase.cs | 44 +++ .../Bootstrap.Admin/Components/TableBase.cs | 281 +++++++++++++++++- .../Bootstrap.Admin/Components/ToastBase.cs | 123 ++++++++ .../Components/ToastCategory.cs | 21 ++ .../Components/ValidatorComponentBase.cs | 48 +++ .../Extensions/JSRuntimeExtensions.cs | 22 +- .../Bootstrap.Admin/Pages/Admin/Dicts.razor | 227 ++++---------- src/admin/Bootstrap.Admin/Pages/_Host.cshtml | 9 +- .../Bootstrap.Admin/Shared/AdminLayout.razor | 1 + src/admin/Bootstrap.Admin/Shared/Alert.razor | 14 + .../Bootstrap.Admin/Shared/Checkbox.razor | 9 + .../Bootstrap.Admin/Shared/EditPage.razor | 31 ++ .../Bootstrap.Admin/Shared/LgbEditForm.razor | 13 + src/admin/Bootstrap.Admin/Shared/Modal.razor | 12 +- .../Bootstrap.Admin/Shared/Pagination.razor | 11 +- .../Shared/PaginationItem.razor | 4 +- src/admin/Bootstrap.Admin/Shared/Query.razor | 16 + src/admin/Bootstrap.Admin/Shared/Select.razor | 12 +- .../Bootstrap.Admin/Shared/SelectItem.razor | 3 +- .../Bootstrap.Admin/Shared/SubmitModal.razor | 20 ++ src/admin/Bootstrap.Admin/Shared/Table.razor | 93 ++++-- src/admin/Bootstrap.Admin/Shared/Toast.razor | 19 ++ .../Bootstrap.Admin/Tasks/TasksExtensions.cs | 1 - .../Bootstrap.Admin/wwwroot/css/blazor.css | 137 +++++++++ .../Bootstrap.Admin/wwwroot/css/theme.css | 1 - .../Bootstrap.Admin/wwwroot/js/ba.blazor.js | 22 +- .../lib/longbow-checkbox/longbow-checkbox.css | 16 +- 50 files changed, 2345 insertions(+), 345 deletions(-) create mode 100644 src/admin/Bootstrap.Admin/Components/AlertBase.cs create mode 100644 src/admin/Bootstrap.Admin/Components/BootstrapAdminDataAnnotationsValidator.cs create mode 100644 src/admin/Bootstrap.Admin/Components/BootstrapAdminEditContextDataAnnotationsExtensions.cs create mode 100644 src/admin/Bootstrap.Admin/Components/CheckBoxState.cs create mode 100644 src/admin/Bootstrap.Admin/Components/CheckboxBase.cs create mode 100644 src/admin/Bootstrap.Admin/Components/Color.cs create mode 100644 src/admin/Bootstrap.Admin/Components/DictsBase.cs create mode 100644 src/admin/Bootstrap.Admin/Components/EditPageBase.cs create mode 100644 src/admin/Bootstrap.Admin/Components/IValidateComponent.cs create mode 100644 src/admin/Bootstrap.Admin/Components/LgbEditFormBase.cs create mode 100644 src/admin/Bootstrap.Admin/Components/LgbInputText.cs create mode 100644 src/admin/Bootstrap.Admin/Components/Placement.cs create mode 100644 src/admin/Bootstrap.Admin/Components/QueryBase.cs create mode 100644 src/admin/Bootstrap.Admin/Components/QueryData.cs create mode 100644 src/admin/Bootstrap.Admin/Components/RequiredValidator.cs create mode 100644 src/admin/Bootstrap.Admin/Components/SelectedItem.cs create mode 100644 src/admin/Bootstrap.Admin/Components/StringLengthValidator.cs create mode 100644 src/admin/Bootstrap.Admin/Components/SubmitModalBase.cs create mode 100644 src/admin/Bootstrap.Admin/Components/ToastBase.cs create mode 100644 src/admin/Bootstrap.Admin/Components/ToastCategory.cs create mode 100644 src/admin/Bootstrap.Admin/Components/ValidatorComponentBase.cs create mode 100644 src/admin/Bootstrap.Admin/Shared/Alert.razor create mode 100644 src/admin/Bootstrap.Admin/Shared/Checkbox.razor create mode 100644 src/admin/Bootstrap.Admin/Shared/EditPage.razor create mode 100644 src/admin/Bootstrap.Admin/Shared/LgbEditForm.razor create mode 100644 src/admin/Bootstrap.Admin/Shared/Query.razor create mode 100644 src/admin/Bootstrap.Admin/Shared/SubmitModal.razor create mode 100644 src/admin/Bootstrap.Admin/Shared/Toast.razor diff --git a/src/admin/Bootstrap.Admin/Components/AdminLayoutComponentBase.cs b/src/admin/Bootstrap.Admin/Components/AdminLayoutComponentBase.cs index 3feffff7..5fa8b27d 100644 --- a/src/admin/Bootstrap.Admin/Components/AdminLayoutComponentBase.cs +++ b/src/admin/Bootstrap.Admin/Components/AdminLayoutComponentBase.cs @@ -22,6 +22,11 @@ namespace Bootstrap.Admin.Components [CascadingParameter(Name = "Default")] public DefaultLayout RootLayout { get; protected set; } = new DefaultLayout(); + /// + /// Toast 组件实例 + /// + protected Toast Toast { get; set; } = new Toast(); + /// /// /// @@ -47,5 +52,13 @@ namespace Bootstrap.Admin.Components 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/AlertBase.cs b/src/admin/Bootstrap.Admin/Components/AlertBase.cs new file mode 100644 index 00000000..19ad6a38 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/AlertBase.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Components; + +namespace Bootstrap.Admin.Components +{ + /// + /// 弹窗组件基类 + /// + public class AlertBase : ModalBase + { + /// + /// + /// + [Parameter] + public RenderFragment? AlertBody { get; set; } + + /// + /// 获得/设置 弹窗 Footer 代码块 + /// + [Parameter] + public RenderFragment? AlertFooter { get; set; } + + /// + /// 获得/设置 是否自动关闭 默认为 true + /// + [Parameter] + public bool AutoClose { get; set; } = true; + + /// + /// 获得/设置 自动关闭时长 默认 1500 毫秒 + /// + [Parameter] + public int Interval { get; set; } = 1500; + + /// + /// 控件渲染完毕后回调方法 + /// + /// + protected override void OnAfterRender(bool firstRender) + { + base.OnAfterRender(firstRender); + + if (_show) + { + _show = false; + Toggle(); + } + } + + private bool _show; + /// + /// + /// + /// + public void Show(string title) + { + Title = title; + _show = true; + StateHasChanged(); + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/BootstrapAdminDataAnnotationsValidator.cs b/src/admin/Bootstrap.Admin/Components/BootstrapAdminDataAnnotationsValidator.cs new file mode 100644 index 00000000..c2fcdf3e --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/BootstrapAdminDataAnnotationsValidator.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using System; + +namespace Bootstrap.Admin.Components +{ + /// + /// BootstrapAdminDataAnnotationsValidator 验证组件 + /// + public class BootstrapAdminDataAnnotationsValidator : ComponentBase + { + /// + /// 获得/设置 当前编辑数据上下文 + /// + [CascadingParameter] + EditContext? CurrentEditContext { get; set; } + + /// + /// 获得/设置 当前编辑窗体上下文 + /// + [CascadingParameter] + public LgbEditFormBase? EditForm { get; set; } + + /// + /// 初始化方法 + /// + protected override void OnInitialized() + { + 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."); + } + + 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."); + } + + CurrentEditContext.AddBootstrapAdminDataAnnotationsValidation(EditForm); + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/BootstrapAdminEditContextDataAnnotationsExtensions.cs b/src/admin/Bootstrap.Admin/Components/BootstrapAdminEditContextDataAnnotationsExtensions.cs new file mode 100644 index 00000000..18f8fd2c --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/BootstrapAdminEditContextDataAnnotationsExtensions.cs @@ -0,0 +1,105 @@ +using Microsoft.AspNetCore.Components.Forms; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; + +namespace Bootstrap.Admin.Components +{ + /// + /// + /// + public static class BootstrapAdminEditContextDataAnnotationsExtensions + { + private static ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo> _propertyInfoCache = new ConcurrentDictionary<(Type, string), PropertyInfo>(); + + /// + /// + /// + /// The . + /// + public static EditContext AddBootstrapAdminDataAnnotationsValidation(this EditContext editContext, LgbEditFormBase editForm) + { + if (editContext == null) + { + throw new ArgumentNullException(nameof(editContext)); + } + + var messages = new ValidationMessageStore(editContext); + + editContext.OnValidationRequested += + (sender, eventArgs) => ValidateModel(sender as EditContext, messages, editForm); + + editContext.OnFieldChanged += + (sender, eventArgs) => ValidateField(editContext, messages, eventArgs.FieldIdentifier, editForm); + + return editContext; + } + + private static void ValidateModel(EditContext? editContext, ValidationMessageStore messages, LgbEditFormBase editForm) + { + if (editContext != null) + { + var validationContext = new ValidationContext(editContext.Model); + var validationResults = new List(); + Validator.TryValidateObject(editContext.Model, validationContext, validationResults, true); + editForm.ValidateObject(editContext.Model, validationContext, validationResults); + + messages.Clear(); + + foreach (var validationResult in validationResults) + { + if (!validationResult.MemberNames.Any()) + { + messages.Add(new FieldIdentifier(editContext.Model, fieldName: string.Empty), validationResult.ErrorMessage); + continue; + } + + foreach (var memberName in validationResult.MemberNames) + { + messages.Add(editContext.Field(memberName), validationResult.ErrorMessage); + } + } + editContext.NotifyValidationStateChanged(); + } + } + + private static void ValidateField(EditContext editContext, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier, LgbEditFormBase editForm) + { + if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo)) + { + var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model); + var validationContext = new ValidationContext(fieldIdentifier.Model) + { + MemberName = propertyInfo.Name + }; + var results = new List(); + + Validator.TryValidateProperty(propertyValue, validationContext, results); + editForm.ValidateProperty(propertyValue, validationContext, results); + + messages.Clear(fieldIdentifier); + messages.Add(fieldIdentifier, results.Select(result => result.ErrorMessage)); + + editContext.NotifyValidationStateChanged(); + } + } + +#nullable disable + internal static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier, out PropertyInfo propertyInfo) + { + var cacheKey = (ModelType: fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName); + if (!_propertyInfoCache.TryGetValue(cacheKey, out propertyInfo)) + { + propertyInfo = cacheKey.ModelType.GetProperty(cacheKey.FieldName); + + _propertyInfoCache[cacheKey] = propertyInfo; + } + + return propertyInfo != null; + } +#nullable restore + } +} diff --git a/src/admin/Bootstrap.Admin/Components/BootstrapComponentBase.cs b/src/admin/Bootstrap.Admin/Components/BootstrapComponentBase.cs index 9d40da16..ea9acf9f 100644 --- a/src/admin/Bootstrap.Admin/Components/BootstrapComponentBase.cs +++ b/src/admin/Bootstrap.Admin/Components/BootstrapComponentBase.cs @@ -26,5 +26,13 @@ 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); } } diff --git a/src/admin/Bootstrap.Admin/Components/CheckBoxState.cs b/src/admin/Bootstrap.Admin/Components/CheckBoxState.cs new file mode 100644 index 00000000..91d416bd --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/CheckBoxState.cs @@ -0,0 +1,49 @@ +namespace Bootstrap.Admin.Components +{ + /// + /// CheckBox 组件状态枚举值 + /// + public enum CheckBoxState + { + /// + /// 未选中 + /// + UnChecked, + /// + /// 选中 + /// + Checked, + /// + /// 混合模式 + /// + Mixed + } + + /// + /// + /// + public static class CheckBoxStateExtensions + { + /// + /// + /// + /// + /// + public static string ToCss(this CheckBoxState state) + { + var ret = "false"; + switch (state) + { + case CheckBoxState.Checked: + ret = "true"; + break; + case CheckBoxState.Mixed: + ret = "mixed"; + break; + case CheckBoxState.UnChecked: + break; + } + return ret; + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/CheckboxBase.cs b/src/admin/Bootstrap.Admin/Components/CheckboxBase.cs new file mode 100644 index 00000000..4390bf8b --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/CheckboxBase.cs @@ -0,0 +1,79 @@ +using Microsoft.AspNetCore.Components; +using System; + +namespace Bootstrap.Admin.Components +{ + /// + /// Checkbox 组件基类 + /// + /// + public class CheckboxBase : ComponentBase + { +#nullable disable + /// + /// 获得/设置 数据绑定项 + /// + [Parameter] + public TItem Item { get; set; } +#nullable restore + + /// + /// 获得/设置 显示文本 + /// + [Parameter] + public string Text { get; set; } = ""; + + /// + /// 获得/设置 是否被选中 + /// + protected bool Checked { get; set; } + + /// + /// 勾选回调方法 + /// + [Parameter] + public Action ToggleCallback { get; set; } = new Action((v, c) => { }); + + /// + /// + /// + [Parameter] + public Func SetCheckCallback { get; set; } = new Func(item => CheckBoxState.UnChecked); + + /// + /// + /// + protected override void OnParametersSet() + { + State = SetCheckCallback(Item); + Checked = State == CheckBoxState.Checked; + } + + /// + /// + /// + [Parameter] + public CheckBoxState State { get; set; } + + /// + /// + /// + /// + protected string RenderStateCss() + { + var ret = "form-checkbox"; + switch (State) + { + case CheckBoxState.Mixed: + ret = "form-checkbox is-indeterminate"; + break; + case CheckBoxState.Checked: + ret = "form-checkbox is-checked"; + break; + case CheckBoxState.UnChecked: + break; + } + return ret; + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/Color.cs b/src/admin/Bootstrap.Admin/Components/Color.cs new file mode 100644 index 00000000..b9bcbbd0 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/Color.cs @@ -0,0 +1,67 @@ +namespace Bootstrap.Admin.Components +{ + /// + /// + /// + public enum Color + { + /// + /// + /// + Primary, + /// + /// + /// + Secondary, + /// + /// + /// + Success, + /// + /// + /// + Danger, + /// + /// + /// + Warning, + /// + /// + /// + Info, + /// + /// + /// + Light, + /// + /// + /// + Dark, + /// + /// + /// + White, + /// + /// + /// + Transparent + } + + /// + /// + /// + public static class ColorExtensions + { + /// + /// + /// + /// + /// + /// + public static string ToCss(this Color color, string prefix = "") + { + string ret = color.ToString().ToLowerInvariant(); + return string.IsNullOrEmpty(prefix) ? ret : $"{prefix}-{ret}"; + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/DictsBase.cs b/src/admin/Bootstrap.Admin/Components/DictsBase.cs new file mode 100644 index 00000000..3f13a848 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/DictsBase.cs @@ -0,0 +1,63 @@ +using Bootstrap.Security; +using Microsoft.AspNetCore.Components; +using System.Collections.Generic; +using System.Linq; + +namespace Bootstrap.Admin.Components +{ + /// + /// 字典表维护组件 + /// + public class DictsBase : ComponentBase + { + /// + /// + /// + protected BootstrapDict QueryModel { get; set; } = new BootstrapDict() { Define = -1 }; + + /// + /// + /// + protected List DefineItems { get; set; } = new List(new SelectedItem[] { new SelectedItem() { Text = "系统使用", Value = "0" }, new SelectedItem() { Text = "自定义", Value = "1" } }); + + /// + /// + /// + protected List QueryDefine { get; set; } = new List(new SelectedItem[] { new SelectedItem() { Text = "全部", Value = "-1", Active = true }, new SelectedItem() { Text = "系统使用", Value = "0" }, new SelectedItem() { Text = "自定义", Value = "1" } }); + + /// + /// + /// + /// + /// + protected QueryData Query(int pageIndex, int pageItems) + { + var data = DataAccess.DictHelper.RetrieveDicts(); + if (QueryModel.Define != -1) data = data.Where(d => d.Define == QueryModel.Define); + if (QueryModel.Name != "") data = data.Where(d => d.Name == QueryModel.Name); + if (QueryModel.Category != "") data = data.Where(d => d.Category == QueryModel.Category); + var totalCount = data.Count(); + var items = data.Skip((pageIndex - 1) * pageItems).Take(pageItems); + return new QueryData() { Items = items, TotalCount = totalCount, PageIndex = pageIndex, PageItems = pageItems }; + } + + /// + /// + /// + /// + protected BootstrapDict Add() + { + return new BootstrapDict(); + } + + /// + /// + /// + protected bool Save(BootstrapDict dict) => DataAccess.DictHelper.Save(dict); + + /// + /// + /// + protected bool Delete(IEnumerable items) => DataAccess.DictHelper.Delete(items.Select(item => item.Id ?? "")); + } +} diff --git a/src/admin/Bootstrap.Admin/Components/EditPageBase.cs b/src/admin/Bootstrap.Admin/Components/EditPageBase.cs new file mode 100644 index 00000000..ff7951ad --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/EditPageBase.cs @@ -0,0 +1,153 @@ +using Bootstrap.Admin.Shared; +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; + +namespace Bootstrap.Admin.Components +{ + /// + /// 可编辑页面组件包含查询与数据表格 + /// + public class EditPageBase : BootstrapComponentBase + { + /// + /// + /// + [Parameter] + public string Id { get; set; } = ""; + +#nullable disable + /// + /// + /// + [Parameter] + public TItem QueryModel { get; set; } +#nullable restore + + /// + /// 查询模板 + /// + [Parameter] + public RenderFragment? QueryBody { get; set; } + + /// + /// 查询按钮回调方法 + /// + [Parameter] + public Func>? OnQuery { get; set; } + + /// + /// + /// + [Parameter] + public RenderFragment? TableHeader { get; set; } + + /// + /// + /// + [Parameter] + public RenderFragment? RowTemplate { get; set; } + + /// + /// 按钮模板 + /// + [Parameter] + public RenderFragment? ButtonTemplate { get; set; } + + /// + /// + /// + [Parameter] + public RenderFragment? TableFooter { get; set; } + + /// + /// + /// + [Parameter] + public RenderFragment? EditTemplate { get; set; } + + /// + /// + /// + protected Table? Table { get; set; } + + /// + /// 新建按钮回调方法 + /// + [Parameter] + public Func? OnAdd { get; set; } + + /// + /// 编辑按钮回调方法 + /// + [Parameter] + public Action? OnEdit { get; set; } + + /// + /// 保存按钮回调方法 + /// + [Parameter] + public Func? OnSave { get; set; } + + /// + /// 删除按钮回调方法 + /// + [Parameter] + public Func, bool>? OnDelete { get; set; } + + /// + /// 组件初始化方法 + /// + protected override void OnInitialized() + { + if (string.IsNullOrEmpty(Id)) throw new InvalidOperationException($"The property {nameof(Id)} can't set to Null"); + } + + /// + /// 数据表格内删除按钮方法 + /// + /// + protected void Delete(TItem item) + { + if (Table != null) + { + Table.SelectedItems.Clear(); + Table.SelectedItems.Add(item); + Table.Delete(); + } + } + + /// + /// + /// + protected void Edit(TItem item) + { + if (Table != null) + { + Table.SelectedItems.Clear(); + Table.SelectedItems.Add(item); + Table.Edit(); + } + } + + /// + /// + /// + protected void Query() + { + // 查询控件按钮触发此事件 + if (OnQuery != null && Table != null) + { + Table.Query(OnQuery.Invoke(1, Table.PageItems)); + } + } + + /// + /// + /// + /// + /// + /// + protected QueryData QueryData(int pageIndex, int pageItems) => OnQuery?.Invoke(pageIndex, pageItems) ?? new QueryData(); + } +} diff --git a/src/admin/Bootstrap.Admin/Components/IValidateComponent.cs b/src/admin/Bootstrap.Admin/Components/IValidateComponent.cs new file mode 100644 index 00000000..5f8e0e39 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/IValidateComponent.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Bootstrap.Admin.Components +{ + /// + /// IValidComponent 接口 + /// + public interface IValidateComponent + { + /// + /// 数据验证方法 + /// + /// + /// + /// + void ValidateProperty(object? propertyValue, ValidationContext context, List results); + + /// + /// 显示或者隐藏提示信息方法 + /// + /// + /// + void ToggleMessage(IEnumerable results, bool validProperty); + } +} diff --git a/src/admin/Bootstrap.Admin/Components/LgbEditFormBase.cs b/src/admin/Bootstrap.Admin/Components/LgbEditFormBase.cs new file mode 100644 index 00000000..fa7e785d --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/LgbEditFormBase.cs @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Bootstrap.Admin.Components +{ + /// + /// LgbEditForm 组件 + /// + public class LgbEditFormBase : ComponentBase + { + /// + /// Gets or sets a collection of additional attributes that will be applied to the created form element. + /// + [Parameter(CaptureUnmatchedValues = true)] + public IReadOnlyDictionary? AdditionalAttributes { get; set; } + + /// + /// Specifies the top-level model object for the form. An edit context will + /// be constructed for this model. If using this parameter, do not also supply + /// a value for . + /// + [Parameter] + public object? Model { get; set; } + + /// + /// Specifies the content to be rendered inside this + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// A callback that will be invoked when the form is submitted. + /// If using this parameter, you are responsible for triggering any validation + /// manually, e.g., by calling . + /// + [Parameter] public EventCallback OnSubmit { get; set; } + + /// + /// A callback that will be invoked when the form is submitted and the + /// is determined to be valid. + /// + [Parameter] public EventCallback OnValidSubmit { get; set; } + + /// + /// A callback that will be invoked when the form is submitted and the + /// is determined to be invalid. + /// + [Parameter] public EventCallback OnInvalidSubmit { get; set; } + + /// + /// 验证组件缓存 静态全局提高性能 + /// + private static ConcurrentDictionary<(Type ModelType, string FieldName), IValidateComponent> _validatorCache = new ConcurrentDictionary<(Type, string), IValidateComponent>(); + + /// + /// 添加数据验证组件到 EditForm 中 + /// + /// + /// + public void AddValidator((Type ModelType, string FieldName) key, IValidateComponent comp) => _validatorCache.AddOrUpdate(key, k => comp, (k, c) => c = comp); + + /// + /// EditModel 数据模型验证方法 + /// + /// + /// + /// + public void ValidateObject(object model, ValidationContext context, List results) + { + // 遍历所有可验证组件进行数据验证 + foreach (var key in _validatorCache) + { + if (key.Key.ModelType == context.ObjectType) + { + if (BootstrapAdminEditContextDataAnnotationsExtensions.TryGetValidatableProperty(new FieldIdentifier(model, key.Key.FieldName), out var propertyInfo)) + { + // 设置其关联属性字段 + var propertyValue = propertyInfo.GetValue(model); + context.MemberName = propertyInfo.Name; + + var validator = _validatorCache[key.Key]; + + // 数据验证 + validator.ValidateProperty(propertyValue, context, results); + validator.ToggleMessage(results, false); + } + } + } + } + + /// + /// 字段验证方法 + /// + /// + /// + /// + public void ValidateProperty(object? propertyValue, ValidationContext context, List results) + { + if (_validatorCache.TryGetValue((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 new file mode 100644 index 00000000..f908a77e --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/LgbInputText.cs @@ -0,0 +1,135 @@ +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/ModalBase.cs b/src/admin/Bootstrap.Admin/Components/ModalBase.cs index 7ef3b54b..e7cdf8c3 100644 --- a/src/admin/Bootstrap.Admin/Components/ModalBase.cs +++ b/src/admin/Bootstrap.Admin/Components/ModalBase.cs @@ -15,6 +15,18 @@ namespace Bootstrap.Admin.Components [Inject] protected IJSRuntime? JSRuntime { get; set; } + /// + /// + /// + [Parameter] + public RenderFragment? ModalBody { get; set; } + + /// + /// 获得/设置 弹窗 Footer 代码块 + /// + [Parameter] + public RenderFragment? ModalFooter { get; set; } + /// /// /// @@ -28,22 +40,28 @@ namespace Bootstrap.Admin.Components public string Title { get; set; } = "未设置"; /// - /// - /// - [Parameter] - public RenderFragment? ModalBody { get; set; } - - /// - /// + /// 获得/设置 是否允许点击后台关闭弹窗 默认为 false /// [Parameter] public bool Backdrop { get; set; } /// - /// + /// 获得/设置 弹窗大小 /// [Parameter] - public RenderFragment? ModalFooter { get; set; } + public ModalSize Size { get; set; } + + /// + /// 获得/设置 是否垂直居中 默认为 true + /// + [Parameter] + public bool IsCentered { get; set; } = true; + + /// + /// 获得/设置 是否显示 Footer 默认为 true + /// + [Parameter] + public bool ShowFooter { get; set; } = true; /// /// @@ -56,5 +74,60 @@ namespace Bootstrap.Admin.Components JSRuntime.InitModal(); } } + + /// + /// 输出窗口大小样式 + /// + /// + protected string RenderModalSize() + { + var ret = ""; + switch (Size) + { + case ModalSize.Default: + break; + case ModalSize.Small: + ret = "modal-sm"; + break; + case ModalSize.Large: + ret = "modal-lg"; + break; + case ModalSize.ExtraLarge: + ret = "modal-xl"; + break; + } + return ret; + } + + /// + /// + /// + public void Toggle() + { + JSRuntime.ToggleModal($"#{Id}"); + } + } + + /// + /// 弹窗大小 + /// + public enum ModalSize + { + /// + /// + /// + Default, + /// + /// + /// + Small, + /// + /// + /// + Large, + /// + /// + /// + ExtraLarge, } } diff --git a/src/admin/Bootstrap.Admin/Components/PaginationBase.cs b/src/admin/Bootstrap.Admin/Components/PaginationBase.cs index 646f4a96..8fd977fe 100644 --- a/src/admin/Bootstrap.Admin/Components/PaginationBase.cs +++ b/src/admin/Bootstrap.Admin/Components/PaginationBase.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; using System; +using System.Collections.Generic; namespace Bootstrap.Admin.Components { @@ -10,25 +10,13 @@ namespace Bootstrap.Admin.Components public class PaginationBase : ComponentBase { /// - /// 获得/设置 页码总数 + /// /// [Parameter] - public int PageCount { get; set; } = 0; + public int TotalCount { get; set; } /// - /// 获得/设置 每页显示数据数量 - /// - [Parameter] - public int PageItems { get; set; } = 20; - - /// - /// 获得/设置 数据总数 - /// - [Parameter] - public int ItemsCount { get; set; } = 0; - - /// - /// 获得/设置 当前页码 + /// /// [Parameter] public int PageIndex { get; set; } = 1; @@ -36,25 +24,38 @@ namespace Bootstrap.Admin.Components /// /// /// - [Inject] - protected IJSRuntime? JSRuntime { get; set; } + [Parameter] + public int PageItems { get; set; } + + /// + /// 获得/设置 页码总数 + /// + public int PageCount + { + get + { + return (int)Math.Ceiling(TotalCount * 1.0 / PageItems); ; + } + } /// /// /// - public Action ClickPageCallback { get; set; } = new Action(i => { }); + [Parameter] + public Action OnPageClick { get; set; } = new Action((pageIndex, pageItems) => { }); /// /// /// - public Action PageItemsChangeCallback { get; set; } = new Action(() => { }); + [Parameter] + public Action OnPageItemsChange { get; set; } = new Action((pageItems) => { }); /// /// /// protected void MovePrev() { - if (PageIndex > 1) ClickPageCallback(PageIndex - 1); + if (PageIndex > 1) OnPageClick(PageIndex - 1, PageItems); } /// @@ -62,7 +63,7 @@ namespace Bootstrap.Admin.Components /// protected void MoveNext() { - if (PageIndex < PageCount) ClickPageCallback(PageIndex + 1); + if (PageIndex < PageCount) OnPageClick(PageIndex + 1, PageItems); } /// @@ -72,7 +73,36 @@ namespace Bootstrap.Admin.Components protected void ClickItem(int pageItems) { PageItems = pageItems; - PageItemsChangeCallback(); + OnPageItemsChange(PageItems); + } + + /// + /// + /// + /// + protected IEnumerable GetPages() + { + var pages = new List() { 20, 40, 80, 100, 200 }; + var ret = new List(); + for (int i = 0; i < pages.Count; i++) + { + ret.Add(pages[i]); + if (pages[i] >= TotalCount) break; + } + return ret; + } + + /// + /// + /// + /// + /// + /// + public void Update(int totalCount, int pageIndex, int pageItems) + { + TotalCount = totalCount; + PageIndex = pageIndex; + PageItems = pageItems; } } } diff --git a/src/admin/Bootstrap.Admin/Components/Placement.cs b/src/admin/Bootstrap.Admin/Components/Placement.cs new file mode 100644 index 00000000..70f1b93c --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/Placement.cs @@ -0,0 +1,84 @@ +namespace Bootstrap.Admin.Components +{ + /// + /// 组件位置枚举类型 + /// + public enum Placement + { + /// + /// + /// + Default, + /// + /// + /// + Top, + /// + /// + /// + TopLeft, + /// + /// + /// + TopRight, + /// + /// + /// + Bottom, + /// + /// + /// + BottomLeft, + /// + /// + /// + BottomRight, + /// + /// + /// + Center + } + + /// + /// + /// + public static class PlacementExtensions + { + /// + /// + /// + /// + /// + /// + public static string ToCss(this Placement placement, string prefix = "") + { + string ret = ""; + switch (placement) + { + case Placement.Center: + ret = "center"; + break; + case Placement.Top: + ret = "top"; + break; + case Placement.TopLeft: + ret = "top-left"; + break; + case Placement.TopRight: + ret = "top-right"; + break; + case Placement.Bottom: + ret = "bottom"; + break; + case Placement.BottomLeft: + ret = "bottom-left"; + break; + case Placement.BottomRight: + case Placement.Default: + ret = "bottom-right"; + break; + } + return string.IsNullOrEmpty(prefix) ? ret : $"{prefix}-{ret}"; + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/QueryBase.cs b/src/admin/Bootstrap.Admin/Components/QueryBase.cs new file mode 100644 index 00000000..1e46e068 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/QueryBase.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Components; +using System; + +namespace Bootstrap.Admin.Components +{ + /// + /// 查询组件 + /// + public class QueryBase : ComponentBase + { + private readonly string _defaultTitle = "查询条件"; + private readonly string _defaultText = "查询"; + + /// + /// 查询组件标题 默认为 查询条件 + /// + [Parameter] + public string Title { get; set; } = ""; + + /// + /// 查询按钮显示文字 默认为 查询 + /// + [Parameter] + public string Text { get; set; } = ""; + + /// + /// 查询组件模板 + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + +#nullable disable + /// + /// + /// + [Parameter] + public TItem QueryModel { get; set; } +#nullable restore + + /// + /// + /// + [Parameter] + public EventCallback QueryModelChanged { get; set; } + + /// + /// 查询按钮回调方法 + /// + [Parameter] + public Action? OnQuery { get; set; } + + /// + /// 参数设置方法 + /// + protected override void OnParametersSet() + { + if (string.IsNullOrEmpty(Title)) Title = _defaultTitle; + if (string.IsNullOrEmpty(Text)) Text = _defaultText; + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/QueryData.cs b/src/admin/Bootstrap.Admin/Components/QueryData.cs new file mode 100644 index 00000000..f1a321c1 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/QueryData.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace Bootstrap.Admin.Components +{ + /// + /// + /// + /// + public class QueryData + { + /// + /// + /// + public IEnumerable Items { get; set; } = new T[0]; + + /// + /// + /// + public int TotalCount { get; set; } + + /// + /// + /// + public int PageIndex { get; set; } = 1; + + /// + /// + /// + public int PageItems { get; set; } = 20; + } +} diff --git a/src/admin/Bootstrap.Admin/Components/RequiredValidator.cs b/src/admin/Bootstrap.Admin/Components/RequiredValidator.cs new file mode 100644 index 00000000..e2e5907b --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/RequiredValidator.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Components; + +namespace Bootstrap.Admin.Components +{ + /// + /// + /// + public class RequiredValidator : ValidatorComponentBase + { + /// + /// 获得/设置 是否允许空字符串 默认 false 不允许 + /// + [Parameter] + public bool AllowEmptyString { get; set; } = false; + + /// + /// + /// + /// + /// + /// + public override void Validate(object? propertyValue, ValidationContext context, List results) + { + var val = propertyValue?.ToString() ?? ""; + if (!AllowEmptyString && val == string.Empty) + { + results.Add(new ValidationResult(ErrorMessage, new string[] { context.MemberName })); + } + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/SelectBase.cs b/src/admin/Bootstrap.Admin/Components/SelectBase.cs index 91b23653..89ce3eb8 100644 --- a/src/admin/Bootstrap.Admin/Components/SelectBase.cs +++ b/src/admin/Bootstrap.Admin/Components/SelectBase.cs @@ -1,27 +1,23 @@ using Microsoft.AspNetCore.Components; using System; +using System.Collections.Generic; +using System.Linq; namespace Bootstrap.Admin.Components { /// /// Select 组件基类 /// - public class SelectBase : ComponentBase + public class SelectBase : ComponentBase { /// - /// + /// 获得/设置 控件 ID /// [Parameter] public string Id { get; set; } = ""; /// - /// - /// - [Parameter] - public RenderFragment? ChildContent { get; set; } - - /// - /// + /// 获得/设置 背景显示文字 /// [Parameter] public string PlaceHolder { get; set; } = "请选择 ..."; @@ -29,49 +25,64 @@ namespace Bootstrap.Admin.Components /// /// 当前选择项实例 /// - public SelectItemBase? SelectedItem { get; set; } + public SelectedItem SelectedItem { get; set; } = new SelectedItem(); + /// + /// 获得/设置 绑定数据集 + /// + [Parameter] + public List Items { get; set; } = new List(); + +#nullable disable + private TItem _value; /// /// /// [Parameter] - public int SelectedValue { get; set; } - - /// - /// - /// - [Parameter] - public EventCallback SelectedValueChanged { get; set; } - - /// - /// - /// - public Action? ClickItemCallback { get; set; } - - /// - /// - /// - public Action? ActiveChanged { get; set; } - - /// - /// - /// - /// - protected override void OnAfterRender(bool firstRender) + public TItem SelectedValue { - if (firstRender) + get { return _value; } + set { - ClickItemCallback = UpdateItem; - ActiveChanged = UpdateItem; + _value = value; + Items.ForEach(t => + { + t.Active = t.Value == _value.ToString(); + if (t.Active) SelectedItem = t; + }); + } + } +#nullable restore + + /// + /// + /// + [Parameter] + public EventCallback SelectedValueChanged { get; set; } + + /// + /// + /// + protected override void OnInitialized() + { + if (!SelectedItem.Active) + { + SelectedItem = Items.FirstOrDefault(item => item.Active) ?? Items.First(); } } /// /// /// - /// - protected void UpdateItem(SelectItemBase item) + [Parameter] + public Action SelectedItemChanged { get; set; } = new Action(v => { }); + + /// + /// + /// + public void ItemClickCallback(SelectedItem item) { + SelectedItemChanged(item); SelectedItem = item; StateHasChanged(); } diff --git a/src/admin/Bootstrap.Admin/Components/SelectItemBase.cs b/src/admin/Bootstrap.Admin/Components/SelectItemBase.cs index 30ce7ca4..b9835e9a 100644 --- a/src/admin/Bootstrap.Admin/Components/SelectItemBase.cs +++ b/src/admin/Bootstrap.Admin/Components/SelectItemBase.cs @@ -1,5 +1,5 @@ -using Bootstrap.Admin.Shared; -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components; +using System; namespace Bootstrap.Admin.Components { @@ -12,44 +12,12 @@ namespace Bootstrap.Admin.Components /// /// [Parameter] - public bool Active { get; set; } + public SelectedItem Item { get; set; } = new SelectedItem(); /// /// /// [Parameter] - public string Text { get; set; } = ""; - - /// - /// - /// - [Parameter] - public string Value { get; set; } = ""; - - /// - /// - /// - [CascadingParameter] - public Select? Select { get; set; } - - /// - /// - /// - /// - protected override void OnAfterRender(bool firstRender) - { - if (firstRender) - { - if (Active) Select?.ActiveChanged?.Invoke(this); - } - } - - /// - /// - /// - protected void ClickItem() - { - Select?.ClickItemCallback?.Invoke(this); - } + public Action ItemClickCallback { get; set; } = new Action(SelectedItem => { }); } } diff --git a/src/admin/Bootstrap.Admin/Components/SelectedItem.cs b/src/admin/Bootstrap.Admin/Components/SelectedItem.cs new file mode 100644 index 00000000..f1c81ab9 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/SelectedItem.cs @@ -0,0 +1,23 @@ +namespace Bootstrap.Admin.Components +{ + /// + /// 选项类 + /// + public class SelectedItem + { + /// + /// 获得/设置 显示名称 + /// + public string Text { get; set; } = ""; + + /// + /// 获得/设置 选项值 + /// + public string Value { get; set; } = ""; + + /// + /// 获得/设置 是否选中 + /// + public bool Active { get; set; } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/StringLengthValidator.cs b/src/admin/Bootstrap.Admin/Components/StringLengthValidator.cs new file mode 100644 index 00000000..d2c1b971 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/StringLengthValidator.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Components; + +namespace Bootstrap.Admin.Components +{ + /// + /// + /// + public class StringLengthValidator : ValidatorComponentBase + { + /// + /// + /// + [Parameter] + public int Length { get; set; } + + /// + /// + /// + /// + /// + /// + public override void Validate(object? propertyValue, ValidationContext context, List results) + { + var val = propertyValue?.ToString() ?? ""; + if (val.Length > Length) results.Add(new ValidationResult(ErrorMessage, new string[] { context.MemberName })); + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/SubmitModalBase.cs b/src/admin/Bootstrap.Admin/Components/SubmitModalBase.cs new file mode 100644 index 00000000..f8bb919a --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/SubmitModalBase.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; + +namespace Bootstrap.Admin.Components +{ + /// + /// 数据绑定提交弹窗组件 + /// + public class SubmitModalBase : ModalBase + { +#nullable disable + /// + /// 获得/设置 弹窗绑定数据实体 + /// + [Parameter] + public TItem Model { get; set; } +#nullable restore + + /// + /// + /// + [Parameter] + public EventCallback ModelChanged { get; set; } + + /// + /// A callback that will be invoked when the form is submitted. + /// If using this parameter, you are responsible for triggering any validation + /// manually, e.g., by calling . + /// + [Parameter] public EventCallback OnSubmit { get; set; } + + /// + /// A callback that will be invoked when the form is submitted and the + /// is determined to be valid. + /// + [Parameter] public EventCallback OnValidSubmit { get; set; } + + /// + /// A callback that will be invoked when the form is submitted and the + /// is determined to be invalid. + /// + [Parameter] public EventCallback OnInvalidSubmit { get; set; } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/TableBase.cs b/src/admin/Bootstrap.Admin/Components/TableBase.cs index 588fe2b9..04ce2f23 100644 --- a/src/admin/Bootstrap.Admin/Components/TableBase.cs +++ b/src/admin/Bootstrap.Admin/Components/TableBase.cs @@ -1,21 +1,27 @@ -using Bootstrap.Admin.Extensions; +using Bootstrap.Admin.Shared; using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; +using Microsoft.AspNetCore.Components.Forms; using System; using System.Collections.Generic; +using System.Linq; namespace Bootstrap.Admin.Components { /// /// 表格组件类 /// - public class TableBase : ComponentBase + public class TableBase : BootstrapComponentBase { /// /// /// - [Inject] - protected IJSRuntime? JSRuntime { get; set; } + public const int DefaultPageItems = 20; + + /// + /// + /// + [Parameter] + public string Id { get; set; } = ""; /// /// @@ -29,6 +35,18 @@ namespace Bootstrap.Admin.Components [Parameter] public RenderFragment? RowTemplate { get; set; } + /// + /// 按钮模板 + /// + [Parameter] + public RenderFragment? ButtonTemplate { get; set; } + + /// + /// + /// + [Parameter] + public RenderFragment? EditTemplate { get; set; } + /// /// /// @@ -38,30 +56,271 @@ namespace Bootstrap.Admin.Components /// /// /// + protected IEnumerable Items { get; set; } = new TItem[0]; + + /// + /// + /// + public List SelectedItems { get; } = new List(); + + /// + /// 获得/设置 是否显示行号 + /// + [Parameter] + public bool ShowLineNo { get; set; } = true; + + /// + /// 获得/设置 是否显示选择列 默认为 true + /// + [Parameter] + public bool ShowCheckbox { get; set; } = true; + + /// + /// 获得/设置 是否显示按钮列 默认为 true + /// + [Parameter] + public bool ShowButtons { get; set; } = true; + + /// + /// 获得/设置 是否显示工具栏 默认为 true + /// [Parameter] public bool ShowToolBar { get; set; } = true; + /// + /// 获得/设置 按钮列 Header 文本 默认为 操作 + /// + [Parameter] + public string ButtonTemplateHeaderText { get; set; } = "操作"; + + /// + /// 点击翻页回调方法 + /// + [Parameter] + public Func>? OnQuery { get; set; } + + /// + /// 新建按钮回调方法 + /// + [Parameter] + public Func? OnAdd { get; set; } + + /// + /// 编辑按钮回调方法 + /// + [Parameter] + public Action? OnEdit { get; set; } + + /// + /// 保存按钮回调方法 + /// + [Parameter] + public Func? OnSave { get; set; } + + /// + /// 删除按钮回调方法 + /// + [Parameter] + public Func, bool>? OnDelete { get; set; } + /// /// /// [Parameter] - public IEnumerable Items { get; set; } = new TItem[0]; + public int TotalCount { get; set; } /// /// /// - public Action AddCallback { get; set; } = new Action(() => { }); - /// - /// - /// - public Action EditCallback { get; set; } = new Action(() => { }); + [Parameter] + public int PageIndex { get; set; } = 1; /// /// /// + [Parameter] + public int PageItems { get; set; } = DefaultPageItems; + + /// + /// 确认删除弹窗 + /// + protected Modal? ConfirmModal { get; set; } + +#nullable disable + /// + /// + /// + protected TItem EditModel { get; set; } +#nullable restore + + /// + /// 编辑数据弹窗 + /// + protected SubmitModal? EditModal { get; set; } + + /// + /// + /// + protected override void OnInitialized() + { + if (OnAdd != null) EditModel = OnAdd.Invoke(); + if (OnQuery != null) + { + var queryData = OnQuery.Invoke(1, DefaultPageItems); + Items = queryData.Items; + TotalCount = queryData.TotalCount; + } + } + + /// + /// + /// + /// + /// + protected void PageClick(int pageIndex, int pageItems) + { + if (pageIndex != PageIndex) + { + PageIndex = pageIndex; + PageItems = pageItems; + Query(); + } + } + + /// + /// + /// + protected void PageItemsChange(int pageItems) + { + if (OnQuery != null) + { + PageIndex = 1; + PageItems = pageItems; + Query(); + } + } + + /// + /// + /// + /// + /// + protected void ToggleCheck(TItem item, bool check) + { + if (item == null) + { + SelectedItems.Clear(); + if (check) SelectedItems.AddRange(Items); + } + else + { + if (check) SelectedItems.Add(item); + else SelectedItems.Remove(item); + } + StateHasChanged(); + } + + /// + /// + /// + /// + /// + protected CheckBoxState CheckState(TItem item) + { + var ret = CheckBoxState.UnChecked; + if (SelectedItems.Count > 0) + { + ret = SelectedItems.Count == Items.Count() ? CheckBoxState.Checked : CheckBoxState.Mixed; + } + return ret; + } + + /// + /// + /// + public void Add() + { + if (OnAdd != null) EditModel = OnAdd.Invoke(); + SelectedItems.Clear(); + EditModal?.Toggle(); + } + + /// + /// + /// + public void Edit() + { + if (SelectedItems.Count == 1) + { + EditModel = SelectedItems[0]; + EditModal?.Toggle(); + } + else + { + ShowMessage("编辑数据", "请选择一个要编辑的数据", ToastCategory.Information); + } + } + + /// + /// 更新查询数据 + /// + /// + public void Query(QueryData queryData) + { + SelectedItems.Clear(); + PageIndex = queryData.PageIndex; + Items = queryData.Items; + TotalCount = queryData.TotalCount; + StateHasChanged(); + } + + private void Query() + { + if (OnQuery != null) Query(OnQuery.Invoke(PageIndex, PageItems)); + } + + /// + /// + /// + /// + protected void Save(EditContext context) + { + var valid = OnSave?.Invoke((TItem)context.Model) ?? false; + if (valid) + { + EditModal?.Toggle(); + Query(); + } + ShowMessage("保存数据", "保存数据" + (valid ? "成功" : "失败"), valid ? ToastCategory.Success : ToastCategory.Error); + } + + /// + /// 删除按钮方法 + /// public void Delete() { + if (SelectedItems.Count > 0) + { + ConfirmModal?.Toggle(); + } + else + { + ShowMessage("删除数据", "请选择要删除的数据", ToastCategory.Information); + } + } + /// + /// 确认删除方法 + /// + public void Confirm() + { + var result = OnDelete?.Invoke(SelectedItems) ?? false; + if (result) + { + ConfirmModal?.Toggle(); + Query(); + } + ShowMessage("删除数据", "删除数据" + (result ? "成功" : "失败"), result ? ToastCategory.Success : ToastCategory.Error); } } } diff --git a/src/admin/Bootstrap.Admin/Components/ToastBase.cs b/src/admin/Bootstrap.Admin/Components/ToastBase.cs new file mode 100644 index 00000000..5601d6b9 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/ToastBase.cs @@ -0,0 +1,123 @@ +using Bootstrap.Admin.Extensions; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; + +namespace Bootstrap.Admin.Components +{ + /// + /// Toast 组件基类 + /// + public class ToastBase : ComponentBase + { + /// + /// IJSRuntime 实例 + /// + [Inject] + protected IJSRuntime? JSRuntime { get; set; } + + /// + /// 是否自动隐藏 默认为 true + /// + [Parameter] + public bool AutoHide { get; set; } = true; + + /// + /// 自动隐藏延时 默认为 1500 毫秒 + /// + [Parameter] + public int Interval { get; set; } = 2000; + + /// + /// 组件显示位置 + /// + [Parameter] + public Placement Placement { get; set; } + + /// + /// Toast 类型 + /// + [Parameter] + public ToastCategory Category { get; set; } + + /// + /// 组件标题文本 + /// + [Parameter] + public string Title { get; set; } = "BootstrapAdmin Blazor"; + + /// + /// 消息正文 + /// + [Parameter] + public string Text { get; set; } = "Toast 消息正文内容-未设置"; + + /// + /// 控件呈现后回调方法 + /// + /// + protected override void OnAfterRender(bool firstRender) + { + if (firstRender) + { + JSRuntime.InitToast(); + } + if (_show) + { + _show = false; + Show(); + } + } + + /// + /// + /// + protected void Show() + { + JSRuntime.ShowToast(); + } + + private bool _show; + /// + /// + /// + public void ShowMessage(string title, string text, ToastCategory category = ToastCategory.Success) + { + Title = title; + Text = text; + Category = category; + _show = true; + StateHasChanged(); + } + + /// + /// + /// + /// + protected string RenderCategory() + { + var ret = ""; + switch (Category) + { + case ToastCategory.Error: + ret = "fa fa-times-circle text-danger"; + break; + case ToastCategory.Information: + ret = "fa fa-exclamation-triangle text-info"; + break; + case ToastCategory.Success: + ret = "fa fa-check-circle text-success"; + break; + } + return ret; + } + + /// + /// + /// + /// + protected string RenderAnimation() + { + return AutoHide ? $"transition: width {(Interval * 1.0 - 200) / 1000}s linear;" : ""; + } + } +} diff --git a/src/admin/Bootstrap.Admin/Components/ToastCategory.cs b/src/admin/Bootstrap.Admin/Components/ToastCategory.cs new file mode 100644 index 00000000..2e2ba1d6 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/ToastCategory.cs @@ -0,0 +1,21 @@ +namespace Bootstrap.Admin.Components +{ + /// + /// Toast 组件类型 + /// + public enum ToastCategory + { + /// + /// 成功 + /// + Success, + /// + /// 提示信息 + /// + Information, + /// + /// 错误信息 + /// + Error + } +} diff --git a/src/admin/Bootstrap.Admin/Components/ValidatorComponentBase.cs b/src/admin/Bootstrap.Admin/Components/ValidatorComponentBase.cs new file mode 100644 index 00000000..58946c55 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Components/ValidatorComponentBase.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Bootstrap.Admin.Components +{ + /// + /// + /// + public abstract class ValidatorComponentBase : ComponentBase + { + /// + /// + /// + [Parameter] + public string ErrorMessage { get; set; } = ""; + + /// + /// + /// + [CascadingParameter] + public LgbInputText? Input { get; set; } + + /// + /// 初始化方法 + /// + protected override void OnInitialized() + { + if (Input == null) + { + throw new InvalidOperationException($"{nameof(ValidatorComponentBase)} requires a cascading " + + $"parameter of type {nameof(LgbInputText)}. For example, you can use {nameof(ValidatorComponentBase)} " + + $"inside an LgbInputText."); + } + + Input.Rules.Add(this); + } + + /// + /// + /// + /// + /// + /// + public abstract void Validate(object? propertyValue, ValidationContext context, List results); + } +} diff --git a/src/admin/Bootstrap.Admin/Extensions/JSRuntimeExtensions.cs b/src/admin/Bootstrap.Admin/Extensions/JSRuntimeExtensions.cs index a6c529ea..3ab6eb1b 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("$.enableAnimation"); + public static void EnableAnimation(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.initDocument"); /// /// 修复 Modal 组件 @@ -53,11 +53,31 @@ namespace Bootstrap.Admin.Extensions /// public static void InitModal(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.initModal"); + /// + /// 修复 Modal 组件 + /// + /// + public static void InitToast(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.initToast"); + /// /// 弹出 Modal 组件 /// /// /// public static void ToggleModal(this IJSRuntime? jSRuntime, string modalId) => jSRuntime.InvokeVoidAsync("$.toggleModal", modalId); + + /// + /// 弹出 Toast 组件 + /// + /// + public static void ShowToast(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.showToast"); + + /// + /// 弹出 Tooltip 组件 + /// + /// + /// + /// + public static void Tooltip(this IJSRuntime? jSRuntime, string id, string method) => jSRuntime.InvokeVoidAsync("$.tooltip", $"#{id}", method); } } diff --git a/src/admin/Bootstrap.Admin/Pages/Admin/Dicts.razor b/src/admin/Bootstrap.Admin/Pages/Admin/Dicts.razor index 8992e481..3367aa90 100644 --- a/src/admin/Bootstrap.Admin/Pages/Admin/Dicts.razor +++ b/src/admin/Bootstrap.Admin/Pages/Admin/Dicts.razor @@ -1,177 +1,56 @@ -@inject IJSRuntime JSRuntime +@inherits DictsBase -
-
查询条件
-
-
-
-
- - -
-
- - -
-
- - -
-
- -
-
-
-
-
-
-
- 查询结果 -
-
-
- - - - - - - - - - - - - -
字典标签字典名称字典代码字典类型@context.Category@context.Name@context.Code@context.Define
- + + +
+ +
-
-
-
-
- - -
-
- - - -
-
-
- - -
-
- - - -
-
- - -
-
- - -
+
+ + +
+
+ + +
+ + + 字典标签 + 字典名称 + 字典代码 + 字典类型 + + + @context.Category + @context.Name + @context.Code + @context.Define + + +
+
+ + + + +
- - - - - - - - -@code { - [Parameter] - public IEnumerable Items { get; set; } = new BootstrapDict[0]; - - [Parameter] - public int ItemsCount { get; set; } = 0; - - [Parameter] - public int PageCount { get; set; } = 0; - - [Parameter] - public int PageIndex { get; set; } = 1; - - protected Select? DictCate { get; set; } - - protected Pagination? Pagination { get; set; } - - protected Table? Table { get; set; } - - protected BootstrapDict Model { get; set; } = new BootstrapDict(); - - protected override void OnInitialized() - { - Query(1); - } - - protected override void OnAfterRender(bool firstRender) - { - if (firstRender) - { - if (Pagination != null) - { - Pagination.ClickPageCallback = pageIndex => - { - if (pageIndex != PageIndex) - { - Query(pageIndex); - StateHasChanged(); - } - }; - Pagination.PageItemsChangeCallback = () => - { - Query(1); - StateHasChanged(); - }; - } - if (Table != null) - { - Table.AddCallback = () => - { - JSRuntime.ToggleModal("#DialogDict"); - }; - Table.EditCallback = () => - { - - }; - } - } - } - - protected void Query(int pageIndex) - { - var data = DataAccess.DictHelper.RetrieveDicts(); - if (!string.IsNullOrEmpty(DictCate?.SelectedItem?.Value)) data = data.Where(d => d.Define.ToString() == DictCate?.SelectedItem?.Value); - ItemsCount = data.Count(); - - var pageItems = Pagination?.PageItems ?? 20; - Items = data.Skip((pageIndex - 1) * pageItems).Take(pageItems); - PageCount = (int)Math.Ceiling(data.Count() * 1.0d / pageItems); - PageIndex = pageIndex; - } - - protected void Save() - { - JSRuntime.ToggleModal("#DialogDict"); - } -} +
+ + +
+
+ + + + + +
+
+ + +
+
+
+ diff --git a/src/admin/Bootstrap.Admin/Pages/_Host.cshtml b/src/admin/Bootstrap.Admin/Pages/_Host.cshtml index a52ec7fb..eb58f29f 100644 --- a/src/admin/Bootstrap.Admin/Pages/_Host.cshtml +++ b/src/admin/Bootstrap.Admin/Pages/_Host.cshtml @@ -17,23 +17,16 @@ - - - - - - - - + diff --git a/src/admin/Bootstrap.Admin/Shared/AdminLayout.razor b/src/admin/Bootstrap.Admin/Shared/AdminLayout.razor index 8b7e3939..b79862f8 100644 --- a/src/admin/Bootstrap.Admin/Shared/AdminLayout.razor +++ b/src/admin/Bootstrap.Admin/Shared/AdminLayout.razor @@ -5,4 +5,5 @@
+ diff --git a/src/admin/Bootstrap.Admin/Shared/Alert.razor b/src/admin/Bootstrap.Admin/Shared/Alert.razor new file mode 100644 index 00000000..5bdd0e72 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Shared/Alert.razor @@ -0,0 +1,14 @@ +@inherits AlertBase + + + + @AlertBody + + + @AlertFooter + + + +@code { + +} diff --git a/src/admin/Bootstrap.Admin/Shared/Checkbox.razor b/src/admin/Bootstrap.Admin/Shared/Checkbox.razor new file mode 100644 index 00000000..1bb45d3c --- /dev/null +++ b/src/admin/Bootstrap.Admin/Shared/Checkbox.razor @@ -0,0 +1,9 @@ +@typeparam TItem +@inherits CheckboxBase + + diff --git a/src/admin/Bootstrap.Admin/Shared/EditPage.razor b/src/admin/Bootstrap.Admin/Shared/EditPage.razor new file mode 100644 index 00000000..da1b5c79 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Shared/EditPage.razor @@ -0,0 +1,31 @@ +@typeparam TItem +@inherits EditPageBase + + + @QueryBody?.Invoke(context) + +
+
+ 查询结果 +
+
+ + + @TableHeader + + + @RowTemplate?.Invoke(context) + + +
+ + + @ButtonTemplate +
+
+ + @EditTemplate?.Invoke(context) + +
+
+
diff --git a/src/admin/Bootstrap.Admin/Shared/LgbEditForm.razor b/src/admin/Bootstrap.Admin/Shared/LgbEditForm.razor new file mode 100644 index 00000000..bd086b67 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Shared/LgbEditForm.razor @@ -0,0 +1,13 @@ +@inherits LgbEditFormBase + + + + + @ChildContent + + + + +@code { + +} diff --git a/src/admin/Bootstrap.Admin/Shared/Modal.razor b/src/admin/Bootstrap.Admin/Shared/Modal.razor index 98a5e570..5c4e446e 100644 --- a/src/admin/Bootstrap.Admin/Shared/Modal.razor +++ b/src/admin/Bootstrap.Admin/Shared/Modal.razor @@ -1,8 +1,7 @@ @inherits ModalBase -
[Parameter] - public int PageIndex { get; set; } = 0; + public int PageIndex { get; set; } = 1; /// /// /// protected void ClickPage() { - Pagination?.ClickPageCallback?.Invoke(PageIndex); + Pagination?.OnPageClick?.Invoke(PageIndex, Pagination.PageItems); } } diff --git a/src/admin/Bootstrap.Admin/Shared/Query.razor b/src/admin/Bootstrap.Admin/Shared/Query.razor new file mode 100644 index 00000000..6f0c4da4 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Shared/Query.razor @@ -0,0 +1,16 @@ +@typeparam TItem +@inherits QueryBase + +
+
@Title
+
+
+
+ @ChildContent?.Invoke(QueryModel) +
+ +
+
+
+
+
diff --git a/src/admin/Bootstrap.Admin/Shared/Select.razor b/src/admin/Bootstrap.Admin/Shared/Select.razor index f0a96464..4cd98b2b 100644 --- a/src/admin/Bootstrap.Admin/Shared/Select.razor +++ b/src/admin/Bootstrap.Admin/Shared/Select.razor @@ -1,13 +1,15 @@ -@inherits SelectBase +@typeparam TItem +@inherits SelectBase diff --git a/src/admin/Bootstrap.Admin/Shared/SelectItem.razor b/src/admin/Bootstrap.Admin/Shared/SelectItem.razor index ff0a0346..f372f76d 100644 --- a/src/admin/Bootstrap.Admin/Shared/SelectItem.razor +++ b/src/admin/Bootstrap.Admin/Shared/SelectItem.razor @@ -1,6 +1,7 @@ @inherits SelectItemBase -
@Text
+
@Item.Text
@code { + } diff --git a/src/admin/Bootstrap.Admin/Shared/SubmitModal.razor b/src/admin/Bootstrap.Admin/Shared/SubmitModal.razor new file mode 100644 index 00000000..83030ed2 --- /dev/null +++ b/src/admin/Bootstrap.Admin/Shared/SubmitModal.razor @@ -0,0 +1,20 @@ +@typeparam TItem +@inherits SubmitModalBase + + + + + @ModalBody + + + + + + + diff --git a/src/admin/Bootstrap.Admin/Shared/Table.razor b/src/admin/Bootstrap.Admin/Shared/Table.razor index ea6ec07b..7623e6e7 100644 --- a/src/admin/Bootstrap.Admin/Shared/Table.razor +++ b/src/admin/Bootstrap.Admin/Shared/Table.razor @@ -1,36 +1,89 @@ @typeparam TItem @inherits TableBase +
- - - + + +
- - - @TableHeader - - - @foreach (var item in Items) - { - @RowTemplate?.Invoke(item) - } - - - @TableFooter - -
+ + + + @if (ShowLineNo) + { + + } + @if (ShowCheckbox) + { + + } + @TableHeader + @if (ShowButtons) + { + + } + + + + @for (int index = 0; index < Items.Count(); index++) + { + + @if (ShowLineNo) + { + + } + @if (ShowCheckbox) + { + + } + @RowTemplate?.Invoke(Items.ElementAt(index)) + @if (ShowButtons) + { + + } + + } + + + @TableFooter + +
行号@ButtonTemplateHeaderText
@(index + 1 + (PageIndex - 1) * PageItems)@ButtonTemplate?.Invoke(Items.ElementAt(index))
+ +
+ + + + + + + + + + + + + + @EditTemplate?.Invoke(EditModel) + + @code { diff --git a/src/admin/Bootstrap.Admin/Shared/Toast.razor b/src/admin/Bootstrap.Admin/Shared/Toast.razor new file mode 100644 index 00000000..eb1a2b7e --- /dev/null +++ b/src/admin/Bootstrap.Admin/Shared/Toast.razor @@ -0,0 +1,19 @@ +@inherits ToastBase + + + +@code { + +} diff --git a/src/admin/Bootstrap.Admin/Tasks/TasksExtensions.cs b/src/admin/Bootstrap.Admin/Tasks/TasksExtensions.cs index b89684fc..6ef34853 100644 --- a/src/admin/Bootstrap.Admin/Tasks/TasksExtensions.cs +++ b/src/admin/Bootstrap.Admin/Tasks/TasksExtensions.cs @@ -1,7 +1,6 @@ using Bootstrap.DataAccess; using Longbow.Tasks; using Microsoft.Extensions.Hosting; -using System; using System.Linq; using System.Threading; using Task = System.Threading.Tasks.Task; diff --git a/src/admin/Bootstrap.Admin/wwwroot/css/blazor.css b/src/admin/Bootstrap.Admin/wwwroot/css/blazor.css index 58f277e2..09fd09bb 100644 --- a/src/admin/Bootstrap.Admin/wwwroot/css/blazor.css +++ b/src/admin/Bootstrap.Admin/wwwroot/css/blazor.css @@ -1,3 +1,7 @@ +#components-reconnect-modal { + z-index: 9999 !important; +} + .nav-link-bar { border: 1px solid #dee2e6; border-top-left-radius: 0.25rem; @@ -67,6 +71,35 @@ nav .dropdown .nav-link-close.dropdown-toggle:after { padding: 0.5rem; } +.table .btn.btn-sm { + font-size: 0.75rem; + padding: 1px 5px; +} + +.table .btn-group { + white-space: nowrap; +} + +.table-col-lineno { + width: 46px; + text-align: right; + white-space: nowrap; +} + +.table-col-checkbox { + width: 32px; + text-align: center; +} + + .table-col-checkbox .checkbox-label { + display: none; + } + +.table-selected .form-checkbox { + line-height: 1; + vertical-align: text-bottom; +} + .card-body .bootstrap-table { margin-top: 0rem; } @@ -75,6 +108,19 @@ nav .dropdown .nav-link-close.dropdown-toggle:after { margin-bottom: 0.625rem; } + .bs-bars .dropdown-menu.show { + display: flex; + min-width: unset; + } + + .bs-bars .dropdown-item { + padding: 6px 12px; + } + + .bs-bars .dropdown-item:not(:first-child) { + border-left: solid 1px #aeb2b7; + } + .bootstrap-table { margin-bottom: 0; } @@ -83,3 +129,94 @@ nav .dropdown .nav-link-close.dropdown-toggle:after { flex: 1 1 auto; margin-bottom: 1rem; } + +.toast { + position: absolute; + z-index: -1; + min-width: 260px; +} + + .toast.show { + z-index: 1080; + } + +.toast-top { + left: 0; + right: 0; + top: 1rem; + margin: 0 auto; +} + +.toast-top-left { + top: 1rem; + left: 1rem; +} + +.toast-top-right { + top: 1rem; + right: 1rem; +} + +.toast-bottom { + left: 0; + right: 0; + bottom: 1rem; + margin: 0 auto; +} + +.toast-bottom-left { + bottom: 1rem; + left: 1rem; +} + +.toast-bottom-right { + bottom: 1rem; + right: 1rem; +} + +.toast-center { + left: 0; + right: 0; + margin: 0 auto; + margin-top: calc(50vh - 42px); +} + +.toast-bar { + margin-right: 8px; +} + +.toast-header { + border-bottom: 1px solid #ccc; +} + +.toast-body { + background-color: #e9ecef; + color: rgba(0, 0, 0, 0.9); + word-break: break-all; +} + +.toast-progress { + position: absolute; + right: 0; + bottom: 0; + height: 4px; + background-color: #000000; + opacity: 0.4; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + filter: alpha(opacity=40); + width: 0; +} + +.showing .toast-progress, .show .toast-progress { + width: 100%; +} + +.modal-confirm-body { + margin-bottom: 15px; + font-size: 1.125rem; +} + +/* Validation */ +input[type="text"].valid, input[type="password"].valid { + padding-right: 30px; +} diff --git a/src/admin/Bootstrap.Admin/wwwroot/css/theme.css b/src/admin/Bootstrap.Admin/wwwroot/css/theme.css index 2542dba5..e25109b3 100644 --- a/src/admin/Bootstrap.Admin/wwwroot/css/theme.css +++ b/src/admin/Bootstrap.Admin/wwwroot/css/theme.css @@ -6,7 +6,6 @@ body { color: #797979; background: #f1f2f7; - -webkit-font-smoothing: antialiased; -webkit-overflow-scrolling: touch; } diff --git a/src/admin/Bootstrap.Admin/wwwroot/js/ba.blazor.js b/src/admin/Bootstrap.Admin/wwwroot/js/ba.blazor.js index 905fae65..e5e76c44 100644 --- a/src/admin/Bootstrap.Admin/wwwroot/js/ba.blazor.js +++ b/src/admin/Bootstrap.Admin/wwwroot/js/ba.blazor.js @@ -106,8 +106,9 @@ var $curTab = $navBar.find('.active').first(); return $curTab.next().attr('url'); }, - enableAnimation: function () { + initDocument: function () { $('body').removeClass('trans-mute'); + $('[data-toggle="tooltip"]').tooltip(); }, initSidebar: function () { $('.sidebar').addNiceScroll().autoScrollSidebar(); @@ -119,15 +120,30 @@ initModal: function () { $('.modal').appendTo($('body')); }, + initToast: function () { + $('.toast').appendTo($('body')); + }, toggleModal: function (modalId) { $(modalId).modal('toggle'); + }, + showToast: function () { + $('.toast').toast('show'); + }, + tooltip: function (id, method) { + $(id).tooltip(method); + }, + submitForm: function (btn) { + $(btn).parent().prev().find('form :submit').click(); } }); $(function () { $(document) - .on('click', '.nav-tabs .nav-link', function (e) { - + .on('hidden.bs.toast', '.toast', function () { + $(this).removeClass('hide'); + }) + .on('inserted.bs.tooltip', '.is-invalid', function () { + $('#' + $(this).attr('aria-describedby')).addClass('is-invalid'); }); }); })(jQuery); diff --git a/src/admin/Bootstrap.Admin/wwwroot/lib/longbow-checkbox/longbow-checkbox.css b/src/admin/Bootstrap.Admin/wwwroot/lib/longbow-checkbox/longbow-checkbox.css index 726f7350..8cb09393 100644 --- a/src/admin/Bootstrap.Admin/wwwroot/lib/longbow-checkbox/longbow-checkbox.css +++ b/src/admin/Bootstrap.Admin/wwwroot/lib/longbow-checkbox/longbow-checkbox.css @@ -51,7 +51,7 @@ margin-left: 10px; } - .form-checkbox.is-checked .checkbox-input .checkbox-inner { + .form-checkbox.is-checked .checkbox-input .checkbox-inner, .form-checkbox.is-indeterminate .checkbox-input .checkbox-inner { background-color: #409eff; border-color: #409eff; } @@ -77,8 +77,20 @@ border-color: #c0c4cc; } + .form-checkbox.is-indeterminate .checkbox-input .checkbox-inner:before { + content: ""; + position: absolute; + display: block; + background-color: #fff; + height: 2px; + transform: scale(.5); + left: 0; + right: 0; + top: 5px; + } + @media (min-width: 576px) { .form-inline .form-checkbox { display: inline-block; } -} \ No newline at end of file +}