555 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			555 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| #region copyright
 | ||
| //------------------------------------------------------------------------------
 | ||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | ||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | ||
| //  源代码使用协议遵循本仓库的开源协议及附加协议
 | ||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | ||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | ||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | ||
| //  QQ群:605534569
 | ||
| //------------------------------------------------------------------------------
 | ||
| #endregion
 | ||
| 
 | ||
| using Mapster;
 | ||
| 
 | ||
| using Masa.Blazor;
 | ||
| using Masa.Blazor.Presets;
 | ||
| 
 | ||
| using Microsoft.AspNetCore.Components.Web;
 | ||
| 
 | ||
| using System.Reflection;
 | ||
| 
 | ||
| using ThingsGateway.Admin.Core;
 | ||
| 
 | ||
| namespace ThingsGateway.Admin.Blazor.Core;
 | ||
| /// <summary>
 | ||
| /// 通用表格
 | ||
| /// </summary>
 | ||
| /// <typeparam name="TItem"></typeparam>
 | ||
| /// <typeparam name="SearchItem"></typeparam>
 | ||
| /// <typeparam name="AddItem"></typeparam>
 | ||
| /// <typeparam name="EditItem"></typeparam>
 | ||
| public partial class AppDataTable<TItem, SearchItem, AddItem, EditItem> : IAppDataTable
 | ||
|     where TItem : PrimaryIdEntity, new()
 | ||
|     where SearchItem : BasePageInput, new()
 | ||
|     where AddItem : class, new()
 | ||
|     where EditItem : class, new()
 | ||
| {
 | ||
| 
 | ||
|     private MDataTable<TItem> _table;
 | ||
| 
 | ||
|     private Dictionary<string, string> DetailModelPairs = new();
 | ||
|     /// <summary>
 | ||
|     /// 添加项委托
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public Func<AddItem, Task> AddCallAsync { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 获得/设置 添加模板
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public RenderFragment<AddItem> AddTemplate { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// MSheet.Class
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public string ClassString { get; set; }
 | ||
|     /// <summary>
 | ||
|     /// MSheet.Style
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public string StyleString { get; set; }
 | ||
|     /// <summary>
 | ||
|     /// 删除项委托
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public Func<IEnumerable<TItem>, Task> DeleteCallAsync { get; set; }
 | ||
|     /// <summary>
 | ||
|     /// 表格紧凑
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool Dense { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 编辑项委托
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public Func<EditItem, Task> EditCallAsync { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 获得/设置 编辑模板
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public RenderFragment<EditItem> EditTemplate { get; set; }
 | ||
|     /// <summary>
 | ||
|     /// 获得/设置 详情模板
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public RenderFragment<(DataTableHeader<TItem>, string)> Detailemplate { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 表头过滤,返回DataTableHeader列表,传输参数已包含全部初始表头与表头标题
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public Action<List<DataTableHeader<TItem>>> FilterHeaders { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 表头过滤之后执行的方法,返回Filter值,ture则显示,false则隐藏
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public Action<List<Filters>> Filters { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 获得/设置 Table Header 模板
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public RenderFragment<DataTableHeader> HeaderTemplate { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 右侧操作栏以菜单形式显示
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsMenuOperTemplate { get; set; } = true;
 | ||
|     /// <summary>
 | ||
|     /// 是否分页
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsPage { get; set; } = true;
 | ||
|     /// <summary>
 | ||
|     /// 是否显示添加按钮
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowAddButton { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 是否显示清空搜索
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowClearSearch { get; set; } = true;
 | ||
|     /// <summary>
 | ||
|     /// 是否显示删除按钮
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowDeleteButton { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 是否显示详情按钮
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowDetailButton { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 是否显示编辑按钮
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowEditButton { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 是否显示过滤
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowFilter { get; set; } = true;
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 是否显示右侧操作栏
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowOperCol { get; set; } = true;
 | ||
|     /// <summary>
 | ||
|     /// 是否显示查询按钮
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowQueryButton { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 是否显示搜索关键字
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowSearchKey { get; set; } = false;
 | ||
|     /// <summary>
 | ||
|     /// 是否显示表格多项选择
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowSelect { get; set; } = true;
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 是否显示顶部操作工具栏
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public bool IsShowToolbar { get; set; } = true;
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 获得/设置 Table Oper 模板
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public RenderFragment<ItemColProps<TItem>> ItemColOperTemplate { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 获得/设置 Table Cols 模板
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public RenderFragment<ItemColProps<TItem>> ItemColTemplate { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 独立设置 Table Cols 模板,需自行实现DateTime类型的时区转换
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public RenderFragment<ItemColProps<TItem>> ItemColWithDTTemplate { get; set; }
 | ||
| 
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 当前显示项目
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public IEnumerable<TItem> Items { get; set; } = new List<TItem>();
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 获得/设置 其他操作栏模板
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public RenderFragment<IEnumerable<TItem>> OtherToolbarTemplate { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 分页选择项目
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public List<PageSize> PageSizeItems { get; set; } = new List<PageSize>()
 | ||
|     {
 | ||
|         new PageSize(){Key="5",Value=5},
 | ||
|         new PageSize(){Key="10",Value=10},
 | ||
|         new PageSize(){Key="50",Value=50},
 | ||
|         new PageSize(){Key="100",Value=100}
 | ||
|      };
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 查询项委托
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public Func<SearchItem, Task<SqlSugarPagedList<TItem>>> QueryCallAsync { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 获得/设置 SearchModel 实例
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public SearchItem SearchModel { get; set; }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 获得/设置 查询与操作栏模板
 | ||
|     /// </summary>
 | ||
|     [Parameter]
 | ||
|     public RenderFragment<SearchItem> SearchTemplate { get; set; }
 | ||
|     private AddItem AddModel { get; set; }
 | ||
|     private bool AddShow { get; set; }
 | ||
|     private bool DeleteLoading { get; set; }
 | ||
|     private TItem DetailModel { get; set; }
 | ||
|     private bool DetailShow { get; set; }
 | ||
|     private EditItem EditModel { get; set; }
 | ||
| 
 | ||
|     private bool EditShow { get; set; }
 | ||
| 
 | ||
|     private List<Filters> FilterHeaderString { get; set; } = new();
 | ||
| 
 | ||
|     private List<DataTableHeader<TItem>> headers = new();
 | ||
| 
 | ||
|     private List<DataTableHeader<TItem>> Headers { get; set; } = new();
 | ||
| 
 | ||
|     [Inject]
 | ||
|     private InitTimezone InitTimezone { get; set; }
 | ||
| 
 | ||
|     private int Page { get; set; }
 | ||
| 
 | ||
|     private SqlSugarPagedList<TItem> PageItems { get; set; } = new();
 | ||
|     private bool QueryLoading { get; set; }
 | ||
| 
 | ||
|     private MForm SearchForm { get; set; }
 | ||
| 
 | ||
|     private IEnumerable<TItem> selectedItem = new List<TItem>();
 | ||
| 
 | ||
|     private int size;
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     public async Task QueryClickAsync()
 | ||
|     {
 | ||
|         try
 | ||
|         {
 | ||
|             QueryLoading = true;
 | ||
|             StateHasChanged();
 | ||
|             PageItems = await QueryCallAsync.Invoke(SearchModel);
 | ||
|             Items = PageItems.Records;
 | ||
|             if (!IsPage)
 | ||
|             {
 | ||
|                 SearchModel.Size = PageItems.Total;
 | ||
|             }
 | ||
|         }
 | ||
|         catch (Exception ex)
 | ||
|         {
 | ||
|             await PopupService.EnqueueSnackbarAsync(ex, false);
 | ||
|         }
 | ||
|         finally
 | ||
|         {
 | ||
|             QueryLoading = false;
 | ||
|             await InvokeAsync(StateHasChanged);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     protected override async Task OnAfterRenderAsync(bool firstRender)
 | ||
|     {
 | ||
|         if (!firstRender)
 | ||
|         {
 | ||
|             if (Page != SearchModel.Current)
 | ||
|             {
 | ||
|                 Page = SearchModel.Current;
 | ||
|                 await QueryClickAsync();
 | ||
|             }
 | ||
|         }
 | ||
|         else
 | ||
|         {
 | ||
|             Page = SearchModel.Current;
 | ||
|             size = SearchModel.Size;
 | ||
|             if (!Items.Any())
 | ||
|                 await QueryClickAsync();
 | ||
|         }
 | ||
| 
 | ||
|         await base.OnAfterRenderAsync(firstRender);
 | ||
|     }
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     protected override async Task OnInitializedAsync()
 | ||
|     {
 | ||
|         SearchModel ??= new();
 | ||
| 
 | ||
|         var propertyInfos = typeof(TItem).GetPropertiesWithCache();
 | ||
|         var data = propertyInfos
 | ||
|             .Select(a => new
 | ||
|             {
 | ||
|                 propertyInfo = a,
 | ||
|                 datatable = a.GetCustomAttribute<DataTableAttribute>()
 | ||
|             })
 | ||
|             .Where(a => a.datatable?.IsShow == true)
 | ||
|             .OrderBy(a => a.datatable.Order).ToList();
 | ||
| 
 | ||
|         //根据特性过滤掉固定不需要的列
 | ||
|         foreach (var item in data)
 | ||
|         {
 | ||
|             Headers.Add(new()
 | ||
|             {
 | ||
|                 Text = typeof(TItem).GetDescription(item.propertyInfo.Name),
 | ||
|                 Value = item.propertyInfo.Name,
 | ||
|                 CellClass = " table-minwidth " + item.datatable.CellClass,
 | ||
|                 Sortable = item.datatable.Sortable,
 | ||
|                 Align = DataTableHeaderAlign.Start,
 | ||
|                 Filterable = !item.datatable.DefaultFilter
 | ||
|             });
 | ||
|         }
 | ||
|         if (IsShowOperCol)
 | ||
|         {
 | ||
|             Headers.Add(new()
 | ||
|             {
 | ||
|                 Text = "操作",
 | ||
|                 Value = BlazorResourceConst.DataTableActions,
 | ||
|                 Width = 200,
 | ||
|                 Sortable = false,
 | ||
|             });
 | ||
|         }
 | ||
| 
 | ||
|         //外部控制列是否存在
 | ||
|         FilterHeaders?.Invoke(Headers);
 | ||
| 
 | ||
|         //初始化过滤显示列表
 | ||
|         foreach (var item in Headers)
 | ||
|         {
 | ||
|             var filter = new Filters()
 | ||
|             { Title = item.Text, Key = item.Value, Value = item.Filterable, };
 | ||
|             FilterHeaderString.Add(filter);
 | ||
|         }
 | ||
| 
 | ||
|         //初始化Filter值
 | ||
|         Filters?.Invoke(FilterHeaderString);
 | ||
| 
 | ||
|         //定义操作列宽度
 | ||
|         var action = Headers.FirstOrDefault(a => a.Value == BlazorResourceConst.DataTableActions);
 | ||
|         if (action != null)
 | ||
|             action.Width = 70 * ((IsShowEditButton ? 1 : 0) + (IsShowDetailButton ? 1 : 0) + (IsShowDeleteButton ? 1 : 0) + (ItemColOperTemplate != null ? 1 : 0));
 | ||
| 
 | ||
|         FilterChanged();//过滤
 | ||
|         await base.OnInitializedAsync();
 | ||
|     }
 | ||
| 
 | ||
| 
 | ||
|     private void AddClick()
 | ||
|     {
 | ||
|         AddModel = new();
 | ||
|         AddShow = true;
 | ||
|     }
 | ||
| 
 | ||
|     private void AddOnCancel()
 | ||
|     {
 | ||
|         AddShow = false;
 | ||
|     }
 | ||
| 
 | ||
|     private async Task AddOnSave(ModalActionEventArgs args)
 | ||
|     {
 | ||
|         try
 | ||
|         {
 | ||
|             if (AddCallAsync != null)
 | ||
|             {
 | ||
|                 await AddCallAsync.Invoke(AddModel);
 | ||
|             }
 | ||
|             await QueryClickAsync();
 | ||
|             AddShow = false;
 | ||
|         }
 | ||
|         catch (Exception ex)
 | ||
|         {
 | ||
|             args.Cancel();
 | ||
|             await PopupService.EnqueueSnackbarAsync(ex, false);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     private void ClearClick()
 | ||
|     {
 | ||
|         SearchForm.Reset();
 | ||
|     }
 | ||
| 
 | ||
|     private async Task DeleteClick(params TItem[] _selectedItem)
 | ||
|     {
 | ||
|         DeleteLoading = true;
 | ||
|         StateHasChanged();
 | ||
|         try
 | ||
|         {
 | ||
|             if (_selectedItem.Length <= 0)
 | ||
|             {
 | ||
|                 await PopupService.EnqueueSnackbarAsync("选择一行后才能进行操作");
 | ||
|             }
 | ||
|             else
 | ||
|             {
 | ||
|                 if (DeleteCallAsync != null)
 | ||
|                 {
 | ||
|                     var confirm = await PopupService.ConfirmAsync("删除", "确定 ?", AlertTypes.Warning);
 | ||
|                     if (confirm)
 | ||
|                     {
 | ||
|                         await DeleteCallAsync(_selectedItem);
 | ||
|                         await QueryClickAsync();
 | ||
|                     }
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
|         catch (Exception ex)
 | ||
|         {
 | ||
|             await PopupService.EnqueueSnackbarAsync(ex, false);
 | ||
|         }
 | ||
|         finally
 | ||
|         {
 | ||
|             DeleteLoading = false;
 | ||
|             StateHasChanged();
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     private async Task DetailClick(params TItem[] _selectedItem)
 | ||
|     {
 | ||
|         if (_selectedItem.Length > 1)
 | ||
|         {
 | ||
|             await PopupService.EnqueueSnackbarAsync("只能选择一行");
 | ||
|         }
 | ||
|         else if (_selectedItem.Length == 1)
 | ||
|         {
 | ||
|             DetailModel = _selectedItem.FirstOrDefault();
 | ||
|             var strs = typeof(TItem).GetPropertyNamesWithCache();
 | ||
|             Dictionary<string, string> keyValuePairs = new();
 | ||
|             foreach (var item in strs)
 | ||
|             {
 | ||
|                 if (item != BlazorResourceConst.DataTableActions)
 | ||
|                 {
 | ||
|                     var value = typeof(TItem).GetMemberInfoValue(DetailModel, item);
 | ||
|                     if (value is DateTime dt2)
 | ||
|                     {
 | ||
|                         value = dt2.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset);
 | ||
|                     }
 | ||
|                     keyValuePairs.Add(item, value?.ToString());
 | ||
|                 }
 | ||
|             }
 | ||
|             DetailModelPairs = keyValuePairs;
 | ||
|             DetailShow = true;
 | ||
|         }
 | ||
|         else
 | ||
|         {
 | ||
|             await PopupService.EnqueueSnackbarAsync("选择一行后才能进行操作");
 | ||
|         }
 | ||
|     }
 | ||
|     private async Task EditClick(params TItem[] _selectedItem)
 | ||
|     {
 | ||
|         if (_selectedItem.Length > 1)
 | ||
|         {
 | ||
|             await PopupService.EnqueueSnackbarAsync("只能选择一行");
 | ||
|         }
 | ||
|         else if (_selectedItem.Length == 1)
 | ||
|         {
 | ||
|             EditModel = _selectedItem.FirstOrDefault().Adapt<EditItem>();
 | ||
|             EditShow = true;
 | ||
|         }
 | ||
|         else
 | ||
|         {
 | ||
|             await PopupService.EnqueueSnackbarAsync("选择一行后才能进行操作");
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     private void EditOnCancel()
 | ||
|     {
 | ||
|         EditShow = false;
 | ||
|     }
 | ||
| 
 | ||
|     private async Task EditOnSaveAsync(ModalActionEventArgs args)
 | ||
|     {
 | ||
|         try
 | ||
|         {
 | ||
|             if (EditCallAsync != null)
 | ||
|             {
 | ||
|                 await EditCallAsync.Invoke(EditModel);
 | ||
|             }
 | ||
|             await QueryClickAsync();
 | ||
|             EditShow = false;
 | ||
|         }
 | ||
|         catch (Exception ex)
 | ||
|         {
 | ||
|             args.Cancel();
 | ||
|             await PopupService.EnqueueSnackbarAsync(ex, false);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     private async Task Enter(KeyboardEventArgs e)
 | ||
|     {
 | ||
|         if (IsShowQueryButton)
 | ||
|             if (e.Code == "Enter" || e.Code == "NumpadEnter")
 | ||
|             {
 | ||
|                 await QueryClickAsync();
 | ||
|             }
 | ||
|     }
 | ||
| 
 | ||
|     private void FilterChanged()
 | ||
|     {
 | ||
|         headers = Headers.Where(it => FilterHeaderString.Any(a => a.Key == it.Value && a.Value == true)).ToList();
 | ||
|     }
 | ||
| 
 | ||
|     private async Task HandleOnOptionsUpdate(DataOptions dataOptions)
 | ||
|     {
 | ||
|         SearchModel.SortField = dataOptions.SortBy.ToList();
 | ||
|         SearchModel.SortDesc = dataOptions.SortDesc.ToList();
 | ||
|         await QueryClickAsync();
 | ||
|     }
 | ||
| 
 | ||
|     private async Task PageChanged(int val)
 | ||
|     {
 | ||
|         if (SearchModel.Current <= 0)
 | ||
|         {
 | ||
|             SearchModel.Current = 1;
 | ||
|         }
 | ||
|         if (size != SearchModel.Size)
 | ||
|         {
 | ||
|             size = SearchModel.Size;
 | ||
|             await QueryClickAsync();
 | ||
|         }
 | ||
| 
 | ||
|     }
 | ||
| } | 
