diff --git a/src/BootstrapBlazor.Shared/Samples/Table/TablesSelection.razor b/src/BootstrapBlazor.Shared/Samples/Table/TablesSelection.razor index 680652ec0089a623763eb91e44a2f7fa58616dc0..b9e671d43a54ed668932d7792dd0b7272d92c6a5 100644 --- a/src/BootstrapBlazor.Shared/Samples/Table/TablesSelection.razor +++ b/src/BootstrapBlazor.Shared/Samples/Table/TablesSelection.razor @@ -26,4 +26,4 @@ } - + diff --git a/src/BootstrapBlazor/Components/Table/Table.razor b/src/BootstrapBlazor/Components/Table/Table.razor index 228baa0b63e5e29ac2e80c42304558518d030bb2..6434fcfe0c1344960752cfa47bc4f24e19a8c74e 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor +++ b/src/BootstrapBlazor/Components/Table/Table.razor @@ -91,18 +91,10 @@ } diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs b/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs index d95af7842fb46403c774d895577c38f486c51fdd..5a0a503e61c7ab28e7d58b4e117339997d54e743 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs @@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; +using System.ComponentModel.DataAnnotations; +using System.Reflection; namespace BootstrapBlazor.Components; @@ -414,15 +416,18 @@ public partial class Table /// protected async Task QueryData() { + // 目前设计使用 Items 参数后不回调 OnQueryAsync 方法 if (Items == null) { if (OnQueryAsync == null && DynamicContext != null && typeof(TItem).IsAssignableTo(typeof(IDynamicObject))) { + // 动态数据 QueryItems = DynamicContext.GetItems().Cast(); TotalCount = QueryItems.Count(); } else { + // 数据集合 await OnQuery(); } } @@ -462,6 +467,9 @@ public partial class Table TotalCount = queryData.TotalCount; IsAdvanceSearch = queryData.IsAdvanceSearch; + // 处理选中行逻辑 + ProcessSelectedRows(); + // 分页情况下内部不做处理防止页码错乱 if (!queryOption.IsPage) { @@ -473,83 +481,109 @@ public partial class Table await ProcessTreeData(); } } - } - void ProcessPageData(QueryData queryData, QueryPageOptions queryOption) - { - var filtered = queryData.IsFiltered; - var sorted = queryData.IsSorted; - var searched = queryData.IsSearch; - - // 外部未处理 SearchText 模糊查询 - if (!searched && queryOption.Searchs.Any()) + void ProcessSelectedRows() { - QueryItems = QueryItems.Where(queryOption.Searchs.GetFilterFunc(FilterLogic.Or)); - TotalCount = QueryItems.Count(); + // 判断模型是否有 [Key] Id 等可识别字段尝试重构 + if (typeof(TItem).GetRuntimeProperties().Any(p => p.IsDefined(typeof(KeyAttribute)))) + { + var rows = new List(); + // 更新选中行逻辑 + foreach (var item in SelectedRows) + { + var key = Utility.GetKeyValue(item); + if (key != null) + { + if (QueryItems.Any(i => Utility.GetKeyValue(i)?.ToString() == key.ToString())) + { + rows.Add(item); + } + } + } + SelectedRows = rows; + } + else + { + SelectedRows.Clear(); + } } - // 外部未处理自定义高级搜索 内部进行高级自定义搜索过滤 - if (!IsAdvanceSearch && queryOption.CustomerSearchs.Any()) + void ProcessPageData(QueryData queryData, QueryPageOptions queryOption) { - QueryItems = QueryItems.Where(queryOption.CustomerSearchs.GetFilterFunc()); - TotalCount = QueryItems.Count(); - IsAdvanceSearch = true; - } + var filtered = queryData.IsFiltered; + var sorted = queryData.IsSorted; + var searched = queryData.IsSearch; - // 外部未过滤,内部自行过滤 - if (!filtered && queryOption.Filters.Any()) - { - QueryItems = QueryItems.Where(queryOption.Filters.GetFilterFunc()); - TotalCount = QueryItems.Count(); - } + // 外部未处理 SearchText 模糊查询 + if (!searched && queryOption.Searchs.Any()) + { + QueryItems = QueryItems.Where(queryOption.Searchs.GetFilterFunc(FilterLogic.Or)); + TotalCount = QueryItems.Count(); + } - // 外部未处理排序,内部自行排序 - // 先处理列头排序 再处理默认多列排序 - if (!sorted) - { - if (queryOption.SortOrder != SortOrder.Unset && !string.IsNullOrEmpty(queryOption.SortName)) + // 外部未处理自定义高级搜索 内部进行高级自定义搜索过滤 + if (!IsAdvanceSearch && queryOption.CustomerSearchs.Any()) { - var invoker = Utility.GetSortFunc(); - QueryItems = invoker(QueryItems, queryOption.SortName, queryOption.SortOrder); + QueryItems = QueryItems.Where(queryOption.CustomerSearchs.GetFilterFunc()); + TotalCount = QueryItems.Count(); + IsAdvanceSearch = true; } - else if (queryOption.SortList != null && queryOption.SortList.Any()) + + // 外部未过滤,内部自行过滤 + if (!filtered && queryOption.Filters.Any()) { - var invoker = Utility.GetSortListFunc(); - QueryItems = invoker(QueryItems, queryOption.SortList); + QueryItems = QueryItems.Where(queryOption.Filters.GetFilterFunc()); + TotalCount = QueryItems.Count(); } - } - } - async Task ProcessTreeData() - { - KeySet.Clear(); - if (TableTreeNode.HasKey) - { - CheckExpandKeys(TreeRows); - } - if (KeySet.Count > 0) - { - TreeRows = new List>(); - foreach (var item in QueryItems) + // 外部未处理排序,内部自行排序 + // 先处理列头排序 再处理默认多列排序 + if (!sorted) { - var node = new TableTreeNode(item) + if (queryOption.SortOrder != SortOrder.Unset && !string.IsNullOrEmpty(queryOption.SortName)) { - HasChildren = CheckTreeChildren(item), - }; - node.IsExpand = node.HasChildren && node.Key != null && KeySet.Contains(node.Key); - if (node.IsExpand) + var invoker = Utility.GetSortFunc(); + QueryItems = invoker(QueryItems, queryOption.SortName, queryOption.SortOrder); + } + else if (queryOption.SortList != null && queryOption.SortList.Any()) { - await RestoreIsExpand(node); + var invoker = Utility.GetSortListFunc(); + QueryItems = invoker(QueryItems, queryOption.SortList); } - TreeRows.Add(node); } } - else + + async Task ProcessTreeData() { - TreeRows = QueryItems.Select(item => new TableTreeNode(item) + KeySet.Clear(); + if (TableTreeNode.HasKey) + { + CheckExpandKeys(TreeRows); + } + if (KeySet.Count > 0) { - HasChildren = CheckTreeChildren(item) - }).ToList(); + TreeRows = new List>(); + foreach (var item in QueryItems) + { + var node = new TableTreeNode(item) + { + HasChildren = CheckTreeChildren(item), + }; + node.IsExpand = node.HasChildren && node.Key != null && KeySet.Contains(node.Key); + if (node.IsExpand) + { + await RestoreIsExpand(node); + } + TreeRows.Add(node); + } + } + else + { + TreeRows = QueryItems.Select(item => new TableTreeNode(item) + { + HasChildren = CheckTreeChildren(item) + }).ToList(); + } } } } diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.Pagination.cs b/src/BootstrapBlazor/Components/Table/Table.razor.Pagination.cs index 90ba2e555f9edcd12540abfac0a3bd9d550596d3..4108341b72e1d8c7e14e3c7d30f35500219f04ef 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.Pagination.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.Pagination.cs @@ -54,7 +54,7 @@ public partial class Table } /// - /// 异步查询回调方法 + /// 异步查询回调方法,设置 后无法触发此回调方法 /// [Parameter] public Func>>? OnQueryAsync { get; set; } diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index cc883b6bc68cc3d75d2b93e27ec326a965101661..a4b2bab9d1b4d4c3c2b10a565008210bc2977de3 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -477,7 +477,7 @@ public partial class Table : BootstrapComponentBase, IDisposable, ITable public RenderFragment>? FooterTemplate { get; set; } /// - /// 获得/设置 数据集合,适用于无功能时仅做数据展示使用,高级功能时请使用 回调委托 + /// 获得/设置 数据集合,适用于无功能仅做数据展示使用,高级功能时请使用 回调委托 /// [Parameter] public IEnumerable? Items { get; set; } diff --git a/src/BootstrapBlazor/Extensions/LambdaExtensions.cs b/src/BootstrapBlazor/Extensions/LambdaExtensions.cs index 4b96bdd275fb671fbbad26d50ad4c49132d85db7..c54c622db3b2bdf07700f46e7ef362543fc59989 100644 --- a/src/BootstrapBlazor/Extensions/LambdaExtensions.cs +++ b/src/BootstrapBlazor/Extensions/LambdaExtensions.cs @@ -3,6 +3,7 @@ // Website: https://www.blazor.zone or https://argozhang.github.io/ using BootstrapBlazor.Components; +using System.ComponentModel.DataAnnotations; using System.Linq.Expressions; using System.Reflection; @@ -600,4 +601,24 @@ public static class LambdaExtensions return false; } #endregion + + /// + /// 获得 指定模型标记 的属性值 + /// + /// + /// + /// + public static Expression> GetKeyValue(TModel model) + { + var type = model is not null ? model.GetType() : typeof(TModel); + Expression> ret = _ => default!; + var property = type.GetRuntimeProperties().FirstOrDefault(p => p.IsDefined(typeof(KeyAttribute))); + if (property != null) + { + var param = Expression.Parameter(typeof(TModel)); + var body = Expression.Property(Expression.Convert(param, type), property); + ret = Expression.Lambda>(Expression.Convert(body, typeof(TValue)), param); + } + return ret; + } } diff --git a/src/BootstrapBlazor/Services/CacheManager.cs b/src/BootstrapBlazor/Services/CacheManager.cs index dce57592f697048aecbf297d6d590663e88d693b..186edade26327da361a811355277f5057f646bac 100644 --- a/src/BootstrapBlazor/Services/CacheManager.cs +++ b/src/BootstrapBlazor/Services/CacheManager.cs @@ -301,6 +301,25 @@ internal class CacheManager : ICacheManager }); invoker(model, value); } + + /// + /// 获得 指定模型标记 的属性值 + /// + /// + /// + /// + /// + public static TValue GetKeyValue(TModel model) + { + var type = model is object o ? o.GetType() : typeof(TModel); + var cacheKey = ($"Lambda-GetKeyValue-{type.FullName}", typeof(TModel)); + var invoker = Instance.GetOrCreate(cacheKey, entry => + { + entry.SetDynamicAssemblyPolicy(type); + return LambdaExtensions.GetKeyValue(model).Compile(); + }); + return invoker(model); + } #endregion #region Lambda Sort diff --git a/src/BootstrapBlazor/Utils/Utility.cs b/src/BootstrapBlazor/Utils/Utility.cs index e6586f194b4357425525e2f8be63523a6b6705d9..e9944f00ca81fededf60a1dc8da6f6f50a1f6a41 100644 --- a/src/BootstrapBlazor/Utils/Utility.cs +++ b/src/BootstrapBlazor/Utils/Utility.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; +using System.ComponentModel.DataAnnotations; using System.Data; using System.Linq.Expressions; using System.Reflection; @@ -31,6 +32,15 @@ public static class Utility /// public static string GetDisplayName(Type modelType, string fieldName) => CacheManager.GetDisplayName(modelType, fieldName); + /// + /// 获得 指定模型标记 的属性值 + /// + /// + /// + /// + /// + public static TValue GetKeyValue(TModel model) => CacheManager.GetKeyValue(model); + /// /// /// diff --git a/test/UnitTest/Extensions/UtilityTest.cs b/test/UnitTest/Extensions/UtilityTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..907e222674316dee43908e8d72fb69ce2ce96f8b --- /dev/null +++ b/test/UnitTest/Extensions/UtilityTest.cs @@ -0,0 +1,22 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using BootstrapBlazor.Shared; + +namespace UnitTest.Extensions; + +public class UtilityTest : BootstrapBlazorTestBase +{ + [Fact] + public void GetKeyValue_Ok() + { + var foo = new Foo() { Id = 1 }; + var v = Utility.GetKeyValue(foo); + Assert.Equal(1, v); + + object foo1 = new Foo() { Id = 2 }; + v = Utility.GetKeyValue(foo1); + Assert.Equal(2, v); + } +}