Angular8使用nz-table实现业务报表那点事

和之国第一美女-公主-光月日和

前言

做了一个业务线上的客户定制化报表需求,因为报表存在各种合并行,合并列,静态动态按需拼接数据等一系列操作,日常使用的 帆软报表平台 ,公司新推的 自定义报表平台 在实现方面可能存在问题,由于年底事情多,人手也不够,各种任务时间排期还特别紧,经组内前端讨论,我们决定用比较稳妥靠谱的方式,手搓了几个定制化报表,由于之前没有做过这么多的自定义合并行列的表格,这次开发实现过程也是一个探索的过程。

最终效果

先看下定制化报表的最终效果

这里简单介绍一下里面的业务逻辑

查询逻辑

基于日期、片区、猪场条件查询数据,日期必选,片区和猪场如果没有选择,默认查询所有猪场的数据,片区和猪场是联动效果,如果选了片区,则过滤当前片区下的所有猪场

数据展示

如上图所示,先看报表标题头部,一共三行,前面片区静态,后面合计静态,中间的片区,猪场,小计数据为动态,猪场下面对应的三列数据是静态

接下看表格数据区域,阶段列中的数据是根据查询结果动态显示,同类型需要合并行,平均存栏和死淘率这两个行数据需要合并列,其他数据默认不合并

思路分析

当前定制化报表的需求清楚了,来分析一下实现思路,首先这个报表不是普通的表格,不能直接进行数据绑定,动态标题头和内容需要数据单独拼接,同时需要数据合理处理报表中行列的动态静态合并

实现过程

返回接口数据格式

{
    "code": 0,
    "data": {
        "headerArea": [
            {
                "Key": "2311081103550000150",
                "Name": "片区01",
                "Value": "2311081103550000150",
                "Row": 1,
                "Column": 9
            },
            ...
            {
                "Key": "Total",
                "Name": "合计",
                "Value": "",
                "Row": 1,
                "Column": 2
            }
        ],
        "headerPigFarm": [
            {
                "Key": "2311081103550000150.2203141401040000076",
                "Name": "母猪场活跃度测试",
                "Value": "2203141401040000076",
                "Row": 1,
                "Column": 3
            },
            {
                "Key": "2311081103550000150.2203141401380000076",
                "Name": "肥猪场活跃度测试",
                "Value": "2203141401380000076",
                "Row": 1,
                "Column": 3
            },
            {
                "Key": "2311081103550000150.Subtotal",
                "Name": "小计",
                "Value": "",
                "Row": 1,
                "Column": 3
            }
            ...
        ],
        "headerColumn": [
            {
                "Key": "2311011538400000150.2105191446220001076.Death",
                "Name": "死亡",
                "Value": "",
                "Row": 1,
                "Column": 1
            },
            {
                "Key": "2311011538400000150.2105191446220001076.Eliminate",
                "Name": "无价淘",
                "Value": "",
                "Row": 1,
                "Column": 1
            },
            {
                "Key": "2311011538400000150.2105191446220001076.Valuable",
                "Name": "有价淘",
                "Value": "",
                "Row": 1,
                "Column": 1
            },
            {
                "Key": "2311011538400000150.Subtotal.Death",
                "Name": "死亡",
                "Value": "",
                "Row": 1,
                "Column": 1
            },
            {
                "Key": "2311011538400000150.Subtotal.Eliminate",
                "Name": "无价淘",
                "Value": "",
                "Row": 1,
                "Column": 1
            },
            ...
        ],
        "bodyData": [
            {
                "PigType": "201911041541201001",
                "PigTypeName": "仔猪",
                "DataDete": "2023-11-01T00:00:00",
                "2311011538400000150.2105191446220001076.Death": 0,
                "2311011538400000150.2105191446220001076.Eliminate": 0,
                "2311011538400000150.2105191446220001076.Valuable": 0,
                "2311011538400000150.Subtotal.Death": 0,
                "2311011538400000150.Subtotal.Eliminate": 0,
                "2311011538400000150.Subtotal.Valuable": 0,
                "2311081103550000150.2203141401040000076.Death": 0,
                "2311081103550000150.2203141401040000076.Eliminate": 0,
                "2311081103550000150.2203141401040000076.Valuable": 0,
                "2311081103550000150.2203141401380000076.Death": 0,
                "2311081103550000150.2203141401380000076.Eliminate": 0,
                "2311081103550000150.2203141401380000076.Valuable": 0,
                "2311081103550000150.Subtotal.Death": 0,
                "2311081103550000150.Subtotal.Eliminate": 0,
                "2311081103550000150.Subtotal.Valuable": 0,
                "Total.Death": 0,
                "Total.Eliminate": 0,
                "Total.Valuable": 0
            },
            {
                "PigType": "201911041541201001",
                "PigTypeName": "仔猪",
                "DataDete": "2023-11-02T00:00:00",
                "2311011538400000150.2105191446220001076.Death": 2,
                "2311011538400000150.2105191446220001076.Eliminate": 2,
                "2311011538400000150.2105191446220001076.Valuable": 1,
                "2311011538400000150.Subtotal.Death": 2,
                "2311011538400000150.Subtotal.Eliminate": 2,
                "2311011538400000150.Subtotal.Valuable": 1,
                "2311081103550000150.2203141401040000076.Death": 9,
                "2311081103550000150.2203141401040000076.Eliminate": 0,
                "2311081103550000150.2203141401040000076.Valuable": 3,
                "2311081103550000150.2203141401380000076.Death": 2,
                "2311081103550000150.2203141401380000076.Eliminate": 2,
                "2311081103550000150.2203141401380000076.Valuable": 1,
                "2311081103550000150.Subtotal.Death": 11,
                "2311081103550000150.Subtotal.Eliminate": 2,
                "2311081103550000150.Subtotal.Valuable": 4,
                "Total.Death": 13,
                "Total.Eliminate": 4,
                "Total.Valuable": 5
            },
            ...
            {
                "PigType": "322",
                "PigTypeName": "公猪",
                "DataDete": "小计",
                "2311011538400000150.2105191446220001076.Death": 1,
                "2311011538400000150.2105191446220001076.Eliminate": 0,
                "2311011538400000150.2105191446220001076.Valuable": 2,
                "2311011538400000150.Subtotal.Death": 1,
                "2311011538400000150.Subtotal.Eliminate": 0,
                "2311011538400000150.Subtotal.Valuable": 2,
                "2311081103550000150.2203141401040000076.Death": 19,
                "2311081103550000150.2203141401040000076.Eliminate": 2,
                "2311081103550000150.2203141401040000076.Valuable": 1,
                "2311081103550000150.2203141401380000076.Death": 0,
                "2311081103550000150.2203141401380000076.Eliminate": 0,
                "2311081103550000150.2203141401380000076.Valuable": 0,
                "2311081103550000150.Subtotal.Death": 19,
                "2311081103550000150.Subtotal.Eliminate": 2,
                "2311081103550000150.Subtotal.Valuable": 1,
                "Total.Death": 20,
                "Total.Eliminate": 2,
                "Total.Valuable": 3
            }
        ]
    }
}

接口里面返回的数据格式我第一次看的时候,对于前端开发来说感觉是非常 特别 的,这里不得不讲一下这里面的逻辑了,报表渲染的表格数据都在 data 这个对象里,headerArea 里面是表头第一行的片区数据,headerPigFarm 是表头第二行猪场数据,headerColumn 是表头第三行静态列数据,有效数据字段是用的 KeyNamebodyData 是表格内容数据,这里面的数据是最 特别 的,每个对象里,通过 分区猪场Key 字段拼接了对应的静态列( DeathEliminate, Valuable) 字段组合成的数据,然后通过 分区Subtotal 组成小计数据,Total.* 是当前行的静态列总计的数据

表格实现

基于接口返回的数据格式,目前来分析,基于数据拼接表格的方式比较好一点,整个表格实现可以分为两部分,一部分是表头,一部分是表体的数据,表格使用的 nz-table

表头实现

thead 中对表头三行进行分别处理,对于 片区 等静态单元格进行手动操作,对于动态的 片区数据 使用循环进行处理,并动态处理 colspan,表头的数据根据不同行的数据,使用不同的数组,三行表头分别进行处理,表头静态动态行列合并渲染如下

<thead>
    <tr style="background-color: #fafafa">
        <th colspan="2" >片区</th>
        <th *ngFor="let item of headerAreaSplit" [attr.colspan]="item.colspan">
            {{ item.Name }}
        </th>
        <th colspan="3" rowspan="2">合计</th>
    </tr>
    <tr style="background-color: #fafafa">
        <th rowspan="2" >阶段</th>
        <th>猪场</th>

        <ng-container *ngFor="let item of headerPigFarm">
            <th colspan="3">
                {{ item.Name }}
            </th>
        </ng-container>
    </tr>
    <tr style="background-color: #fafafa">
        <th>日期</th>
        <ng-container *ngFor="let item of headerPigFarm">
            <th>死亡</th>
            <th>无价淘</th>
            <th>有价淘</th>
        </ng-container>
        <th>死亡</th>
        <th>无价淘</th>
        <th>有价淘</th>
    </tr>
</thead>

下面为表头行的数据处理,根据已经设置好的静态列设置动态列的数量,这里还有一个注意点是控制好 colspan 和需要动态循环的那部分数据

const { headerArea, headerPigFarm, headerColumn, bodyData } = data;

// 第一行表头数据组装
this.headerArea = headerArea;
this.headerAreaSplit = [];
// @ts-ignore
let arr = structuredClone(headerArea).splice(0, headerArea.length - 1);
arr.forEach((v) => {
    let colspan = 0;
    headerPigFarm.forEach((v2) => {
        if (v2.Key.indexOf(v.Key) > -1) {
            colspan++;
        }
    });
    this.headerAreaSplit.push({
        Name: v.Name,
        Key: v.Key,
        colspan: colspan * 3,
    });
});

// 第二行表头数据组装
this.headerPigFarm = headerPigFarm;
this.headerColumn = headerColumn;
this.bodyData = bodyData;
表体实现

这里面首先基于阶段(PigTypeName)列处理数据,统一处理成 key, value 的形式,然后处理 rowspan 得到同类型阶段值数据实现行合并,猪场数据循环拼接得到猪场下的静态三列数据,然后单独拼接小计,总计的数据。

以下由于静态数据组装很多,删除了部分代码,只保留了整体结构

// 表格数据行组装
let dataList = [];
const nameCount = {};
bodyData.forEach((v) => {
    let a3 = [];

    a3.push({
        key: 'PigTypeName',
        value: v.PigTypeName,
        rowspan: 0,
    });

    const name = v.PigTypeName;
    nameCount[name] = (nameCount[name] || 0) + 1;

    if (v.DataDete.indexOf('T') > -1) {
        a3.push({
            key: 'DataDete',
            value: v.DataDete.split('T')[0],
        });

        headerPigFarm.forEach((v2) => {
            a3.push({
                key: v2.Key + '.Death',
                value: v[v2.Key + '.Death'],
            });
            ...
        });

        a3.push({
            key: 'Total.Death',
            value: v['Total.Death'],
        });

        ...
    } else {
        a3.push({
            key: 'DataDete',
            value: v.DataDete,
        });
        if (v.DataDete === '小计') {
            headerPigFarm.forEach((v2) => {
                a3.push({
                    key: v2.Key + '.Death',
                    value: v[v2.Key + '.Death'],
                });
                a3.push({
                    key: v2.Key + '.Eliminate',
                    value: v[v2.Key + '.Eliminate'],
                });
                a3.push({
                    key: v2.Key + '.Valuable',
                    value: v[v2.Key + '.Valuable'],
                });
            });
            a3.push({
                key: 'Total.Death',
                value: v['Total.Death'],
            });

            ...
        }
        ...
    }
    dataList.push(a3);
});


for (const name in nameCount) {
    if (nameCount.hasOwnProperty(name)) {
        const i = dataList.findIndex((item) => item[0].value === name);
        dataList[i][0].rowspan = nameCount[name];
    }
}

this.dataList = dataLis;

表体数据处理好以后,渲染这边就简单很多了,由于静态动态数据都拼在了一起拼好了,直接根据行列数组渲染就行了,单元格(td)行列的 rowspancolspanjs 部分也进行了按需处理,这样就得到了最开始看到的最终效果

<tbody>
    <tr *ngFor="let data of dataList">
        <ng-container *ngFor="let v of data">
            <ng-container *ngIf="v.rowspan !== 0">
                <td [attr.colspan]="v.colspan" [attr.rowspan]="v.rowspan"><span>{{ v.value }}</span></td>
            </ng-container>
        </ng-container>
    </tr>
</tbody>

提示

colspan 和 rowspan 数字大于1后,对应的行列单元格数量需要减少

写在最后

关于这种客户自定义复杂度较高的报表实现,最复杂的部分可能就是渲染逻辑梳理好以后数据的拼接,当然这个也看前后端的配合情况,如果接口返回的数据格式基于前端数据渲染逻辑的话,可能处理的就比较少了

还有更优雅的实现思路吗?

欢迎大家讨论交流,如果文章感觉有用,随手点个赞再走呗 ^_^ 🥰🥰

微信公众号:草帽Lufei

  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要在 Angular 中使用 ng-zorro-antd 的 nz-table 组件合并表格,可以使用 nzSpanMethod 属性来指定合并规则。具体步骤如下: 1. 在组件中定义表格数据源 dataSource 和表格列定义 columns。 2. 在表格列定义中,对需要合并的列设置 nzSpanMethod 属性,该属性值为一个回调函数,用于计算该列每个单元格的合并行数和列数。 3. 在回调函数中,可以通过参数 row 和 column 获取当前单元格所在的行和列,然后根据业务需求计算出该单元格需要合并的行数和列数。 示例代码如下: ``` <ng-container *ngFor="let col of columns"> <th *ngIf="!col.children" [nzWidth]="col.width || ''" [nzLeft]="col.left || ''" [nzRight]="col.right || ''" [nzAlign]="col.align || 'center'" [nzVerticalAlign]="col.verticalAlign || 'middle'" [nzSpanMethod]="col.spanMethod"> {{ col.title }} </th> <th *ngIf="col.children" [nzColSpan]="col.colSpan" [nzRowSpan]="col.rowSpan"> {{ col.title }} </th> </ng-container> ``` 其中,columns 数组中的每个元素表示一个表格列定义,具体属性如下: - title:列标题。 - width:列宽度。 - left:左侧距离。 - right:右侧距离。 - align:水平对齐方式。 - verticalAlign:垂直对齐方式。 - children:子列定义,用于实现表头多级嵌套。 - colSpan:列合并数。 - rowSpan:行合并数。 - spanMethod:合并规则回调函数。 合并规则回调函数的参数如下: - row:当前单元格所在的行。 - column:当前单元格所在的列。 回调函数需要返回一个对象,包含以下属性: - rowspan:合并的行数。 - colspan:合并的列数。 示例代码如下: ``` spanMethod(row: any, column: any): { rowspan: number, colspan: number } { if (column.key === 'name') { if (row.id === 1) { return { rowspan: 2, colspan: 1 }; } else if (row.id === 3) { return { rowspan: 2, colspan: 1 }; } } return { rowspan: 1, colspan: 1 }; } ``` 以上代码实现了对 name 列的合并,当该列的值为 'John' 或 'Lucy' 时,将该单元格合并到下一行;当该列的值为 'Tom' 或 'Jerry' 时,将该单元格合并到上一行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

草帽lufei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值