Angular合并单元格

396 阅读2分钟

背景

对结果进行展示时,需要对数据相同的列进行合并,具体效果如下:

image-20210302092931913

环境

  • @Angular/cli@11.0.7
  • ng-devui@11.0.0

操作

  • 先对数据源进行处理

    原始数据源数据格式如下:

    export const students = [
    	{ school: 'hx', grade: 'one', name: 'xm', course: 'math', fraction: '80', average: '80' },
    	{ school: 'hx', grade: 'one', name: 'xm', course: 'chinese', fraction: '81', average: '80' },
    	{ school: 'hx', grade: 'one', name: 'xm', course: 'english', fraction: '82', average: '80' },
    	{ school: 'hx', grade: 'one', name: 'zy', course: 'math', fraction: '80', average: '80' },
    	{ school: 'hx', grade: 'one', name: 'zy', course: 'chinese', fraction: '81', average: '80' },
    	{ school: 'hx', grade: 'one', name: 'zy', course: 'english', fraction: '82', average: '80' },
    	{ school: 'hx', grade: 'two', name: 'xh', course: 'math', fraction: '80', average: '80' },
    	{ school: 'hx', grade: 'two', name: 'xh', course: 'chinese', fraction: '81', average: '80' },
    	{ school: 'hx', grade: 'two', name: 'xh', course: 'english', fraction: '82', average: '80' },
    ];
    

    需将数据源中相同的列进行合并,同时,对cource列进行行转列,转换后数据格式如下:

    export const ss = [
    	{
    		type: 'fraction',
    		child: [
    			{
    				school: 'hx',
    				child: [
    					{
    						grade: 'one',
    						child: [
    							{ name: 'xm', type: 'fraction', school: 'hx', grade: 'one', math: '80', chinese: '81', english: '82' },
    							{ name: 'zy', type: 'fraction', school: 'hx', grade: 'one', math: '80', chinese: '81', english: '82' },
    						],
    						len: 2,
    					},
    					{
    						grade: 'two',
    						child: [{ name: 'xh', type: 'fraction', school: 'hx', grade: 'two', math: '80', chinese: '81', english: '82' }],
    						len: 1,
    					},
    				],
    				len: 3,
    			},
    		],
    		len: 3,    ## len表示总共有多少行数据,供rowspan使用
    	},
    	{
    		type: 'average',
    		child: [
    			{
    				school: 'hx',
    				child: [
    					{
    						grade: 'one',
    						child: [
    							{ name: 'xm', type: 'average', school: 'hx', grade: 'one', math: '80', chinese: '80', english: '80' },
    							{ name: 'zy', type: 'average', school: 'hx', grade: 'one', math: '80', chinese: '80', english: '80' },
    						],
    						len: 2,
    					},
    					{
    						grade: 'two',
    						child: [{ name: 'xh', type: 'average', school: 'hx', grade: 'two', math: '80', chinese: '80', english: '80' }],
    						len: 1,
    					},
    				],
    				len: 3,
    			},
    		],
    		len: 3,
    	},
    ];
    

    具体实现转换的代码如下:

    export class ConditionDatatableComponent implements OnInit {
        rowSpan: number[] = [];
        stuSource = [];
    	stuColumns = ['type', 'school', 'grade', 'name', 'math', 'chinese', 'english'];
        
        constructor() {}
    
    	ngOnInit(): void {
    		this.stuSource = this.setSource();
    		console.log(this.stuSource);
    	}
        
        /**
         * 设置rowspan
         */
        setRowSpans(index: number, value: number): number[] {
    		this.rowSpan[index] = value;
    		return this.rowSpan;
    	}
        
        /**
    	 * 数据源处理
    	 */
    	setSource() {
    		let dest = [];
    		students.forEach((item, index) => {
                //处理fraction
    			this.setDataSource(item, dest, this.stuColumns, ['type', 'school', 'grade', 'name', 'course'], 0, 'fraction');
                //处理average
    			this.setDataSource(item, dest, this.stuColumns, ['type', 'school', 'grade', 'name', 'course'], 0, 'average');
    		});
    		return dest;
    	}
        
        /**
    	 * 对数据源进行处理
    	 */
    	setDataSource(item: any, dest: any[], columns: string[], groups: string[], index: number, graphType: string) {
    		if (!item) return;
    		if (!groups || groups.length == 0 || groups.length < index) return;
    		if (!graphType) return;
    
    		//当前key值
    		let target = groups[index];
    		//当前的value值
    		let value = this.isGraphType(target) ? graphType : item[target];
    
    		let isCreate = false;
    		let len = 0;
    		let dest_child = dest.filter((a) => a[target] == value)[0];
    		if (!dest_child) {
    			isCreate = true;
    			dest_child = {};
    			dest_child[target] = value;
    			if (!this.isEnd(groups, index)) {
    				dest_child['child'] = [];
    			} else {
    				for (let i = 0; i < index; i++) {
    					let col = columns[i];
    					let v = this.isGraphType(col) ? graphType : item[col];
    					dest_child[col] = v;
    				}
    			}
    			dest.push(dest_child);
    		}
    
    		if (!this.isEnd(groups, index)) {
    			len = this.setDataSource(item, dest_child.child ?? [], columns, groups, index + 1, graphType);
    			dest_child['len'] = (dest_child['len'] ?? 0) + len;
    		} else {
    			//当前key值
    			target = groups[index + 1];
    			//当前的value值
    			value = item[target];
    			dest_child[value] = item[graphType];
    			if (isCreate) {
    				len = 1;
    			}
    		}
    		return len;
    	}
    }
    
  • 页面展示

    页面展示部分主要使用的是ng-devui的组件(可使用其他组件,逻辑是一样的),再辅以ng-templateng-container以及ngTemplateOutlet来实现该功能,具体代码如下:

    <d-data-table [dataSource]="stuSource">
    	<thead dTableHead>
    		<tr dTableRow>
    			<th dHeadCell *ngFor="let name of stuColumns">{{ name }}</th>
    		</tr>
    	</thead>
    	<tbody dTableBody>
    		<ng-template let-column="column" let-colIndex="colIndex" let-rowItem="rowItem" let-rowIndex="rowIndex">
    			<ng-container *ngTemplateOutlet="TableTd; context: { item: rowItem, colIndex: 0, startColIndex: 0, rowSpans: rowSpan }"></ng-container>
    		</ng-template>
    	</tbody>
    </d-data-table>
    
    <ng-template #TableTd let-item="item" let-colIndex="colIndex" let-startColIndex="startColIndex" let-rowSpans="rowSpans">
    	<ng-container *ngIf="item['child']">
    		<ng-container *ngFor="let item_child of item['child']; let i = index">
    			<ng-container
    				*ngTemplateOutlet="
    					TableTd;
    					context: {
    						item: item_child,
    						colIndex: colIndex + 1,
    						startColIndex: i === 0 ? startColIndex : colIndex + 1,
    						rowSpans: setRowSpans(colIndex, item['len'])
    					}
    				"
    			></ng-container>
    		</ng-container>
    	</ng-container>
    
    	<ng-container *ngIf="!item['child']">
    		<tr dTableRow>
    			<ng-container *ngFor="let col of stuColumns; let i = index">
    				<td
    					dTableCell
    					*ngIf="i >= startColIndex"
    					[ngClass]="{ 'border-left': i === startColIndex && startColIndex > 0 }"
    					[attr.rowSpan]="rowSpan && rowSpan[i] && rowSpan[i] > 0 ? rowSpan[i] : 1"
    				>
    					{{ item[col] }}
    				</td>
    			</ng-container>
    		</tr>
    	</ng-container>
    </ng-template>
    

    上述代码中:

    [ngClass]="{ 'border-left': i === startColIndex && startColIndex > 0 }"
    

    格式为:[ngClass]="{'border-left':condition}",第一个参数为样式的名称,第二个参数为boolean值,当第二个参数,即conditiontrue时,才会添加第一个参数的样式,即border-left

  • 样式

    样式采用的是scss格式,代码如下:

    @import '~ng-devui/styles-var/devui-var.scss';
    
    tr {
    	td:first-child {
    		border-left: 1px solid $devui-dividing-line;
    	}
    
    	td {
    		border-right: 1px solid $devui-dividing-line;
    	}
    }
    
    thead {
    	tr {
    		th:first-child {
    			border-left: 1px solid $devui-dividing-line;
    		}
    
    		th {
    			border-top: 1px solid $devui-dividing-line;
    			border-right: 1px solid $devui-dividing-line;
    		}
    	}
    }
    
    .border-left {
    	border-left: 1px $devui-dividing-line !important;
    }