!49 增加功能:Blazor 框架下字典表维护功能完成
* feat: Query 组件支持泛型 * refactor: Dicts 组件移除不用的引用 * refactor: EditPage 组件增加 Id 保护 * doc: 增加程序注释 * feat: 增加验证控件扩展 * refactor: IValidateComponent 更改方法名 * refactor: 重构 checkbox 选中机制 * fix: 查询按钮过滤条件不生效问题 * fix: 修复查询区域按钮点击不能切换到首页数据问题 * feat: 完善保存功能 * refactor: Save 方法参数更改为实体类 * refactor: 优化每页显示数据算法 * fix: 修复新建数据项时清除已选数据功能 * feat: 完善删除功能 * feat: 查询后清楚已选条目 * feat: 修复编辑按钮功能 * feat: 增加行号设置 * refactor: 重构分页组件移除 Table 组件对其引用 * refactor: 重构 Dicts 页面组件化 * feat: 增加 Validate 功能 * refactor: 查询方法增加默认参数值 * feat: 增加删除确认功能 * style: 微调表格内按钮高度 * style: 微调 gear 样式 * fix: 修复表格数据删除后仍然可编辑 * feat: 增加 Table 表格内按钮组 * refactor: 增加删除数据是否成功提示 * feat: 增加 Toast 组件 * feat: 增加 Alert 控件 * feat: 增加保存功能 * feat: 表格首列设置为 checkbox 功能 * feat: 增加选择列
This commit is contained in:
parent
c824c008fc
commit
93af3948be
|
@ -22,6 +22,11 @@ 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>
|
||||
|
@ -47,5 +52,13 @@ namespace Bootstrap.Admin.Components
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// 弹窗组件基类
|
||||
/// </summary>
|
||||
public class AlertBase : ModalBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? AlertBody { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 弹窗 Footer 代码块
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? AlertFooter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否自动关闭 默认为 true
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool AutoClose { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 自动关闭时长 默认 1500 毫秒
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int Interval { get; set; } = 1500;
|
||||
|
||||
/// <summary>
|
||||
/// 控件渲染完毕后回调方法
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
base.OnAfterRender(firstRender);
|
||||
|
||||
if (_show)
|
||||
{
|
||||
_show = false;
|
||||
Toggle();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _show;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="title"></param>
|
||||
public void Show(string title)
|
||||
{
|
||||
Title = title;
|
||||
_show = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using System;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// BootstrapAdminDataAnnotationsValidator 验证组件
|
||||
/// </summary>
|
||||
public class BootstrapAdminDataAnnotationsValidator : ComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 当前编辑数据上下文
|
||||
/// </summary>
|
||||
[CascadingParameter]
|
||||
EditContext? CurrentEditContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 当前编辑窗体上下文
|
||||
/// </summary>
|
||||
[CascadingParameter]
|
||||
public LgbEditFormBase? EditForm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化方法
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class BootstrapAdminEditContextDataAnnotationsExtensions
|
||||
{
|
||||
private static ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo> _propertyInfoCache = new ConcurrentDictionary<(Type, string), PropertyInfo>();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="editContext">The <see cref="EditContext"/>.</param>
|
||||
/// <param name="editForm"></param>
|
||||
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<ValidationResult>();
|
||||
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<ValidationResult>();
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -26,5 +26,13 @@ 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// CheckBox 组件状态枚举值
|
||||
/// </summary>
|
||||
public enum CheckBoxState
|
||||
{
|
||||
/// <summary>
|
||||
/// 未选中
|
||||
/// </summary>
|
||||
UnChecked,
|
||||
/// <summary>
|
||||
/// 选中
|
||||
/// </summary>
|
||||
Checked,
|
||||
/// <summary>
|
||||
/// 混合模式
|
||||
/// </summary>
|
||||
Mixed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class CheckBoxStateExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Checkbox 组件基类
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem"></typeparam>
|
||||
public class CheckboxBase<TItem> : ComponentBase
|
||||
{
|
||||
#nullable disable
|
||||
/// <summary>
|
||||
/// 获得/设置 数据绑定项
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public TItem Item { get; set; }
|
||||
#nullable restore
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 显示文本
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否被选中
|
||||
/// </summary>
|
||||
protected bool Checked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 勾选回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action<TItem, bool> ToggleCallback { get; set; } = new Action<TItem, bool>((v, c) => { });
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<TItem, CheckBoxState> SetCheckCallback { get; set; } = new Func<TItem, CheckBoxState>(item => CheckBoxState.UnChecked);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
State = SetCheckCallback(Item);
|
||||
Checked = State == CheckBoxState.Checked;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public CheckBoxState State { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum Color
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Primary,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Secondary,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Success,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Danger,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Warning,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Info,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Light,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Dark,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
White,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Transparent
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class ColorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="color"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToCss(this Color color, string prefix = "")
|
||||
{
|
||||
string ret = color.ToString().ToLowerInvariant();
|
||||
return string.IsNullOrEmpty(prefix) ? ret : $"{prefix}-{ret}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using Bootstrap.Security;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// 字典表维护组件
|
||||
/// </summary>
|
||||
public class DictsBase : ComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected BootstrapDict QueryModel { get; set; } = new BootstrapDict() { Define = -1 };
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected List<SelectedItem> DefineItems { get; set; } = new List<SelectedItem>(new SelectedItem[] { new SelectedItem() { Text = "系统使用", Value = "0" }, new SelectedItem() { Text = "自定义", Value = "1" } });
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected List<SelectedItem> QueryDefine { get; set; } = new List<SelectedItem>(new SelectedItem[] { new SelectedItem() { Text = "全部", Value = "-1", Active = true }, new SelectedItem() { Text = "系统使用", Value = "0" }, new SelectedItem() { Text = "自定义", Value = "1" } });
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageItems"></param>
|
||||
protected QueryData<BootstrapDict> 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<BootstrapDict>() { Items = items, TotalCount = totalCount, PageIndex = pageIndex, PageItems = pageItems };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected BootstrapDict Add()
|
||||
{
|
||||
return new BootstrapDict();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected bool Save(BootstrapDict dict) => DataAccess.DictHelper.Save(dict);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected bool Delete(IEnumerable<BootstrapDict> items) => DataAccess.DictHelper.Delete(items.Select(item => item.Id ?? ""));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
using Bootstrap.Admin.Shared;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// 可编辑页面组件包含查询与数据表格
|
||||
/// </summary>
|
||||
public class EditPageBase<TItem> : BootstrapComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
#nullable disable
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public TItem QueryModel { get; set; }
|
||||
#nullable restore
|
||||
|
||||
/// <summary>
|
||||
/// 查询模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<TItem>? QueryBody { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 查询按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<int, int, QueryData<TItem>>? OnQuery { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? TableHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<TItem>? RowTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 按钮模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<TItem>? ButtonTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? TableFooter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<TItem>? EditTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected Table<TItem>? Table { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 新建按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<TItem>? OnAdd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 编辑按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action<TItem>? OnEdit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 保存按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<TItem, bool>? OnSave { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 删除按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<IEnumerable<TItem>, bool>? OnDelete { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组件初始化方法
|
||||
/// </summary>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Id)) throw new InvalidOperationException($"The property {nameof(Id)} can't set to Null");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据表格内删除按钮方法
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
protected void Delete(TItem item)
|
||||
{
|
||||
if (Table != null)
|
||||
{
|
||||
Table.SelectedItems.Clear();
|
||||
Table.SelectedItems.Add(item);
|
||||
Table.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected void Edit(TItem item)
|
||||
{
|
||||
if (Table != null)
|
||||
{
|
||||
Table.SelectedItems.Clear();
|
||||
Table.SelectedItems.Add(item);
|
||||
Table.Edit();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected void Query()
|
||||
{
|
||||
// 查询控件按钮触发此事件
|
||||
if (OnQuery != null && Table != null)
|
||||
{
|
||||
Table.Query(OnQuery.Invoke(1, Table.PageItems));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageItems"></param>
|
||||
/// <returns></returns>
|
||||
protected QueryData<TItem> QueryData(int pageIndex, int pageItems) => OnQuery?.Invoke(pageIndex, pageItems) ?? new QueryData<TItem>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// IValidComponent 接口
|
||||
/// </summary>
|
||||
public interface IValidateComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据验证方法
|
||||
/// </summary>
|
||||
/// <param name="propertyValue"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="results"></param>
|
||||
void ValidateProperty(object? propertyValue, ValidationContext context, List<ValidationResult> results);
|
||||
|
||||
/// <summary>
|
||||
/// 显示或者隐藏提示信息方法
|
||||
/// </summary>
|
||||
/// <param name="results"></param>
|
||||
/// <param name="validProperty"></param>
|
||||
void ToggleMessage(IEnumerable<ValidationResult> results, bool validProperty);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// LgbEditForm 组件
|
||||
/// </summary>
|
||||
public class LgbEditFormBase : ComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of additional attributes that will be applied to the created <c>form</c> element.
|
||||
/// </summary>
|
||||
[Parameter(CaptureUnmatchedValues = true)]
|
||||
public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="EditContext"/>.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public object? Model { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the content to be rendered inside this
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="EditContext.Validate"/>.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<EditContext> OnSubmit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A callback that will be invoked when the form is submitted and the
|
||||
/// <see cref="EditContext"/> is determined to be valid.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<EditContext> OnValidSubmit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A callback that will be invoked when the form is submitted and the
|
||||
/// <see cref="EditContext"/> is determined to be invalid.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<EditContext> OnInvalidSubmit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 验证组件缓存 静态全局提高性能
|
||||
/// </summary>
|
||||
private static ConcurrentDictionary<(Type ModelType, string FieldName), IValidateComponent> _validatorCache = new ConcurrentDictionary<(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);
|
||||
|
||||
/// <summary>
|
||||
/// EditModel 数据模型验证方法
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="results"></param>
|
||||
public void ValidateObject(object model, ValidationContext context, List<ValidationResult> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字段验证方法
|
||||
/// </summary>
|
||||
/// <param name="propertyValue"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="results"></param>
|
||||
public void ValidateProperty(object? propertyValue, ValidationContext context, List<ValidationResult> results)
|
||||
{
|
||||
if (_validatorCache.TryGetValue((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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,18 @@ namespace Bootstrap.Admin.Components
|
|||
[Inject]
|
||||
protected IJSRuntime? JSRuntime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ModalBody { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 弹窗 Footer 代码块
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ModalFooter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
@ -28,22 +40,28 @@ namespace Bootstrap.Admin.Components
|
|||
public string Title { get; set; } = "未设置";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ModalBody { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// 获得/设置 是否允许点击后台关闭弹窗 默认为 false
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool Backdrop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// 获得/设置 弹窗大小
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ModalFooter { get; set; }
|
||||
public ModalSize Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否垂直居中 默认为 true
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsCentered { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否显示 Footer 默认为 true
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowFooter { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
@ -56,5 +74,60 @@ namespace Bootstrap.Admin.Components
|
|||
JSRuntime.InitModal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 输出窗口大小样式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void Toggle()
|
||||
{
|
||||
JSRuntime.ToggleModal($"#{Id}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹窗大小
|
||||
/// </summary>
|
||||
public enum ModalSize
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Default,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Small,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Large,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
ExtraLarge,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 页码总数
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int PageCount { get; set; } = 0;
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 每页显示数据数量
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int PageItems { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 数据总数
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int ItemsCount { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 当前页码
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int PageIndex { get; set; } = 1;
|
||||
|
@ -36,25 +24,38 @@ namespace Bootstrap.Admin.Components
|
|||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Inject]
|
||||
protected IJSRuntime? JSRuntime { get; set; }
|
||||
[Parameter]
|
||||
public int PageItems { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 页码总数
|
||||
/// </summary>
|
||||
public int PageCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)Math.Ceiling(TotalCount * 1.0 / PageItems); ;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Action<int> ClickPageCallback { get; set; } = new Action<int>(i => { });
|
||||
[Parameter]
|
||||
public Action<int, int> OnPageClick { get; set; } = new Action<int, int>((pageIndex, pageItems) => { });
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Action PageItemsChangeCallback { get; set; } = new Action(() => { });
|
||||
[Parameter]
|
||||
public Action<int> OnPageItemsChange { get; set; } = new Action<int>((pageItems) => { });
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected void MovePrev()
|
||||
{
|
||||
if (PageIndex > 1) ClickPageCallback(PageIndex - 1);
|
||||
if (PageIndex > 1) OnPageClick(PageIndex - 1, PageItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -62,7 +63,7 @@ namespace Bootstrap.Admin.Components
|
|||
/// </summary>
|
||||
protected void MoveNext()
|
||||
{
|
||||
if (PageIndex < PageCount) ClickPageCallback(PageIndex + 1);
|
||||
if (PageIndex < PageCount) OnPageClick(PageIndex + 1, PageItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -72,7 +73,36 @@ namespace Bootstrap.Admin.Components
|
|||
protected void ClickItem(int pageItems)
|
||||
{
|
||||
PageItems = pageItems;
|
||||
PageItemsChangeCallback();
|
||||
OnPageItemsChange(PageItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected IEnumerable<int> GetPages()
|
||||
{
|
||||
var pages = new List<int>() { 20, 40, 80, 100, 200 };
|
||||
var ret = new List<int>();
|
||||
for (int i = 0; i < pages.Count; i++)
|
||||
{
|
||||
ret.Add(pages[i]);
|
||||
if (pages[i] >= TotalCount) break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="totalCount"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageItems"></param>
|
||||
public void Update(int totalCount, int pageIndex, int pageItems)
|
||||
{
|
||||
TotalCount = totalCount;
|
||||
PageIndex = pageIndex;
|
||||
PageItems = pageItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// 组件位置枚举类型
|
||||
/// </summary>
|
||||
public enum Placement
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Default,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Top,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
TopLeft,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
TopRight,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Bottom,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
BottomLeft,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
BottomRight,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Center
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class PlacementExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="placement"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <returns></returns>
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询组件
|
||||
/// </summary>
|
||||
public class QueryBase<TItem> : ComponentBase
|
||||
{
|
||||
private readonly string _defaultTitle = "查询条件";
|
||||
private readonly string _defaultText = "查询";
|
||||
|
||||
/// <summary>
|
||||
/// 查询组件标题 默认为 查询条件
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Title { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 查询按钮显示文字 默认为 查询
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 查询组件模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<TItem>? ChildContent { get; set; }
|
||||
|
||||
#nullable disable
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public TItem QueryModel { get; set; }
|
||||
#nullable restore
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback<TItem> QueryModelChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 查询按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action? OnQuery { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数设置方法
|
||||
/// </summary>
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Title)) Title = _defaultTitle;
|
||||
if (string.IsNullOrEmpty(Text)) Text = _defaultText;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class QueryData<T>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IEnumerable<T> Items { get; set; } = new T[0];
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int PageIndex { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int PageItems { get; set; } = 20;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class RequiredValidator : ValidatorComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 是否允许空字符串 默认 false 不允许
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool AllowEmptyString { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="propertyValue"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="results"></param>
|
||||
public override void Validate(object? propertyValue, ValidationContext context, List<ValidationResult> results)
|
||||
{
|
||||
var val = propertyValue?.ToString() ?? "";
|
||||
if (!AllowEmptyString && val == string.Empty)
|
||||
{
|
||||
results.Add(new ValidationResult(ErrorMessage, new string[] { context.MemberName }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +1,23 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Select 组件基类
|
||||
/// </summary>
|
||||
public class SelectBase : ComponentBase
|
||||
public class SelectBase<TItem> : ComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// 获得/设置 控件 ID
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// 获得/设置 背景显示文字
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string PlaceHolder { get; set; } = "请选择 ...";
|
||||
|
@ -29,49 +25,64 @@ namespace Bootstrap.Admin.Components
|
|||
/// <summary>
|
||||
/// 当前选择项实例
|
||||
/// </summary>
|
||||
public SelectItemBase? SelectedItem { get; set; }
|
||||
public SelectedItem SelectedItem { get; set; } = new SelectedItem();
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 绑定数据集
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public List<SelectedItem> Items { get; set; } = new List<SelectedItem>();
|
||||
|
||||
#nullable disable
|
||||
private TItem _value;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int SelectedValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback<int> SelectedValueChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Action<SelectItemBase>? ClickItemCallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Action<SelectItemBase>? ActiveChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
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
|
||||
|
||||
///<summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback<TItem> SelectedValueChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (!SelectedItem.Active)
|
||||
{
|
||||
SelectedItem = Items.FirstOrDefault(item => item.Active) ?? Items.First();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
protected void UpdateItem(SelectItemBase item)
|
||||
[Parameter]
|
||||
public Action<SelectedItem> SelectedItemChanged { get; set; } = new Action<SelectedItem>(v => { });
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void ItemClickCallback(SelectedItem item)
|
||||
{
|
||||
SelectedItemChanged(item);
|
||||
SelectedItem = item;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
|
|
@ -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
|
|||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
public SelectedItem Item { get; set; } = new SelectedItem();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Value { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[CascadingParameter]
|
||||
public Select? Select { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
if (Active) Select?.ActiveChanged?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected void ClickItem()
|
||||
{
|
||||
Select?.ClickItemCallback?.Invoke(this);
|
||||
}
|
||||
public Action<SelectedItem> ItemClickCallback { get; set; } = new Action<SelectedItem>(SelectedItem => { });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// 选项类
|
||||
/// </summary>
|
||||
public class SelectedItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 显示名称
|
||||
/// </summary>
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项值
|
||||
/// </summary>
|
||||
public string Value { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否选中
|
||||
/// </summary>
|
||||
public bool Active { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class StringLengthValidator : ValidatorComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int Length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="propertyValue"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="results"></param>
|
||||
public override void Validate(object? propertyValue, ValidationContext context, List<ValidationResult> results)
|
||||
{
|
||||
var val = propertyValue?.ToString() ?? "";
|
||||
if (val.Length > Length) results.Add(new ValidationResult(ErrorMessage, new string[] { context.MemberName }));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据绑定提交弹窗组件
|
||||
/// </summary>
|
||||
public class SubmitModalBase<TItem> : ModalBase
|
||||
{
|
||||
#nullable disable
|
||||
/// <summary>
|
||||
/// 获得/设置 弹窗绑定数据实体
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public TItem Model { get; set; }
|
||||
#nullable restore
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback<TItem> ModelChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="EditContext.Validate"/>.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<EditContext> OnSubmit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A callback that will be invoked when the form is submitted and the
|
||||
/// <see cref="EditContext"/> is determined to be valid.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<EditContext> OnValidSubmit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A callback that will be invoked when the form is submitted and the
|
||||
/// <see cref="EditContext"/> is determined to be invalid.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<EditContext> OnInvalidSubmit { get; set; }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 表格组件类
|
||||
/// </summary>
|
||||
public class TableBase<TItem> : ComponentBase
|
||||
public class TableBase<TItem> : BootstrapComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Inject]
|
||||
protected IJSRuntime? JSRuntime { get; set; }
|
||||
public const int DefaultPageItems = 20;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
@ -29,6 +35,18 @@ namespace Bootstrap.Admin.Components
|
|||
[Parameter]
|
||||
public RenderFragment<TItem>? RowTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 按钮模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<TItem>? ButtonTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<TItem>? EditTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
@ -38,30 +56,271 @@ namespace Bootstrap.Admin.Components
|
|||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected IEnumerable<TItem> Items { get; set; } = new TItem[0];
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public List<TItem> SelectedItems { get; } = new List<TItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否显示行号
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowLineNo { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否显示选择列 默认为 true
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowCheckbox { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否显示按钮列 默认为 true
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowButtons { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否显示工具栏 默认为 true
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowToolBar { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 按钮列 Header 文本 默认为 操作
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string ButtonTemplateHeaderText { get; set; } = "操作";
|
||||
|
||||
/// <summary>
|
||||
/// 点击翻页回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<int, int, QueryData<TItem>>? OnQuery { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 新建按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<TItem>? OnAdd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 编辑按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action<TItem>? OnEdit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 保存按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<TItem, bool>? OnSave { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 删除按钮回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<IEnumerable<TItem>, bool>? OnDelete { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public IEnumerable<TItem> Items { get; set; } = new TItem[0];
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Action AddCallback { get; set; } = new Action(() => { });
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Action EditCallback { get; set; } = new Action(() => { });
|
||||
[Parameter]
|
||||
public int PageIndex { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int PageItems { get; set; } = DefaultPageItems;
|
||||
|
||||
/// <summary>
|
||||
/// 确认删除弹窗
|
||||
/// </summary>
|
||||
protected Modal? ConfirmModal { get; set; }
|
||||
|
||||
#nullable disable
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected TItem EditModel { get; set; }
|
||||
#nullable restore
|
||||
|
||||
/// <summary>
|
||||
/// 编辑数据弹窗
|
||||
/// </summary>
|
||||
protected SubmitModal<TItem>? EditModal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageItems"></param>
|
||||
protected void PageClick(int pageIndex, int pageItems)
|
||||
{
|
||||
if (pageIndex != PageIndex)
|
||||
{
|
||||
PageIndex = pageIndex;
|
||||
PageItems = pageItems;
|
||||
Query();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected void PageItemsChange(int pageItems)
|
||||
{
|
||||
if (OnQuery != null)
|
||||
{
|
||||
PageIndex = 1;
|
||||
PageItems = pageItems;
|
||||
Query();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="check"></param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
protected CheckBoxState CheckState(TItem item)
|
||||
{
|
||||
var ret = CheckBoxState.UnChecked;
|
||||
if (SelectedItems.Count > 0)
|
||||
{
|
||||
ret = SelectedItems.Count == Items.Count() ? CheckBoxState.Checked : CheckBoxState.Mixed;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void Add()
|
||||
{
|
||||
if (OnAdd != null) EditModel = OnAdd.Invoke();
|
||||
SelectedItems.Clear();
|
||||
EditModal?.Toggle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void Edit()
|
||||
{
|
||||
if (SelectedItems.Count == 1)
|
||||
{
|
||||
EditModel = SelectedItems[0];
|
||||
EditModal?.Toggle();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowMessage("编辑数据", "请选择一个要编辑的数据", ToastCategory.Information);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新查询数据
|
||||
/// </summary>
|
||||
/// <param name="queryData"></param>
|
||||
public void Query(QueryData<TItem> queryData)
|
||||
{
|
||||
SelectedItems.Clear();
|
||||
PageIndex = queryData.PageIndex;
|
||||
Items = queryData.Items;
|
||||
TotalCount = queryData.TotalCount;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void Query()
|
||||
{
|
||||
if (OnQuery != null) Query(OnQuery.Invoke(PageIndex, PageItems));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除按钮方法
|
||||
/// </summary>
|
||||
public void Delete()
|
||||
{
|
||||
if (SelectedItems.Count > 0)
|
||||
{
|
||||
ConfirmModal?.Toggle();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowMessage("删除数据", "请选择要删除的数据", ToastCategory.Information);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认删除方法
|
||||
/// </summary>
|
||||
public void Confirm()
|
||||
{
|
||||
var result = OnDelete?.Invoke(SelectedItems) ?? false;
|
||||
if (result)
|
||||
{
|
||||
ConfirmModal?.Toggle();
|
||||
Query();
|
||||
}
|
||||
ShowMessage("删除数据", "删除数据" + (result ? "成功" : "失败"), result ? ToastCategory.Success : ToastCategory.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
using Bootstrap.Admin.Extensions;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Toast 组件基类
|
||||
/// </summary>
|
||||
public class ToastBase : ComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// IJSRuntime 实例
|
||||
/// </summary>
|
||||
[Inject]
|
||||
protected IJSRuntime? JSRuntime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否自动隐藏 默认为 true
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool AutoHide { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 自动隐藏延时 默认为 1500 毫秒
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int Interval { get; set; } = 2000;
|
||||
|
||||
/// <summary>
|
||||
/// 组件显示位置
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Placement Placement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Toast 类型
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public ToastCategory Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组件标题文本
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Title { get; set; } = "BootstrapAdmin Blazor";
|
||||
|
||||
/// <summary>
|
||||
/// 消息正文
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Text { get; set; } = "Toast 消息正文内容-未设置";
|
||||
|
||||
/// <summary>
|
||||
/// 控件呈现后回调方法
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
JSRuntime.InitToast();
|
||||
}
|
||||
if (_show)
|
||||
{
|
||||
_show = false;
|
||||
Show();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected void Show()
|
||||
{
|
||||
JSRuntime.ShowToast();
|
||||
}
|
||||
|
||||
private bool _show;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void ShowMessage(string title, string text, ToastCategory category = ToastCategory.Success)
|
||||
{
|
||||
Title = title;
|
||||
Text = text;
|
||||
Category = category;
|
||||
_show = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected string RenderAnimation()
|
||||
{
|
||||
return AutoHide ? $"transition: width {(Interval * 1.0 - 200) / 1000}s linear;" : "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Toast 组件类型
|
||||
/// </summary>
|
||||
public enum ToastCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
Success,
|
||||
/// <summary>
|
||||
/// 提示信息
|
||||
/// </summary>
|
||||
Information,
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bootstrap.Admin.Components
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public abstract class ValidatorComponentBase : ComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string ErrorMessage { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[CascadingParameter]
|
||||
public LgbInputText? Input { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化方法
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="propertyValue"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="results"></param>
|
||||
public abstract void Validate(object? propertyValue, ValidationContext context, List<ValidationResult> results);
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ namespace Bootstrap.Admin.Extensions
|
|||
/// 启用动画
|
||||
/// </summary>
|
||||
/// <param name="jSRuntime"></param>
|
||||
public static void EnableAnimation(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.enableAnimation");
|
||||
public static void EnableAnimation(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.initDocument");
|
||||
|
||||
/// <summary>
|
||||
/// 修复 Modal 组件
|
||||
|
@ -53,11 +53,31 @@ namespace Bootstrap.Admin.Extensions
|
|||
/// <param name="jSRuntime"></param>
|
||||
public static void InitModal(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.initModal");
|
||||
|
||||
/// <summary>
|
||||
/// 修复 Modal 组件
|
||||
/// </summary>
|
||||
/// <param name="jSRuntime"></param>
|
||||
public static void InitToast(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.initToast");
|
||||
|
||||
/// <summary>
|
||||
/// 弹出 Modal 组件
|
||||
/// </summary>
|
||||
/// <param name="jSRuntime"></param>
|
||||
/// <param name="modalId"></param>
|
||||
public static void ToggleModal(this IJSRuntime? jSRuntime, string modalId) => jSRuntime.InvokeVoidAsync("$.toggleModal", modalId);
|
||||
|
||||
/// <summary>
|
||||
/// 弹出 Toast 组件
|
||||
/// </summary>
|
||||
/// <param name="jSRuntime"></param>
|
||||
public static void ShowToast(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.showToast");
|
||||
|
||||
/// <summary>
|
||||
/// 弹出 Tooltip 组件
|
||||
/// </summary>
|
||||
/// <param name="jSRuntime"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="method"></param>
|
||||
public static void Tooltip(this IJSRuntime? jSRuntime, string id, string method) => jSRuntime.InvokeVoidAsync("$.tooltip", $"#{id}", method);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,177 +1,56 @@
|
|||
@inject IJSRuntime JSRuntime
|
||||
@inherits DictsBase
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">查询条件</div>
|
||||
<div class="card-body">
|
||||
<form class="form-inline">
|
||||
<div class="row">
|
||||
<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" id="txt_dict_name" />
|
||||
</div>
|
||||
<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" id="txt_dict_cate" data-provide="typeahead" />
|
||||
</div>
|
||||
<div class="form-group col-sm-6 col-md-auto">
|
||||
<label class="control-label" for="txt_dict_define">字典类别</label>
|
||||
<Select @ref="DictCate">
|
||||
<SelectItem Text="全部" Value="" Active="true"></SelectItem>
|
||||
<SelectItem Text="系统使用" Value="0"></SelectItem>
|
||||
<SelectItem Text="自定义" Value="1"></SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
<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 => Query(1)"><i class="fa fa-search" aria-hidden="true"></i><span>查询</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
查询结果
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="bootstrap-table">
|
||||
<Table @ref="Table" Items="Items" TItem="Bootstrap.Security.BootstrapDict">
|
||||
<TableHeader>
|
||||
<th>字典标签</th>
|
||||
<th>字典名称</th>
|
||||
<th>字典代码</th>
|
||||
<th>字典类型</th>
|
||||
</TableHeader>
|
||||
<RowTemplate>
|
||||
<td>@context.Category</td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Code</td>
|
||||
<td>@context.Define</td>
|
||||
</RowTemplate>
|
||||
</Table>
|
||||
<Pagination @ref="Pagination" PageCount="PageCount" PageIndex="PageIndex" ItemsCount="ItemsCount"></Pagination>
|
||||
<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>
|
||||
</div>
|
||||
<div id="tableButtons" class="d-none">
|
||||
<div class='btn-group'>
|
||||
<button class='edit btn btn-sm btn-success' asp-auth="edit"><i class='fa fa-edit'></i><span>编辑</span></button>
|
||||
<button class='del btn btn-sm btn-danger' asp-auth="del"><i class='fa fa-remove'></i><span>删除</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal Id="DialogDict" Title="字典编辑窗口">
|
||||
<ModalBody>
|
||||
<form class="form-inline">
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-6">
|
||||
<label class="control-label" for="dictCate">字典标签</label>
|
||||
<input type="text" class="form-control" id="dictCate" @bind="Model.Category" placeholder="不可为空,50字以内" maxlength="50" data-valid="true" />
|
||||
</div>
|
||||
<div class="form-group col-sm-6">
|
||||
<label class="control-label" for="dictDefine">字典类型</label>
|
||||
<Select Id="dictDefine" @bind-SelectedValue="Model.Define">
|
||||
<SelectItem Text="系统使用" Value="0" Active="true"></SelectItem>
|
||||
<SelectItem Text="自定义" Value="1"></SelectItem>
|
||||
</Select>
|
||||
|
||||
</div>
|
||||
<div class="form-group col-sm-6">
|
||||
<label class="control-label" for="dictName">字典名称</label>
|
||||
<input type="text" class="form-control" id="dictName" @bind="Model.Name" placeholder="不可为空,50字以内" maxlength="50" data-valid="true" />
|
||||
</div>
|
||||
<div class="form-group col-sm-6">
|
||||
<label class="control-label" for="dictCode">字典代码</label>
|
||||
<input type="text" class="form-control" id="dictCode" @bind="Model.Code" placeholder="不可为空,2000字以内" maxlength="2000" data-valid="true" />
|
||||
</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>
|
||||
</QueryBody>
|
||||
<TableHeader>
|
||||
<th>字典标签</th>
|
||||
<th>字典名称</th>
|
||||
<th>字典代码</th>
|
||||
<th>字典类型</th>
|
||||
</TableHeader>
|
||||
<RowTemplate>
|
||||
<td>@context.Category</td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Code</td>
|
||||
<td>@context.Define</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>
|
||||
</form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i>
|
||||
<span>关闭</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" @onclick="Save">
|
||||
<i class="fa fa-save"></i>
|
||||
<span>保存</span>
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public IEnumerable<BootstrapDict> 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<BootstrapDict>? 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");
|
||||
}
|
||||
}
|
||||
<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>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditPage>
|
||||
|
|
|
@ -17,23 +17,16 @@
|
|||
<environment include="Development">
|
||||
<link href="~/lib/twitter-bootstrap/css/bootstrap.css" rel="stylesheet" />
|
||||
<link href="~/lib/font-awesome/css/font-awesome.css" rel="stylesheet" />
|
||||
<link href="~/lib/sweetalert/sweetalert2.css" rel="stylesheet" />
|
||||
<link href="~/lib/toastr.js/toastr.css" rel="stylesheet" />
|
||||
<link href="~/lib/nprogress/nprogress.css" rel="stylesheet" />
|
||||
<link href="~/lib/sweetalert/sweetalert2.css" rel="stylesheet" />
|
||||
<link href="~/lib/overlayscrollbars/OverlayScrollbars.css" rel="stylesheet" />
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<link href="~/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/font-awesome/css/font-awesome.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/sweetalert/sweetalert2.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/toastr.js/toastr.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/nprogress/nprogress.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/sweetalert/sweetalert2.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/overlayscrollbars/OverlayScrollbars.min.css" rel="stylesheet" />
|
||||
</environment>
|
||||
<link href="~/lib/captcha/slidercaptcha.css" rel="stylesheet" />
|
||||
<link href="~/lib/longbow-select/longbow-select.css" rel="stylesheet" />
|
||||
<link href="~/lib/longbow-checkbox/longbow-checkbox.css" rel="stylesheet" />
|
||||
<link href="~/css/theme.css" rel="stylesheet" asp-append-version="true" />
|
||||
<link href="~/css/theme-responsive.css" rel="stylesheet" asp-append-version="true" />
|
||||
<link href="~/css/site.css" rel="stylesheet" asp-append-version="true" />
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
<Section ShowCardTitle="@RootLayout.Model.ShowCardTitle" LockScreenPeriod=@RootLayout.Model.LockScreenPeriod>
|
||||
<TabSet @ref="TabSet"></TabSet>
|
||||
</Section>
|
||||
<Toast @ref="Toast"></Toast>
|
||||
</CascadingValue>
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
@inherits AlertBase
|
||||
|
||||
<Modal Backdrop="@Backdrop" IsCentered="@IsCentered" Size="@Size" Id="@Id" Title="@Title" ShowFooter="@ShowFooter">
|
||||
<ModalBody>
|
||||
@AlertBody
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
@AlertFooter
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
@typeparam TItem
|
||||
@inherits CheckboxBase<TItem>
|
||||
|
||||
<label role="checkbox" aria-checked="@State.ToCss()" class="@RenderStateCss()" @onclick="e => ToggleCallback(Item, !Checked)">
|
||||
<span class="checkbox-input">
|
||||
<span class="checkbox-inner"></span>
|
||||
</span>
|
||||
<span class="checkbox-label">@Text</span>
|
||||
</label>
|
|
@ -0,0 +1,31 @@
|
|||
@typeparam TItem
|
||||
@inherits EditPageBase<TItem>
|
||||
|
||||
<Query OnQuery="Query" TItem="TItem" @bind-QueryModel="QueryModel">
|
||||
@QueryBody?.Invoke(context)
|
||||
</Query>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
查询结果
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<Table @ref="Table" Id="@Id" TItem="TItem" OnQuery="QueryData" OnAdd="OnAdd" OnDelete="OnDelete" OnSave="OnSave">
|
||||
<TableHeader>
|
||||
@TableHeader
|
||||
</TableHeader>
|
||||
<RowTemplate>
|
||||
@RowTemplate?.Invoke(context)
|
||||
</RowTemplate>
|
||||
<ButtonTemplate>
|
||||
<div class='btn-group'>
|
||||
<button class='btn btn-sm btn-success' asp-auth="edit" @onclick="e => Edit(context)"><i class='fa fa-edit'></i><span>编辑</span></button>
|
||||
<button class='btn btn-sm btn-danger' asp-auth="del" @onclick="e => Delete(context)"><i class='fa fa-remove'></i><span>删除</span></button>
|
||||
@ButtonTemplate
|
||||
</div>
|
||||
</ButtonTemplate>
|
||||
<EditTemplate>
|
||||
@EditTemplate?.Invoke(context)
|
||||
</EditTemplate>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
@inherits LgbEditFormBase
|
||||
|
||||
<CascadingValue Value="this">
|
||||
<EditForm Model="@Model" AdditionalAttributes="@AdditionalAttributes" OnSubmit="OnSubmit" OnValidSubmit="OnValidSubmit" OnInvalidSubmit="OnInvalidSubmit">
|
||||
<BootstrapAdminDataAnnotationsValidator />
|
||||
@ChildContent
|
||||
<button type="submit" class="d-none">submit</button>
|
||||
</EditForm>
|
||||
</CascadingValue>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
@inherits ModalBase
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="@Id" tabindex="-1" role="dialog" aria-labelledby="modal_@Id" aria-hidden="true" data-backdrop="@(Backdrop ? "none" : "static")">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="@(IsCentered ? "modal-dialog modal-dialog-centered" : "modal-dialog") @RenderModalSize()" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modal_@Id">@Title</h5>
|
||||
|
@ -13,9 +12,12 @@
|
|||
<div class="modal-body">
|
||||
@ModalBody
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@ModalFooter
|
||||
</div>
|
||||
@if (ShowFooter)
|
||||
{
|
||||
<div class="modal-footer">
|
||||
@ModalFooter
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,18 +3,19 @@
|
|||
<CascadingValue Value="this">
|
||||
<nav class="d-flex align-items-center" aria-label="分页组件">
|
||||
<div class="pagination-bar">
|
||||
显示第 <span>@((PageIndex - 1) * PageItems + 1)</span> 到第 <span>@(Math.Min(PageIndex * PageItems, ItemsCount))</span> 条记录,总共 <span>@ItemsCount</span> 条记录 每页显示
|
||||
显示第 <span>@((PageIndex - 1) * PageItems + 1)</span> 到第 <span>@(Math.Min(PageIndex * PageItems, TotalCount))</span> 条记录,总共 <span>@TotalCount</span> 条记录 每页显示
|
||||
<div class="btn-group dropup">
|
||||
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">@PageItems</button>
|
||||
<div class="dropdown-menu">
|
||||
<div class="@(PageItems == 20 ? "dropdown-item active" : "dropdown-item")" @onclick="@(e => ClickItem(20))">20</div>
|
||||
<div class="@(PageItems == 40 ? "dropdown-item active" : "dropdown-item")" @onclick="@(e => ClickItem(40))">40</div>
|
||||
<div class="@(PageItems == 80 ? "dropdown-item active" : "dropdown-item")" @onclick="@(e => ClickItem(80))">80</div>
|
||||
@foreach (var page in GetPages())
|
||||
{
|
||||
<div class="@(PageItems == page ? "dropdown-item active" : "dropdown-item")" @onclick="@(e => ClickItem(page))">@(page.ToString())</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
条记录
|
||||
</div>
|
||||
<ul class="@(PageCount == 1 ? "pagination d-none" : "pagination")">
|
||||
<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++)
|
||||
{
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
///
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int PageIndex { get; set; } = 0;
|
||||
public int PageIndex { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected void ClickPage()
|
||||
{
|
||||
Pagination?.ClickPageCallback?.Invoke(PageIndex);
|
||||
Pagination?.OnPageClick?.Invoke(PageIndex, Pagination.PageItems);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
@typeparam TItem
|
||||
@inherits QueryBase<TItem>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">@Title</div>
|
||||
<div class="card-body">
|
||||
<div class="form-inline">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,13 +1,15 @@
|
|||
@inherits SelectBase
|
||||
@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">
|
||||
<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">
|
||||
<CascadingValue Value="this">
|
||||
@ChildContent
|
||||
</CascadingValue>
|
||||
@foreach (var item in Items)
|
||||
{
|
||||
<SelectItem Item="@item" ItemClickCallback="ItemClickCallback"></SelectItem>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@inherits SelectItemBase
|
||||
|
||||
<div class="@(Active ? "dropdown-item active" : "dropdown-item")" data-val="@Value" @onclick="ClickItem">@Text</div>
|
||||
<div class="@(Item.Active ? "dropdown-item active" : "dropdown-item")" data-val="@Item.Value" @onclick="@(e=>ItemClickCallback(Item))">@Item.Text</div>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
@typeparam TItem
|
||||
@inherits SubmitModalBase<TItem>
|
||||
|
||||
<Modal Id="@Id" Title="Title" Size="Size">
|
||||
<ModalBody>
|
||||
<LgbEditForm class="form-inline" Model="Model" OnValidSubmit="OnValidSubmit" OnInvalidSubmit="OnInvalidSubmit" OnSubmit="OnSubmit">
|
||||
@ModalBody
|
||||
</LgbEditForm>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i>
|
||||
<span>关闭</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="$.submitForm(this)">
|
||||
<i class="fa fa-save"></i>
|
||||
<span>保存</span>
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
|
@ -1,36 +1,89 @@
|
|||
@typeparam TItem
|
||||
@inherits TableBase<TItem>
|
||||
|
||||
<div class="bootstrap-table">
|
||||
<div class="@(ShowToolBar ? "bs-bars" : "bs-bars d-none")">
|
||||
<div class="toolbar btn-group">
|
||||
<button id="btn_add" type="button" @onclick="(e => AddCallback())" class="btn btn-success" asp-auth="add"><i class="fa fa-plus" aria-hidden="true"></i><span>新增</span></button>
|
||||
<button id="btn_delete" type="button" @onclick="Delete" class="btn btn-danger" asp-auth="del"><i class="fa fa-remove" aria-hidden="true"></i><span>删除</span></button>
|
||||
<button id="btn_edit" type="button" @onclick="(e => EditCallback())" class="btn btn-primary" asp-auth="edit"><i class="fa fa-pencil" aria-hidden="true"></i><span>编辑</span></button>
|
||||
<button type="button" @onclick="Add" class="btn btn-success" asp-auth="add"><i class="fa fa-plus" aria-hidden="true"></i><span>新增</span></button>
|
||||
<button type="button" @onclick="Delete" class="btn btn-danger" asp-auth="del"><i class="fa fa-remove" aria-hidden="true"></i><span>删除</span></button>
|
||||
<button type="button" @onclick="Edit" class="btn btn-primary" asp-auth="edit"><i class="fa fa-pencil" aria-hidden="true"></i><span>编辑</span></button>
|
||||
</div>
|
||||
<div class="gear btn-group">
|
||||
<button class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" type="button"><i class="fa fa-gear"></i></button>
|
||||
<div class="dropdown-menu">
|
||||
<div id="tb_add" title="新增" @onclick="(e => AddCallback())" asp-auth="add"><i class="fa fa-plus"></i></div>
|
||||
<div id="tb_delete" title="删除" @onclick="Delete" asp-auth="del"><i class="fa fa-remove"></i></div>
|
||||
<div id="tb_edit" title="编辑" @onclick="(e => EditCallback())" asp-auth="edit"><i class="fa fa-pencil"></i></div>
|
||||
<div class="dropdown-item" title="新增" @onclick="Add" asp-auth="add"><i class="fa fa-plus"></i></div>
|
||||
<div class="dropdown-item" title="删除" @onclick="Delete" asp-auth="del"><i class="fa fa-remove"></i></div>
|
||||
<div class="dropdown-item" title="编辑" @onclick="Edit" asp-auth="edit"><i class="fa fa-pencil"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>@TableHeader</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Items)
|
||||
{
|
||||
<tr>@RowTemplate?.Invoke(item)</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>@TableFooter</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<table class="table table-striped table-bordered table-hover table-selected">
|
||||
<thead>
|
||||
<tr>
|
||||
@if (ShowLineNo)
|
||||
{
|
||||
<th class="table-col-lineno">行号</th>
|
||||
}
|
||||
@if (ShowCheckbox)
|
||||
{
|
||||
<th class="table-col-checkbox"><Checkbox TItem="TItem" SetCheckCallback="CheckState" ToggleCallback="ToggleCheck"></Checkbox></th>
|
||||
}
|
||||
@TableHeader
|
||||
@if (ShowButtons)
|
||||
{
|
||||
<th>@ButtonTemplateHeaderText</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (int index = 0; index < Items.Count(); index++)
|
||||
{
|
||||
<tr>
|
||||
@if (ShowLineNo)
|
||||
{
|
||||
<td class="table-col-lineno">@(index + 1 + (PageIndex - 1) * PageItems)</td>
|
||||
}
|
||||
@if (ShowCheckbox)
|
||||
{
|
||||
<td class="table-col-checkbox"><Checkbox TItem="TItem" Item="Items.ElementAt(index)" SetCheckCallback="item => SelectedItems.Contains(item) ? CheckBoxState.Checked : CheckBoxState.UnChecked" ToggleCallback="ToggleCheck"></Checkbox></td>
|
||||
}
|
||||
@RowTemplate?.Invoke(Items.ElementAt(index))
|
||||
@if (ShowButtons)
|
||||
{
|
||||
<td>@ButtonTemplate?.Invoke(Items.ElementAt(index))</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>@TableFooter</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<Pagination PageItems="PageItems" TotalCount="TotalCount" PageIndex="PageIndex" OnPageClick="PageClick" OnPageItemsChange="PageItemsChange"></Pagination>
|
||||
</div>
|
||||
|
||||
<Modal @ref="ConfirmModal" Id="@($"{Id}_confirm")" Title="数据删除">
|
||||
<ModalBody>
|
||||
<div class="modal-confirm-body">您确定要删除选中的所有数据吗?</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button type="button" class="btn btn-danger" @onclick="Confirm">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
<span>我要删除</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i>
|
||||
<span>取消</span>
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
<SubmitModal @ref="EditModal" Id="@($"{Id}_submit")" TItem="TItem" Title="字典编辑窗口" Size="ModalSize.ExtraLarge" @bind-Model="EditModel" OnValidSubmit="Save">
|
||||
<ModalBody>
|
||||
@EditTemplate?.Invoke(EditModel)
|
||||
</ModalBody>
|
||||
</SubmitModal>
|
||||
|
||||
@code {
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
@inherits ToastBase
|
||||
|
||||
<div class="toast fade @Placement.ToCss("toast")" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="@(AutoHide ? "true":"false")" data-delay="@Interval">
|
||||
<div class="toast-header">
|
||||
<div class="toast-bar"><i class="@RenderCategory()"></i></div>
|
||||
<strong class="mr-auto">@Title</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
@Text
|
||||
</div>
|
||||
<div class="toast-progress" style="@RenderAnimation()"></div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
body {
|
||||
color: #797979;
|
||||
background: #f1f2f7;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue