Angular 6中基于Material Design的嵌套表格实现财务支出报表问题
我完全理解你在Angular 6里用Material Table实现多层嵌套财务支出报表时遇到的行错位问题——这种层级深的嵌套数据和Material Table的扁平化数据源天然有点冲突,处理不好就容易列不对齐。下面是一套能完美匹配你需求的解决方案,一步一步来:
1. 先把嵌套数据转成带层级标识的扁平结构
Material Table更适合处理扁平化的数据集,所以我们先写一个递归函数,把你的嵌套JSON转成每行都包含层级信息、标签和数值字段的数组,这样后续绑定就不会错位了:
// 组件内定义递归扁平化函数 flattenExpenditureData(sections: any[]): any[] { const flatData: any[] = []; const traverse = (item: any, level: number, parentKey: string = '') => { // 基础字段:层级、父节点标识,以及所有数值列(无数据则设为null) const baseFields = { level, parentKey, actuals: item.actuals || null, budgetEstimate: item.budgetEstimate || null, revisedEstimate: item.revisedEstimate || null, budgetEstimateCurrentYear: item.budgetEstimateCurrentYear || null, isExpanded: true // 预留折叠功能的字段 }; // 根据节点类型生成标签和类型标识 if (item.sections) { flatData.push({ ...baseFields, type: 'section', label: item.sections }); item.headDetail?.forEach(head => traverse(head, level + 1, item.sections)); } else if (item.majorHeadNumber) { flatData.push({ ...baseFields, type: 'major', label: `${item.majorHeadNumber} - ${item.majorHeadDescription}` }); item.subMajorHeadDetail?.forEach(subMajor => traverse(subMajor, level + 1, item.majorHeadNumber)); } else if (item.subMajorHeadNumber) { flatData.push({ ...baseFields, type: 'subMajor', label: `${item.subMajorHeadNumber} - ${item.subMajorHeadDescription || 'N/A'}` }); item.minorHeadDetail?.forEach(minor => traverse(minor, level + 1, item.subMajorHeadNumber)); } else if (item.minorHeadNumber) { flatData.push({ ...baseFields, type: 'minor', label: `${item.minorHeadNumber} - ${item.minorHeadDescription}` }); item.subMinorHeadDetail?.forEach(subMinor => traverse(subMinor, level + 1, item.minorHeadNumber)); } else if (item.subMinorHeadNumber) { flatData.push({ ...baseFields, type: 'subMinor', label: `${item.subMinorHeadNumber} - ${item.subMinorHeadDescription}` }); item.detailedHeadDetail?.forEach(detailed => traverse(detailed, level + 1, item.subMinorHeadNumber)); } else if (item.detailedHeadNumber) { flatData.push({ ...baseFields, type: 'detailed', label: `${item.detailedHeadNumber} - ${item.detailedHeadDescription}` }); item.objectHeadDetails?.forEach(object => traverse(object, level + 1, item.detailedHeadNumber)); } else if (item.objectHeadNumber) { flatData.push({ ...baseFields, type: 'object', label: `${item.objectHeadNumber} - ${item.objectHeadDescription}` }); } }; sections.forEach(section => traverse(section, 0)); return flatData; } // 组件初始化时处理数据 flatExpenditureData: any[] = []; expenditureDataSource = new MatTableDataSource(this.flatExpenditureData); displayedColumns = ['item', 'actuals', 'budgetEstimate', 'revisedEstimate', 'budgetEstimateCurrentYear']; ngOnInit() { this.flatExpenditureData = this.flattenExpenditureData(this.detailOfExpenditureSection); this.expenditureDataSource.data = this.flatExpenditureData; }
2. 自定义Material Table模板,实现层级缩进和列对齐
通过层级值设置左边距来区分不同层级的项目,同时给数值列设置右对齐,确保视觉上的整齐:
<table mat-table [dataSource]="expenditureDataSource" class="mat-elevation-z8"> <!-- 支出项目列 --> <ng-container matColumnDef="item"> <th mat-header-cell *matHeaderCellDef> Head Details </th> <td mat-cell *matCellDef="let element" [style.padding-left.px]="element.level * 24"> <!-- 折叠按钮(仅非顶级节点显示) --> <button *ngIf="element.level > 0 && hasChildren(element)" mat-icon-button (click)="toggleExpand(element)" class="expand-btn" > <mat-icon>{{ element.isExpanded ? 'expand_more' : 'chevron_right' }}</mat-icon> </button> <span [class]="getItemClass(element.type)">{{ element.label }}</span> </td> </ng-container> <!-- 数值列定义 --> <ng-container matColumnDef="actuals"> <th mat-header-cell *matHeaderCellDef> Actuals </th> <td mat-cell *matCellDef="let element" class="numeric-cell"> {{ element.actuals || '-' }} </td> </ng-container> <ng-container matColumnDef="budgetEstimate"> <th mat-header-cell *matHeaderCellDef> Budget Estimate </th> <td mat-cell *matCellDef="let element" class="numeric-cell"> {{ element.budgetEstimate || '-' }} </td> </ng-container> <ng-container matColumnDef="revisedEstimate"> <th mat-header-cell *matHeaderCellDef> Revised Estimate </th> <td mat-cell *matCellDef="let element" class="numeric-cell"> {{ element.revisedEstimate || '-' }} </td> </ng-container> <ng-container matColumnDef="budgetEstimateCurrentYear"> <th mat-header-cell *matHeaderCellDef> Budget Estimate (Current Year) </th> <td mat-cell *matCellDef="let element" class="numeric-cell"> {{ element.budgetEstimateCurrentYear || '-' }} </td> </ng-container> <!-- 表头和行渲染 --> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;" [class.hidden-row]="!row.isExpanded"></tr> </table>
3. 补充辅助函数和样式
添加折叠功能的辅助方法,以及区分层级的样式:
// 判断当前节点是否有子节点 hasChildren(element: any): boolean { return this.flatExpenditureData.some(item => item.parentKey === element.label.split(' - ')[0] && item.level === element.level + 1 ); } // 切换折叠状态 toggleExpand(element: any) { element.isExpanded = !element.isExpanded; // 递归切换所有子节点的展开状态 const toggleChildren = (parentKey: string, level: number) => { const children = this.flatExpenditureData.filter(item => item.parentKey === parentKey && item.level === level ); children.forEach(child => { child.isExpanded = element.isExpanded; toggleChildren(child.label.split(' - ')[0], level + 1); }); }; toggleChildren(element.label.split(' - ')[0], element.level + 1); // 更新数据源 this.expenditureDataSource.data = [...this.flatExpenditureData]; } // 返回层级对应的样式类 getItemClass(type: string): string { switch(type) { case 'section': return 'section-header'; case 'major': return 'major-head'; case 'subMajor': return 'submajor-head'; default: return 'regular-item'; } }
对应的CSS样式:
.section-header { font-weight: bold; font-size: 1.1rem; color: #1976d2; } .major-head { font-weight: 600; color: #2d3748; } .submajor-head { font-weight: 500; } .numeric-cell { text-align: right; padding-right: 16px; } .expand-btn { padding: 4px; margin-right: 4px; } .hidden-row { display: none; }
核心解决思路
之前的行错位问题本质是嵌套数据中父节点没有数值字段,导致Material Table渲染时部分列无内容,破坏了表格的网格对齐。通过扁平化处理后,每一行都包含所有列的字段(无数据则用占位符),再结合层级缩进和样式区分,就能完美实现你想要的嵌套报表效果,同时还支持折叠展开的交互优化。
内容的提问来源于stack exchange,提问作者Iswar
相关产品推荐
相关产品推荐

