refactor: 字典表功能完善

This commit is contained in:
Argo Window10 2019-12-10 15:38:23 +08:00
parent 0ef303540e
commit 60b0f2225d
35 changed files with 487 additions and 332 deletions

View File

@ -22,11 +22,6 @@ namespace Bootstrap.Admin.Components
[CascadingParameter(Name = "Default")]
public DefaultLayout RootLayout { get; protected set; } = new DefaultLayout();
/// <summary>
/// Toast 组件实例
/// </summary>
protected Toast Toast { get; set; } = new Toast();
/// <summary>
///
/// </summary>
@ -44,21 +39,11 @@ namespace Bootstrap.Admin.Components
/// <returns></returns>
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);
}
/// <summary>
///
/// </summary>
/// <param name="title"></param>
/// <param name="text"></param>
/// <param name="cate"></param>
public void ShowMessage(string title, string text, ToastCategory cate = ToastCategory.Success) => Toast.ShowMessage(title, text, cate);
}
}

View File

@ -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);

View File

@ -9,11 +9,6 @@ namespace Bootstrap.Admin.Components
/// </summary>
public class BootstrapComponentBase : ComponentBase
{
/// <summary>
///
/// </summary>
[CascadingParameter(Name = "Admin")]
protected AdminLayout Layout { get; set; } = new AdminLayout();
/// <summary>
///
@ -26,13 +21,10 @@ namespace Bootstrap.Admin.Components
/// </summary>
[Inject]
protected IJSRuntime? JSRuntime { get; set; }
/// <summary>
///
/// </summary>
/// <param name="title"></param>
/// <param name="text"></param>
/// <param name="cate"></param>
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();
}
}

View File

@ -39,16 +39,6 @@ namespace Bootstrap.Admin.Components
/// </summary>
public NavigatorBarModel Model { get; set; } = new NavigatorBarModel("");
/// <summary>
///
/// </summary>
public SideBar? SideBar { get; set; }
/// <summary>
///
/// </summary>
public Header? Header { get; set; }
/// <summary>
///
/// </summary>
@ -95,23 +85,22 @@ namespace Bootstrap.Admin.Components
}
}
/// <summary>
/// 设置参数方法
/// </summary>
protected override void OnParametersSet()
{
RequestUrl = new UriBuilder(NavigationManager?.Uri ?? "").Path;
Model = new NavigatorBarModel(UserName, RequestUrl.ToMvcMenuUrl());
}
/// <summary>
///
/// </summary>
/// <param name="firstRender"></param>
protected override void OnAfterRender(bool firstRender)
{
if (!firstRender) ResetSideBar();
}
/// <summary>
/// 更新侧边栏方法
/// </summary>
public void ResetSideBar()
{
RequestUrl = new UriBuilder(NavigationManager?.Uri ?? "").Path;
Model = new NavigatorBarModel(UserName, RequestUrl.ToMvcMenuUrl());
SideBar?.Update(Model);
if (firstRender) JSRuntime.InitDocument();
}
}
}

View File

@ -59,5 +59,11 @@ namespace Bootstrap.Admin.Components
///
/// </summary>
protected bool Delete(IEnumerable<BootstrapDict> items) => DataAccess.DictHelper.Delete(items.Select(item => item.Id ?? ""));
/// <summary>
///
/// </summary>
/// <returns></returns>
protected override bool ShouldRender() => false;
}
}

View File

@ -8,7 +8,7 @@ namespace Bootstrap.Admin.Components
/// <summary>
/// 可编辑页面组件包含查询与数据表格
/// </summary>
public class EditPageBase<TItem> : BootstrapComponentBase
public class EditPageBase<TItem> : ComponentBase
{
/// <summary>
///

View File

@ -12,6 +12,12 @@ namespace Bootstrap.Admin.Components
/// </summary>
public class LgbEditFormBase : ComponentBase
{
/// <summary>
///
/// </summary>
[Parameter]
public string Id { get; set; } = "";
/// <summary>
/// Gets or sets a collection of additional attributes that will be applied to the created <c>form</c> element.
/// </summary>
@ -54,14 +60,14 @@ namespace Bootstrap.Admin.Components
/// <summary>
/// 验证组件缓存 静态全局提高性能
/// </summary>
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>();
/// <summary>
/// 添加数据验证组件到 EditForm 中
/// </summary>
/// <param name="key"></param>
/// <param name="comp"></param>
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);
/// <summary>
/// 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
/// <param name="results"></param>
public void ValidateProperty(object? propertyValue, ValidationContext context, List<ValidationResult> 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);
}
}
/// <summary>
///
/// </summary>
/// <param name="firstRender"></param>
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
}
}
}

View File

@ -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
{
/// <summary>
/// LgbInputText 组件
/// </summary>
public class LgbInputText : InputText, IValidateComponent
{
/// <summary>
///
/// </summary>
[Inject]
protected IJSRuntime? JSRuntime { get; set; }
/// <summary>
///
/// </summary>
[CascadingParameter]
public LgbEditFormBase? EditForm { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public string Id { get; set; } = "";
/// <summary>
///
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
/// <summary>
///
/// </summary>
protected string ErrorMessage { get; set; } = "";
/// <summary>
///
/// </summary>
protected string ValidCss { get; set; } = "";
private string _tooltipMethod = "";
/// <summary>
///
/// </summary>
/// <param name="propertyValue"></param>
/// <param name="context"></param>
/// <param name="results"></param>
public void ValidateProperty(object? propertyValue, ValidationContext context, List<ValidationResult> results)
{
Rules.ToList().ForEach(validator => validator.Validate(propertyValue, context, results));
}
/// <summary>
/// 显示/隐藏验证结果方法
/// </summary>
/// <param name="results"></param>
/// <param name="validProperty">是否对本属性进行数据验证</param>
public void ToggleMessage(IEnumerable<ValidationResult> 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";
}
}
/// <summary>
///
/// </summary>
protected override void OnInitialized()
{
EditForm?.AddValidator((FieldIdentifier.Model.GetType(), FieldIdentifier.FieldName), this);
}
/// <summary>
///
/// </summary>
/// <param name="firstRender"></param>
protected override void OnAfterRender(bool firstRender)
{
if (!string.IsNullOrEmpty(_tooltipMethod) && !string.IsNullOrEmpty(Id))
{
JSRuntime.Tooltip(Id, _tooltipMethod);
_tooltipMethod = "";
}
}
/// <summary>
///
/// </summary>
public ICollection<ValidatorComponentBase> Rules { get; } = new HashSet<ValidatorComponentBase>();
/// <summary>
///
/// </summary>
/// <param name="builder"></param>
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<string>(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<CascadingValue<LgbInputText>>(7);
builder.AddAttribute(8, "IsFixed", true);
builder.AddAttribute(9, "Value", this);
builder.AddAttribute(10, "ChildContent", ChildContent);
builder.CloseComponent();
builder.CloseElement();
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Linq;
namespace Bootstrap.Admin.Components
{
/// <summary>
/// LgbInputText 组件
/// </summary>
public class LgbInputTextBase : ValidateInputBase<string>
{
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <param name="result"></param>
/// <param name="validationErrorMessage"></param>
/// <returns></returns>
protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage)
{
result = value;
validationErrorMessage = "";
return true;
}
/// <summary>
///
/// </summary>
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;
}
}
}
}

View File

@ -56,6 +56,7 @@ namespace Bootstrap.Admin.Components
protected void MovePrev()
{
if (PageIndex > 1) OnPageClick(PageIndex - 1, PageItems);
else OnPageClick(PageCount, PageItems);
}
/// <summary>
@ -64,6 +65,7 @@ namespace Bootstrap.Admin.Components
protected void MoveNext()
{
if (PageIndex < PageCount) OnPageClick(PageIndex + 1, PageItems);
else OnPageClick(1, PageItems);
}
/// <summary>
@ -95,14 +97,21 @@ namespace Bootstrap.Admin.Components
/// <summary>
///
/// </summary>
/// <param name="totalCount"></param>
/// <param name="pageIndex"></param>
/// <param name="pageItems"></param>
public void Update(int totalCount, int pageIndex, int pageItems)
protected int StartPageIndex { get; set; }
/// <summary>
///
/// </summary>
protected int EndPageIndex { get; set; }
/// <summary>
///
/// </summary>
protected override void OnParametersSet()
{
TotalCount = totalCount;
PageIndex = pageIndex;
PageItems = pageItems;
// 计算 分页开始页码与结束页码
StartPageIndex = Math.Max(1, PageIndex - 4);
EndPageIndex = Math.Min(PageCount, PageIndex + 5);
}
}
}

View File

@ -11,6 +11,12 @@ namespace Bootstrap.Admin.Components
private readonly string _defaultTitle = "查询条件";
private readonly string _defaultText = "查询";
/// <summary>
///
/// </summary>
[Parameter]
public string Id { get; set; } = "";
/// <summary>
/// 查询组件标题 默认为 查询条件
/// </summary>
@ -37,12 +43,6 @@ namespace Bootstrap.Admin.Components
public TItem QueryModel { get; set; }
#nullable restore
/// <summary>
///
/// </summary>
[Parameter]
public EventCallback<TItem> QueryModelChanged { get; set; }
/// <summary>
/// 查询按钮回调方法
/// </summary>
@ -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);
}
}

View File

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Components;
namespace Bootstrap.Admin.Components
{
/// <summary>
///
/// </summary>
public class QueryInputTextBase<TItem> : LgbInputTextBase
{
}
}

View File

@ -9,11 +9,19 @@ namespace Bootstrap.Admin.Components
/// </summary>
public class RequiredValidator : ValidatorComponentBase
{
/// <summary>
///
/// </summary>
public RequiredValidator()
{
ErrorMessage = "这是必填字段";
}
/// <summary>
/// 获得/设置 是否允许空字符串 默认 false 不允许
/// </summary>
[Parameter]
public bool AllowEmptyString { get; set; } = false;
public bool AllowEmptyString { get; set; }
/// <summary>
///

View File

@ -8,20 +8,8 @@ namespace Bootstrap.Admin.Components
/// <summary>
/// Select 组件基类
/// </summary>
public class SelectBase<TItem> : ComponentBase
public class SelectBase<TItem> : ValidateInputBase<TItem>
{
/// <summary>
/// 获得/设置 控件 ID
/// </summary>
[Parameter]
public string Id { get; set; } = "";
/// <summary>
/// 获得/设置 背景显示文字
/// </summary>
[Parameter]
public string PlaceHolder { get; set; } = "请选择 ...";
/// <summary>
/// 当前选择项实例
/// </summary>
@ -33,38 +21,24 @@ namespace Bootstrap.Admin.Components
[Parameter]
public List<SelectedItem> Items { get; set; } = new List<SelectedItem>();
#nullable disable
private TItem _value;
/// <summary>
///
/// </summary>
[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
///<summary>
///
/// </summary>
[Parameter]
public EventCallback<TItem> SelectedValueChanged { get; set; }
/// <summary>
///
/// </summary>
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
///
/// </summary>
[Parameter]
public Action<SelectedItem> SelectedItemChanged { get; set; } = new Action<SelectedItem>(v => { });
public Action<SelectedItem>? SelectedItemChanged { get; set; }
/// <summary>
///
/// </summary>
public void ItemClickCallback(SelectedItem item)
{
SelectedItemChanged(item);
SelectedItem = item;
StateHasChanged();
CurrentValueAsString = item.Value;
}
}
}

View File

@ -15,12 +15,9 @@ namespace Bootstrap.Admin.Components
public NavigatorBarModel Model { get; set; } = new NavigatorBarModel("");
/// <summary>
/// 视图更新方法
/// 侧边栏绑定 Model 改变事件
/// </summary>
public void Update(NavigatorBarModel model)
{
Model = model;
StateHasChanged();
}
[Parameter]
public EventCallback<NavigatorBarModel> ModelChanged { get; set; }
}
}

View File

@ -9,11 +9,20 @@ namespace Bootstrap.Admin.Components
/// </summary>
public class StringLengthValidator : ValidatorComponentBase
{
private int _length = 50;
/// <summary>
///
/// </summary>
[Parameter]
public int Length { get; set; }
public int Length
{
get { return _length; }
set
{
_length = value;
ErrorMessage = $"不可为空,{_length}字以内";
}
}
/// <summary>
///

View File

@ -10,7 +10,7 @@ namespace Bootstrap.Admin.Components
/// <summary>
/// 表格组件类
/// </summary>
public class TableBase<TItem> : BootstrapComponentBase
public class TableBase<TItem> : ComponentBase
{
/// <summary>
///
@ -245,6 +245,19 @@ namespace Bootstrap.Admin.Components
EditModal?.Toggle();
}
/// <summary>
/// Toast 组件实例
/// </summary>
protected Toast? Toast { get; set; }
/// <summary>
///
/// </summary>
/// <param name="title"></param>
/// <param name="text"></param>
/// <param name="cate"></param>
protected void ShowMessage(string title, string text, ToastCategory cate = ToastCategory.Success) => Toast?.ShowMessage(title, text, cate);
/// <summary>
///
/// </summary>
@ -322,5 +335,7 @@ namespace Bootstrap.Admin.Components
}
ShowMessage("删除数据", "删除数据" + (result ? "成功" : "失败"), result ? ToastCategory.Success : ToastCategory.Error);
}
protected override void OnAfterRender(bool firstRender) => base.OnAfterRender(firstRender);
}
}

View File

@ -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
{
/// <summary>
///
/// </summary>
public abstract class ValidateInputBase<TItem> : InputBase<TItem>, IValidateComponent
{
/// <summary>
///
/// </summary>
[Inject]
protected IJSRuntime? JSRuntime { get; set; }
/// <summary>
///
/// </summary>
[CascadingParameter]
public LgbEditFormBase? EditForm { get; set; }
/// <summary>
///
/// </summary>
public string Id
{
get { return $"{EditForm?.Id}_{FieldIdentifier.FieldName}"; }
}
/// <summary>
///
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
/// <summary>
///
/// </summary>
protected string? PlaceHolder
{
get
{
if (AdditionalAttributes != null &&
AdditionalAttributes.TryGetValue("placeholder", out var ph) &&
!string.IsNullOrEmpty(Convert.ToString(ph)))
{
return ph.ToString();
}
return null;
}
}
/// <summary>
///
/// </summary>
protected string ErrorMessage { get; set; } = "";
/// <summary>
///
/// </summary>
protected string ValidCss { get; set; } = "";
/// <summary>
///
/// </summary>
protected string DisplayName { get; set; } = "-";
/// <summary>
///
/// </summary>
protected override void OnInitialized()
{
EditForm?.AddValidator((EditForm, FieldIdentifier.Model.GetType(), FieldIdentifier.FieldName), this);
DisplayName = FieldIdentifier.GetDisplayName();
}
/// <summary>
///
/// </summary>
/// <param name="firstRender"></param>
protected override void OnAfterRender(bool firstRender)
{
if (!string.IsNullOrEmpty(_tooltipMethod) && !string.IsNullOrEmpty(Id))
{
JSRuntime.Tooltip(Id, _tooltipMethod);
_tooltipMethod = "";
}
}
/// <summary>
///
/// </summary>
public ICollection<ValidatorComponentBase> Rules { get; } = new HashSet<ValidatorComponentBase>();
private string _tooltipMethod = "";
/// <summary>
///
/// </summary>
/// <param name="propertyValue"></param>
/// <param name="context"></param>
/// <param name="results"></param>
public void ValidateProperty(object? propertyValue, ValidationContext context, List<ValidationResult> results)
{
Rules.ToList().ForEach(validator => validator.Validate(propertyValue, context, results));
}
/// <summary>
/// 显示/隐藏验证结果方法
/// </summary>
/// <param name="results"></param>
/// <param name="validProperty">是否对本属性进行数据验证</param>
public void ToggleMessage(IEnumerable<ValidationResult> 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";
}
}
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <param name="result"></param>
/// <param name="validationErrorMessage"></param>
/// <returns></returns>
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<TItem>(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)}'.");
}
}
}

View File

@ -20,7 +20,7 @@ namespace Bootstrap.Admin.Components
///
/// </summary>
[CascadingParameter]
public LgbInputText? Input { get; set; }
public LgbInputTextBase? Input { get; set; }
/// <summary>
/// 初始化方法
@ -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.");
}

View File

@ -0,0 +1,46 @@
using Bootstrap.Security;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Concurrent;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
///
/// </summary>
public static class DisplayNamesExtensions
{
private static ConcurrentDictionary<(Type ModelType, string FieldName), string> _displayNameCache = new ConcurrentDictionary<(Type, string), string>();
/// <summary>
/// 向系统中加入实体类显示名称字典
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="displayName"></param>
/// <returns></returns>
public static bool TryGetValue((Type ModelType, string FieldName) cacheKey, out string? displayName) => _displayNameCache.TryGetValue(cacheKey, out displayName);
/// <summary>
///
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="valueFactory"></param>
/// <returns></returns>
public static string GetOrAdd((Type ModelType, string FieldName) cacheKey, Func<(Type, string), string> valueFactory) => _displayNameCache.GetOrAdd(cacheKey, valueFactory);
}
}

View File

@ -0,0 +1,38 @@
using Bootstrap.Admin.Components;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Forms;
using System.ComponentModel;
namespace Bootstrap.Admin.Extensions
{
/// <summary>
///
/// </summary>
public static class FieldIdentifierExtensions
{
/// <summary>
///
/// </summary>
/// <param name="fieldIdentifier"></param>
/// <returns></returns>
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 ?? "未设置";
}
}
}

View File

@ -45,7 +45,7 @@ namespace Bootstrap.Admin.Extensions
/// 启用动画
/// </summary>
/// <param name="jSRuntime"></param>
public static void EnableAnimation(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.initDocument");
public static void InitDocument(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.initDocument");
/// <summary>
/// 修复 Modal 组件

View File

@ -2,18 +2,9 @@
<EditPage Id="dict" TItem="Bootstrap.Security.BootstrapDict" QueryModel="QueryModel" OnQuery="Query" OnAdd="Add" OnDelete="Delete" OnSave="Save">
<QueryBody>
<div class="form-group col-sm-6 col-md-auto">
<label class="control-label" for="txt_dict_cate">字典标签</label>
<input type="text" class="form-control" @bind="context.Category" data-provide="typeahead" />
</div>
<div class="form-group col-sm-6 col-md-auto">
<label class="control-label" for="txt_dict_name">字典名称</label>
<input type="text" class="form-control" @bind="context.Name" />
</div>
<div class="form-group col-sm-6 col-md-auto">
<label class="control-label" for="txt_dict_define">字典类型</label>
<Select Items="QueryDefine" TItem="int" SelectedItemChanged="item=>context.Define = int.Parse(item.Value)"></Select>
</div>
<LgbInputText @bind-Value="@context.Category" maxlength="50" />
<Select Items="QueryDefine" TItem="int" @bind-Value="@context.Define" />
<LgbInputText @bind-Value="@context.Name" maxlength="50" />
</QueryBody>
<TableHeader>
<th>字典标签</th>
@ -25,32 +16,23 @@
<td>@context.Category</td>
<td>@context.Name</td>
<td>@context.Code</td>
<td>@context.Define</td>
<td>@(DefineItems.FirstOrDefault(d => d.Value == context.Define.ToString())?.Text ?? "-")</td>
</RowTemplate>
<EditTemplate>
<div class="row">
<div class="form-group col-sm-6">
<label class="control-label" for="dictCate">字典标签</label>
<LgbInputText Id="dictCate" type="text" class="form-control" data-toggle="tooltip" @bind-Value="context.Category" placeholder="不可为空50字以内">
<RequiredValidator AllowEmptyString="false" ErrorMessage="这是必填字段" />
<StringLengthValidator Length="5" ErrorMessage="不可为空50字以内" />
</LgbInputText>
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="dictDefine">字典类型</label>
<Select Id="dictDefine" Items="DefineItems" TItem="int" @bind-SelectedValue="context.Define" SelectedItemChanged="@(item=>context.Define = int.Parse(item.Value))"></Select>
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="dictName">字典名称</label>
<LgbInputText Id="dictName" type="text" class="form-control" @bind-Value="context.Name" placeholder="不可为空50字以内" maxlength="50">
<RequiredValidator AllowEmptyString="false" ErrorMessage="这是必填字段" />
<StringLengthValidator Length="5" ErrorMessage="不可为空50字以内" />
</LgbInputText>
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="dictCode">字典代码</label>
<LgbInputText Id="dictCode" type="text" class="form-control" @bind-Value="context.Code" placeholder="不可为空2000字以内" maxlength="2000" />
</div>
<LgbInputText @bind-Value="@context.Category" placeholder="不可为空50字以内" maxlength="50">
<RequiredValidator />
<StringLengthValidator Length="50" />
</LgbInputText>
<Select Items="DefineItems" TItem="int" @bind-Value="@context.Define"></Select>
<LgbInputText @bind-Value="@context.Name" placeholder="不可为空50字以内" maxlength="50">
<RequiredValidator />
<StringLengthValidator Length="50" />
</LgbInputText>
<LgbInputText @bind-Value="@context.Code" placeholder="不可为空2000字以内" maxlength="2000">
<RequiredValidator />
<StringLengthValidator Length="2000" />
</LgbInputText>
</div>
</EditTemplate>
</EditPage>

View File

@ -5,5 +5,4 @@
<Section ShowCardTitle="@RootLayout.Model.ShowCardTitle" LockScreenPeriod=@RootLayout.Model.LockScreenPeriod>
<TabSet @ref="TabSet"></TabSet>
</Section>
<Toast @ref="Toast"></Toast>
</CascadingValue>

View File

@ -1,8 +1,8 @@
@inherits DefaultLayoutComponentBase
<CascadingValue Value=this Name="Default">
<Header @ref="Header" Icon="@Model.Icon" Title="@Model.Title" IsAdmin=@IsAdmin UserName="@Model.UserName" @bind-DisplayName="@DisplayName"></Header>
<SideBar @ref="SideBar" Model=@Model />
<Header Icon="@Model.Icon" Title="@Model.Title" IsAdmin=@IsAdmin UserName="@Model.UserName" @bind-DisplayName="@DisplayName"></Header>
<SideBar @bind-Model=@Model />
@Body
<Footer Text="@Model.Footer" IsDemo=@Model.IsDemo></Footer>
</CascadingValue>

View File

@ -1,7 +1,7 @@
@typeparam TItem
@inherits EditPageBase<TItem>
<Query OnQuery="Query" TItem="TItem" @bind-QueryModel="QueryModel">
<Query Id="@($"{Id}_query")" OnQuery="Query" TItem="TItem" QueryModel="QueryModel">
@QueryBody?.Invoke(context)
</Query>
<div class="card">

View File

@ -0,0 +1,9 @@
@inherits LgbInputTextBase
<div class="form-group col-sm-6">
<label class="control-label" for="@Id">@DisplayName</label>
<input Id="@Id" data-original-title="@ErrorMessage" class="@($"form-control {CssClass} {ValidCss}")" placeholder="@PlaceHolder" maxlength=@MaxLength type="text" @bind="Value" @oninput="EventCallback.Factory.CreateBinder<string>(this, __value => CurrentValueAsString = __value, CurrentValueAsString)" />
<CascadingValue Value="this" IsFixed="true">
@ChildContent
</CascadingValue>
</div>

View File

@ -17,7 +17,7 @@
</div>
<ul class="@(PageCount > 1 ? "pagination" : "pagination d-none")">
<li class="page-item" @onclick="MovePrev"><div class="page-link" aria-label="上一页"><i class="fa fa-angle-double-left"></i></div></li>
@for (int i = 1; i <= PageCount; i++)
@for (int i = StartPageIndex; i <= EndPageIndex; i++)
{
<PaginationItem Active="i == PageIndex" PageIndex="i"></PaginationItem>
}

View File

@ -4,13 +4,13 @@
<div class="card">
<div class="card-header">@Title</div>
<div class="card-body">
<div class="form-inline">
<LgbEditForm class="form-inline" Id="@Id" Model="QueryModel" OnValidSubmit="e => OnQuery?.Invoke()">
<div class="row">
@ChildContent?.Invoke(QueryModel)
<div class="form-group col-sm-6 col-md-auto flex-sm-fill justify-content-sm-end align-self-sm-end">
<button type="button" id="btn_query" class="btn btn-primary btn-fill" @onclick="e => OnQuery?.Invoke()"><i class="fa fa-search" aria-hidden="true"></i><span>@Text</span></button>
<button type="submit" class="btn btn-primary btn-fill"><i class="fa fa-search"></i><span>@Text</span></button>
</div>
</div>
</div>
</LgbEditForm>
</div>
</div>

View File

@ -1,15 +1,18 @@
@typeparam TItem
@inherits SelectBase<TItem>
<div data-toggle="lgbSelect" class="form-select dropdown">
<input type="text" readonly="readonly" class="form-control form-select-input" id="@Id" data-toggle="dropdown" placeholder="@PlaceHolder" value="@SelectedItem.Text">
<span class="form-select-append"><i class="fa fa-angle-up"></i></span>
<div class="dropdown-menu-arrow"></div>
<div class="dropdown-menu">
@foreach (var item in Items)
{
<SelectItem Item="@item" ItemClickCallback="ItemClickCallback"></SelectItem>
}
<div class="form-group col-sm-6">
<label class="control-label" for="@Id">@DisplayName</label>
<div data-toggle="lgbSelect" class="form-select dropdown">
<input type="text" readonly="readonly" class="form-control form-select-input" id="@Id" data-toggle="dropdown" placeholder="@PlaceHolder" value="@SelectedItem.Text" />
<span class="form-select-append"><i class="fa fa-angle-up"></i></span>
<div class="dropdown-menu-arrow"></div>
<div class="dropdown-menu">
@foreach (var item in Items)
{
<SelectItem Item="@item" ItemClickCallback="ItemClickCallback"></SelectItem>
}
</div>
</div>
</div>

View File

@ -1,9 +1,9 @@
@typeparam TItem
@inherits SubmitModalBase<TItem>
<Modal Id="@Id" Title="Title" Size="Size">
<Modal Id="@Id" Title="@Title" Size="@Size">
<ModalBody>
<LgbEditForm class="form-inline" Model="Model" OnValidSubmit="OnValidSubmit" OnInvalidSubmit="OnInvalidSubmit" OnSubmit="OnSubmit">
<LgbEditForm class="form-inline" Id="@Id" Model="Model" OnValidSubmit="OnValidSubmit" OnInvalidSubmit="OnInvalidSubmit" OnSubmit="OnSubmit">
@ModalBody
</LgbEditForm>
</ModalBody>

View File

@ -79,12 +79,10 @@
</ModalFooter>
</Modal>
<SubmitModal @ref="EditModal" Id="@($"{Id}_submit")" TItem="TItem" Title="字典编辑窗口" Size="ModalSize.ExtraLarge" @bind-Model="EditModel" OnValidSubmit="Save">
<SubmitModal @ref="EditModal" Id="@($"{Id}_edit")" TItem="TItem" Title="字典编辑窗口" Size="ModalSize.ExtraLarge" @bind-Model="EditModel" OnValidSubmit="Save">
<ModalBody>
@EditTemplate?.Invoke(EditModel)
</ModalBody>
</SubmitModal>
@code {
}
<Toast @ref="Toast"></Toast>

View File

@ -87,6 +87,7 @@ namespace Bootstrap.Admin
{
if (Enviroment.IsDevelopment()) options.DetailedErrors = true;
});
services.AddDisplayNames();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -215,8 +215,3 @@ nav .dropdown .nav-link-close.dropdown-toggle:after {
margin-bottom: 15px;
font-size: 1.125rem;
}
/* Validation */
input[type="text"].valid, input[type="password"].valid {
padding-right: 30px;
}

View File

@ -109,14 +109,8 @@
initDocument: function () {
$('body').removeClass('trans-mute');
$('[data-toggle="tooltip"]').tooltip();
},
initSidebar: function () {
$('.sidebar').addNiceScroll().autoScrollSidebar();
},
enableBackground: function (val) {
if (val) $('.main-content').addClass('welcome-bg').find('nav').addClass('d-none').removeClass('d-flex');
else $('.main-content').removeClass('welcome-bg').find('nav').addClass('d-flex').removeClass('d-none');
},
initModal: function () {
$('.modal').appendTo($('body'));
},
@ -130,7 +124,12 @@
$('.toast').toast('show');
},
tooltip: function (id, method) {
$(id).tooltip(method);
var $ele = $(id);
if (method === 'enable') {
$ele.tooltip();
$ele.parents('form').find('.invalid:first').focus();
}
else $ele.tooltip(method);
},
submitForm: function (btn) {
$(btn).parent().prev().find('form :submit').click();