我正在参与「启航方案」

前语

经过 2 个月的规划开发周期,基于 Luckysheet 的自定义报表功用开发总算阶段完毕了。

现在的报表运用程度仍是比较简略的,但中间涉及到的流程难度是比较大的,现在对其做个总结。

一、概述

问题:自定义报表的需求产生来源?

在许多的内部办理体系里面,都会有些专门的报表模块开发,针对特定的需求,展现一张表固定的字段,然后由程序员进行后端表的规划及数据拼装并在前端页面展现的功用。当遇到一些需求动态生成报表的情况时,以及保护一些动态报表的功用时,就最好有一个自定义报表的功用来完结此需求。而自定义报表的规划是非常重要的,根据规划的程度,完结功用的杂乱度也相差很大。例如有些报表,固定了一些查询条件,然后勾选这些查询条件就默许是总一张表,这就是简略的需求,而自定义报表需求一些更加杂乱的定制功用。

自定义报表优势:

1、自动化:不需求手动收集、汇总数据,录入excel;

2、易操作:设置好后直接查询即可;

3、高价值:发挥起数据剖析的效果;

现在版别的自定义报表特色:

优点:

  • 灵活性强;
  • 基于 luckysheet 开发的自定义报表,具有原生的 excel 报表的优势,具有强壮的功用,例如生成图表保存等;

缺陷:

  • 上手难度比较杂乱,自由度高;
  • 比较简约;

二、规划思路

自定义报表的规划思路是大体需求几个概念:

  • 字段:字段能够理解为表的表头数据,一些报表办理体系中的字段是固定的,经过固定的字段查询特定的数据即可,而真实要做自定义,报表的内容即是动态可保护的,而且需求为这些字段赋予特定的搜索条件;
  • 规矩:规矩则是针对字段亦或表规划的规矩,规矩能够拟定查询时间范围、报表细粒度(日报表、周报表、月报表、年报表、自定义报表)、查询的数据细粒度(分、小时)等、报表数据的填充方向(横向、纵向)等;
  • 表头:能够将报表拆分为两类,一类是日常查询报表,查询日常的数据即可,作为查询运用;另一类是保护报表,运维人员或许需求填入特定的数据,并展现到各个报表的用处;而保护表则需求表头的数据,字段假如在保护表中运用,则升级为表头,这些表头能够在另一个页面烘托展现数据;

当然,主要搞定前两个就能够完结一个自定义报表的雏形了。

在运用 luckysheet 的进程中,发现假如后端将数据返回前端的话,前端把 整个 json 中的 celldata 元素挨个给单元格填充数据赋值的话,就会显得很慢,随着数据量的巨大,烘托的进程或许到达分钟级别,因而,就只能靠后端来完结数据拼装工作了,然后前端拼接数据即可。

后端数据拼装思路大概如下:

luckysheet(3):基于 luckysheet 实现自定义报表

三、实现事例

数据的保存功用依旧经过存储整个大 json 来完结,毕竟简略。

前端向单元格赋值数据的时候能够插入额定的特点信息,告知后端这个单元格的信息是字段信息,代码如下:

    luckysheet.setCellValue(2, 1, {
        m: "字段a",
        v: "字段a",
      fieldId: "1",
   });

后端识别出字段后,往这个字段的下方或右方根据字段规矩查询数据,并拼装数据,往每个单元格赋值即可。

还需求判别出兼并单元格的信息:

private ArrayList<ReportCustomWorkSheetHeader> getReportCustomWorkSheetHeaders(JSONArray jsonArray, List<JSONObject> jsonObjects) {
        ArrayList<ReportCustomWorkSheetHeader> sheetHeaders = new ArrayList<>();
        for (int i = 0; i < jsonObjects.size(); i++) {
            JSONObject jsonObject = (JSONObject) jsonArray.get(i);
            JSONArray celldatas = jsonObject.getJSONArray("celldata");
            List<ReportCustomWorkSheetHeader> collect = (List<ReportCustomWorkSheetHeader>) celldatas.stream().map(e -> {
                ReportCustomWorkSheetHeader header = new ReportCustomWorkSheetHeader();
                JSONObject properties = BeanUtil.copyProperties(e, JSONObject.class);
                header.setRow((Integer) properties.get("r"));
                header.setColumn((Integer) properties.get("c"));
                JSONObject v = (JSONObject) properties.get("v");
                if (v != null) {
                    header.setV((v.get("v")==null?null:v.get("v")+""));
                    header.setM((v.get("m")==null?null:v.get("m")+""));
                    if (v.get("fieldId") != null) {
                        header.setFieldId(Long.valueOf(v.get("fieldId").toString().trim()));
                    }
                    if(v.get("isData") !=null){
                        return null;
                    }
                    header.setCellJson(JSON.toJSONString(v));
                    JSONObject mc = (JSONObject) v.get("mc");
                    if(mc!=null){
                        header.setMcC((Integer) mc.get("c"));
                        header.setMcR((Integer) mc.get("r"));
                        header.setMcRs((Integer) mc.get("rs"));
                        header.setMcCs((Integer) mc.get("cs"));
                    }
                }
                return header;
            }).filter(e->e!=null).collect(Collectors.toList());
            sheetHeaders.addAll(collect);
        }
        return sheetHeaders;
    }

兼并单元格中,假如是上下兼并,则基准点以最下面的单元格为准;假如是左右兼并,则基准点以最右边的单元格为准增加数据。

可得公式如下:

得到数据后,向各个字段的下方或右方赋值:

  private ArrayList<ReportCustomWorkSheetHeader> getResult(ArrayList<ReportCustomWorkSheetHeader> sheetHeaders, LinkedHashMap<String, LinkedHashMap<String, String>> linkedHashMap, ReportCustomWorkSheetRuleSetting allRuleSetting) {
        ArrayList<ReportCustomWorkSheetHeader> insetCellDatas = new ArrayList<>();
        sheetHeaders.stream().filter(sh->sh.getFieldId()!=null).forEach(shs->{
            LinkedHashMap<String, String> valueMap = linkedHashMap.get(shs.getInitFieldId().toString().trim());
            if(CollectionUtil.isNotEmpty(valueMap)){
                int row = shs.getRow();
                int column =shs.getColumn();
                if(shs.getMcC()!=null && shs.getMcCs()!=null){
                    row = shs.getMcRs()+shs.getRow()-1;
                    column = shs.getMcCs()+shs.getColumn() -1;
                }
                for (Map.Entry<String, String> valueEntry : valueMap.entrySet()) {
                    ReportCustomWorkSheetHeader cellData = new ReportCustomWorkSheetHeader();
                    if(allRuleSetting.getFillDirection().equals(ReportConst.REPORT_CUSTOM_FILL_DIRECTION_0.getStatus())){
                        cellData.setRow(row);
                        cellData.setColumn(++column);
                    }else{
                        cellData.setRow(++row);
                        cellData.setColumn(column);
                    }
                    cellData.setIsData(1);
                    cellData.setM(valueEntry.getValue());
                    cellData.setV(valueEntry.getValue());
                    insetCellDatas.add(cellData);
                }
            }
        });
        return insetCellDatas;
    }

然后根据这些数据填充 celldata 数据,返回 json 即可,这是个完整的demo流程规划。

整体来说,功用的杂乱度在于流程的判断条件较多,需求考虑日月年分钟等各种情况的呈现,完结这部分考虑后,一个简略的自定义报表即完结了,最后,感谢 luckysheet 的开源奉献。

前文回顾:

  • luckysheet(1): 在线 excel 介绍及运用
  • luckysheet(2) : 前后端存储展现实战