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

前言

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

最终效果

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

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

查询逻辑

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

数据展示

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

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

思路分析

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

实现过程

返回接口数据格式

json 复制代码
{
    "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,表头的数据根据不同行的数据,使用不同的数组,三行表头分别进行处理,表头静态动态行列合并渲染如下

html 复制代码
<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 和需要动态循环的那部分数据

js 复制代码
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 得到同类型阶段值数据实现行合并,猪场数据循环拼接得到猪场下的静态三列数据,然后单独拼接小计,总计的数据。

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

js 复制代码
// 表格数据行组装
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 部分也进行了按需处理,这样就得到了最开始看到的最终效果

html 复制代码
<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

相关推荐
qq_3901617721 分钟前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test1 小时前
js下载excel示例demo
前端·javascript·excel
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事1 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶1 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo1 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v1 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
知孤云出岫1 小时前
web 渗透学习指南——初学者防入狱篇
前端·网络安全·渗透·web
贩卖纯净水.1 小时前
Chrome调试工具(查看CSS属性)
前端·chrome
栈老师不回家2 小时前
Vue 计算属性和监听器
前端·javascript·vue.js