MVC视图中三级嵌套集合的整洁展示方案咨询
需求:在MVC视图中整洁展示三级嵌套集合数据
我需要在MVC视图里呈现一个三级嵌套的集合数据(账户→地点→支付方式,每个支付方式关联账单数量),先后尝试了两种方案但都不满意,寻求合适的实现方式:
尝试1:嵌套表格实现
最初用嵌套表格渲染,虽然能展示数据,但布局混乱、无法对齐,也没法正常使用表头,效果很差。
视图代码:
<table class="table table-hover" style="vertical-align: middle"> <tbody> @*Accounts*@ @for (var counterAccount = 0; counterAccount < Model.RecordsToBeSubmitted.Count; counterAccount++) { var counterAccountSafe = counterAccount; <tr> <th scope="row">@Model.RecordsToBeSubmitted[counterAccountSafe].Account</th> <td> <table class="table"> <tbody> @*Locations*@ @for (var counterLocation = 0; counterLocation < Model.RecordsToBeSubmitted[counterAccountSafe].Locations.Count; counterLocation++) { var counterLocationSafe = counterLocation; <tr> <td>@Model.RecordsToBeSubmitted[counterAccountSafe].Locations[counterLocationSafe].Location</td> <td> <table class="table"> <tbody> @*Pay Modes*@ @for (var counterPayMode = 0; counterPayMode < Model.RecordsToBeSubmitted[counterAccountSafe].Locations[counterLocationSafe].PaymentModes.Count; counterPayMode++) { var counterPayModeSafe = counterPayMode; <tr> <td>@Model.RecordsToBeSubmitted[counterAccountSafe].Locations[counterLocationSafe].PaymentModes[counterPayModeSafe].PaymentMode</td> <td>@Model.RecordsToBeSubmitted[counterAccountSafe].Locations[counterLocationSafe].PaymentModes[counterPayModeSafe].BillDetails.Count</td> </tr> } </tbody> </table> </td> </tr> } </tbody> </table> </td> </tr> } </tbody> </table>
原始嵌套模型类:
public class GrpSummaryResult : IBdo { [DataMember] public bool IsProcessable { get; set; } [DataMember] public int TotalBills { get; set; } [DataMember] public IList<GrpSummaryGroupAccount> Accounts { get; set; } } [DataContract(Namespace = Constants.BdoNamespace)] public class GrpSummaryGroupAccount : IBdo { [DataMember] public string Account { get; set; } [DataMember] public IList<GrpSummaryGroupLocation> Locations { get; set; } } [DataContract(Namespace = Constants.BdoNamespace)] public class GrpSummaryGroupLocation : IBdo { [DataMember] public int Location { get; set; } [DataMember] public IList<GrpSummaryGroupPaymentMode> PaymentModes { get; set; } } [DataContract(Namespace = Constants.BdoNamespace)] public class GrpSummaryGroupPaymentMode : IBdo { [DataMember] public int PaymentModeId { get; set; } [DataMember] public string PaymentMode { get; set; } [DataMember] public IList<BillInfoDetail> BillDetails { get; set; } } [DataContract(Namespace = Constants.BdoNamespace)] public class BillInfoDetail : IBdo { [DataMember] public string BillNumber { get; set; } [DataMember] public string ServiceName { get; set; } [DataMember] public decimal Fees { get; set; } }
尝试2:扁平数据+Kendo PivotGrid
后来改用扁平数据模型,但PivotGrid的展示效果不符合预期,不需要它的透视、配置等额外功能,只需要简洁的层级展示。
扁平数据模型:
[DataContract(Namespace = Constants.BdoNamespace)] public class BillInfoDetail : IBdo { [DataMember] public string FocusReferenceNumber { get => $"{BillNumber}-{PaymentModeId}"; private set { } } // 原有BillInfoDetail字段... [DataMember] public BillInfo Bill { get; set; } } public class BillInfo : IBdo { [DataMember] public string BillNumber { get; set; } [DataMember] public int PayModeId { get; set; } [DataMember] public int CashierId { get; set; } [DataMember] public string PayMode { get; set; } [DataMember] public string CustomerCode { get; set; } [DataMember] public string CustomerName { get; set; } [DataMember] public string AccountGroup { get; set; } }
PivotGrid实现代码:
@(Html.Kendo().PivotConfigurator() .Name("configurator") .HtmlAttributes(new { @class = "hidden-on-narrow" }) .Filterable(true) .Sortable() .Height(580) ) @(Html.Kendo().PivotGrid<DA.Systems.Focus.Business.BDO.BillInfoDetail>() .Name("pivotGrid") .HtmlAttributes(new { @class = "hidden-on-narrow" }) .Filterable(true) .Configurator("#configurator") .ColumnWidth(120) .Height(570) .BindTo(Model.FlatData) .DataSource(dataSource => dataSource .Ajax() .Schema(schema => schema .Model(m => m.Field("Account", typeof(string)).From("Bill.AccountGroup")) .Model(m => m.Field("PaymentMode", typeof(string)).From("Bill.PayMode")) .Cube(cube => cube .Dimensions(dimensions => { dimensions.Add("Account").Caption("Accounts"); dimensions.Add(model => model.CashierId).Caption("Location"); dimensions.Add("PaymentMode").Caption("Payment Modes"); }) .Measures(measures => { measures.Add("Bills").Format("{0}").Field(model => model.FocusReferenceNumber).AggregateName("count"); }) )) .Columns(columns => { columns.Add("Account").Expand(true); columns.Add("PaymentMode"); }) .Measures(measures => measures.Values("Bills")) .Events(e => e.Error("onError")) ) )
推荐解决方案:Kendo Hierarchical Grid(层级网格)
既然你有权限使用Kendo MVC,**Hierarchical Grid(层级展开网格)**是最适合的选择——它原生支持多级嵌套数据的展开/折叠,布局整齐,自带表头对齐,不需要额外复杂的CSS,也没有PivotGrid的冗余功能。
实现步骤:
1. 复用原始嵌套模型(不需要扁平数据)
直接使用你最初的GrpSummaryResult、GrpSummaryGroupAccount等嵌套模型,不需要转换为扁平结构。
2. 视图中配置Kendo Hierarchical Grid
@(Html.Kendo().Grid<GrpSummaryGroupAccount>() .Name("hierarchicalGrid") .Columns(columns => { columns.Bound(c => c.Account).Title("账户"); columns.Command(cmd => cmd.Expand()).Width(60); // 展开/折叠按钮 }) .Sortable() .Pageable(false) .DataSource(dataSource => dataSource .Ajax() .Read(read => read.Action("GetAccounts", "YourController")) // 替换为你的控制器Action,返回Accounts列表 ) .DetailTemplate(@<text> <!-- 第二层级:地点 --> @(Html.Kendo().Grid<GrpSummaryGroupLocation>() .Name("grid_#=Account#") // 唯一命名避免冲突 .Columns(columns => { columns.Bound(l => l.Location).Title("地点ID"); columns.Command(cmd => cmd.Expand()).Width(60); }) .Sortable() .Pageable(false) .DataSource(dataSource => dataSource .Ajax() .Read(read => read.Action("GetLocations", "YourController") .Data("function() { return { account: '#=Account#' }; }")) // 传递父级账户参数 ) .DetailTemplate(@<text> <!-- 第三层级:支付方式 + 账单数量 --> @(Html.Kendo().Grid<GrpSummaryGroupPaymentMode>() .Name("grid_#=Account#_#=Location#") .Columns(columns => { columns.Bound(p => p.PaymentMode).Title("支付方式"); columns.Bound(p => p.BillDetails.Count).Title("账单数量").Width(120); }) .Sortable() .Pageable(false) .DataSource(dataSource => dataSource .Ajax() .Read(read => read.Action("GetPaymentModes", "YourController") .Data("function() { return { account: '#=Account#', location: '#=Location#' }; }")) // 传递父级参数 ) ) </text>) ) </text>) )
3. 控制器中添加对应的读取Action
public ActionResult GetAccounts([DataSourceRequest] DataSourceRequest request) { var result = // 从业务逻辑获取GrpSummaryResult中的Accounts列表 return Json(result.ToDataSourceResult(request)); } public ActionResult GetLocations(string account, [DataSourceRequest] DataSourceRequest request) { var accountData = // 根据account获取对应的GrpSummaryGroupAccount对象 return Json(accountData.Locations.ToDataSourceResult(request)); } public ActionResult GetPaymentModes(string account, int location, [DataSourceRequest] DataSourceRequest request) { var accountData = // 根据account获取账户对象 var locationData = accountData.Locations.First(l => l.Location == location); return Json(locationData.PaymentModes.ToDataSourceResult(request)); }
替代方案:纯HTML+CSS分组列表(无Kendo依赖)
如果不想用Kendo,也可以用Bootstrap的折叠组件+嵌套列表来实现,布局同样整齐:
<div class="container mt-4"> @foreach (var account in Model.RecordsToBeSubmitted) { <div class="card mb-3"> <div class="card-header" data-bs-toggle="collapse" data-bs-target="#account-@account.Account"> <h5 class="mb-0">@account.Account</h5> </div> <div id="account-@account.Account" class="collapse show"> <div class="card-body p-0"> @foreach (var location in account.Locations) { <div class="card border-0"> <div class="card-header" data-bs-toggle="collapse" data-bs-target="#location-@account.Account-@location.Location"> <h6 class="mb-0">地点:@location.Location</h6> </div> <div id="location-@account.Account-@location.Location" class="collapse show"> <div class="card-body"> <table class="table table-sm mb-0"> <thead> <tr> <th>支付方式</th> <th>账单数量</th> </tr> </thead> <tbody> @foreach (var payMode in location.PaymentModes) { <tr> <td>@payMode.PaymentMode</td> <td>@payMode.BillDetails.Count</td> </tr> } </tbody> </table> </div> </div> </div> } </div> </div> </div> } </div>
这个方案依赖Bootstrap的折叠组件,不需要额外JS(除了Bootstrap的JS),布局整洁,支持展开/折叠,表头对齐也没问题。
内容的提问来源于stack exchange,提问作者DoomerDGR8
相关产品推荐
相关产品推荐

