likes
comments
collection
share

【原创】Ruoyi若依框架微服务架构ExcelUtil扩展(一)——ExcelExtendUtil

作者站长头像
站长
· 阅读数 13

【原创】Ruoyi若依框架微服务架构ExcelUtil扩展(一)——ExcelExtendUtil

完整代码

因最大行数限制此篇文章仅说明背景、功能实现思路以及使用方法,若想查看完整代码请移步【原创】Ruoyi若依框架微服务架构ExcelUtil扩展完整代码(二)——ExcelExtendUtil

背景

若依框架自带的Excel导出工具类——ExcelUtil基于org.apache.poi,,版本为4.1.2

路径为ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java

因不满足具体工作需求,比如:

1、自定义多级表头

2、灵活的行合并,列合并

3、灵活的单元格背景颜色渲染

所以,根据具体需求

在扩展此需求后,考虑扩展和灵活性,使用什么方式能满足这两点?

经过一番思考和查阅资料之后,感觉采用两种方式,基于约定大于配置原则:

1、设置特定格式Json串

2、设置特定格式的集合

Json串格式

[
      {
        "code":"competitiveItem",
        "name":"竞赛项",
        "colIndex": 0,
        "rowIndex": 0,
        "spanRow": "0,1",
        "spanCol": null
      },
      {
        "code":"deptName",
        "name":"单位名称",
        "colIndex": 1,
        "rowIndex": 0,
        "spanRow": "0,1",
        "spanCol": null
      },
      {
        "code":"unit",
        "name":"单位",
        "colIndex": 2,
        "rowIndex": 0,
        "spanRow": "0,1",
        "spanCol": null
      },
      {
        "code":"monthPlanTarget",
        "name":"月计划指标",
        "colIndex": 3,
        "rowIndex": 0,
        "spanRow": "0,1",
        "spanCol": null
      },
      {
        "children":[
          {
            "code":"planTarget",
            "name":"计划指标",
            "parentCode":"-1",
            "colIndex": 4,
            "rowIndex": 1,
            "spanRow": null,
            "spanCol": null
          },
          {
            "code":"performance",
            "name":"完成量",
            "parentCode":"-1",
            "colIndex": 5,
            "rowIndex": 1,
            "spanRow": null,
            "spanCol": null
          },
          {
            "code":"overproduction",
            "name":"超产量",
            "parentCode":"-1",
            "colIndex": 6,
            "rowIndex": 1,
            "spanRow": null,
            "spanCol": null
          },
          {
            "code":"overproductionRatio",
            "name":"超产比例",
            "parentCode":"-1",
            "colIndex": 7,
            "rowIndex": 1,
            "spanRow": null,
            "spanCol": null
          }
        ],
        "code":"-1",
        "name":"日进度完成情况",
        "colIndex": 4,
        "rowIndex": 0,
        "spanRow": null,
        "spanCol": "4,7"
      },
      {
        "children":[
          {
            "code":"rangePlanTarget",
            "name":"应完成量",
            "parentCode":"-1",
            "colIndex": 8,
            "rowIndex": 1,
            "spanRow": null,
            "spanCol": null
          },
          {
            "code":"rangePerformance",
            "name":"实际完成量",
            "parentCode":"-1",
            "colIndex": 9,
            "rowIndex": 1,
            "spanRow": null,
            "spanCol": null
          },
          {
            "code":"rangeOverproduction",
            "name":"实际超产量",
            "parentCode":"-1",
            "colIndex": 10,
            "rowIndex": 1,
            "spanRow": null,
            "spanCol": null
          },
          {
            "code":"rangeOverproductionRatio",
            "name":"实际超产比",
            "parentCode":"-1",
            "colIndex": 11,
            "rowIndex": 1,
            "spanRow": null,
            "spanCol": null
          }
        ],
        "code":"-1",
        "name":"按时间进度完成情况",
        "colIndex": 8,
        "rowIndex": 0,
        "spanRow": null,
        "spanCol": "8,11"
      }

    ]

集合格式

【原创】Ruoyi若依框架微服务架构ExcelUtil扩展(一)——ExcelExtendUtil

ExcelExtendUtil类新增代码

1、增加属性

	 /**
     * 标题索引
     */
    private Integer titleRowIndex;	
	/**
     * 表头开始的索引
     */
    private int tableTopStartIndex = 1;

    /**
     * 表数据开始的索引
     */
    private int tableDataStartIndex = 2;	
	
	/**
     * 是否开启多级表头
     */
    private boolean openMultilevelTableTop = false;

    /**
     * 表头所占最大行索引
     */
    private int tableTopMaxIndex = 1;

    /**
     * 表头Json数据
     */
    private String tableTopJsonData;

    /**
     * 表头数据集合列表
     */
    private List<JsonData> tableTopJsonDataList;

2、writeSheet修改为以下代码,主要是根据this.openMultilevelTableTop进行判断,如果设置开启自定义多表头,则调用this.createTableHead()方法处理自定义表头的Json数据或集合数据,否则走若依默认的。

	/**
     * 创建写入数据到Sheet
     */
    public void writeSheet()
    {
        // 取出一共有多少个sheet.
        int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize));
        for (int index = 0; index < sheetNo; index++)
        {
            createSheet(sheetNo, index);

            // 产生表头信息开始一行

            this.tableTopStartIndex = this.titleRowIndex+1;
            Row row = sheet.createRow(this.tableTopStartIndex);

            int column = 0;
            // TODO 动态添加多级表头
            if(this.openMultilevelTableTop) {
                try {
                    this.createTableHead();
                    this.tableTopMaxIndex = tableTopRowMap.entrySet().stream().map(Map.Entry::getKey).max(Comparator.comparing(i->i)).get();
                    System.out.println(this.tableTopMaxIndex);
                } catch (Exception e) {
                    e.printStackTrace();
                }


            } else {
                //根据字段注解配置参数填充表头
                for (Object[] os : fields)
                {
                    Excel excel = (Excel) os[1];
                    this.createCell(excel, row, column++);
                }
            }
            //表数据开始索引计算
            this.tableDataStartIndex = this.tableTopStartIndex + this.tableTopMaxIndex;
            Row startDataRow = sheet.createRow(this.tableDataStartIndex);

            if (Type.EXPORT.equals(type))
            {
                fillExcelData(index, startDataRow);
                addStatisticsRow();
            }
        }
    }

3、createTableHead()recursionCreateTableTopCell()方法

 	/**
     * 创建多级表头
     */
    public void createTableHead() {
        List<JsonData> list = new ArrayList<>();
        if(StringUtils.isNotEmpty(tableTopJsonData) || (StringUtils.isNotEmpty(tableTopJsonData) && this.tableTopJsonDataList != null)) {
            JSONArray jsonArray = JSONObject.parseArray(this.tableTopJsonData);
            list = jsonArray.toJavaList(JsonData.class);
        } else if(StringUtils.isEmpty(tableTopJsonData) && this.tableTopJsonDataList != null) {
            list = this.tableTopJsonDataList;
        } else {
            throw new ServiceException("请设置表头Json数据字符串或者列表!");
        }

        Row row = sheet.createRow(this.tableTopStartIndex);
        for(int i = 0; i < list.size(); i++) {
            this.recursionCreateTableTopCell(list.get(i), row);
        }

        //EasyExcel多表头格式 保留
        //List<List<String>> headList = head(list);

    }

    /**
     * 递归-深度优先 创建表格单元格
     * @param jsonData
     * @param row
     * @return
     */
    public Cell recursionCreateTableTopCell(JsonData jsonData, Row row) {
        this.tableTopRowMap.put(row.getRowNum(), row);


        // 创建列
        Cell cell = row.createCell(jsonData.getColIndex().intValue());
        // 写入列信息
        cell.setCellValue(jsonData.getName());
        //setDataValidation2(jsonData.getColIndex().intValue());
        cell.setCellStyle(styles.get("header"));

        //合并规则信息
        String spanRow = jsonData.getSpanRow();
        String spanCol = jsonData.getSpanCol();

		//处理多级表头行合并
        if(spanRow != null) {
            String[] spanRowArray = spanRow.split(",");
            int spanRowStartIndex = Integer.parseInt(spanRowArray[0]);
            int spanRowEndIndex = Integer.parseInt(spanRowArray[1]);
            int spanColStartIndex = jsonData.getColIndex().intValue();
            int spanColEndIndex = jsonData.getColIndex().intValue();

            CellRangeAddress region = new CellRangeAddress(spanRowStartIndex+this.tableTopStartIndex, spanRowEndIndex+this.tableTopStartIndex, spanColStartIndex, spanColEndIndex);
            sheet.addMergedRegion(region);
        }
		//处理多级表头列合并
        if(spanCol != null) {
            String[] spanColArray = spanCol.split(",");
            int spanRowStartIndex = jsonData.getRowIndex().intValue();
            int spanRowEndIndex = jsonData.getRowIndex().intValue();
            int spanColStartIndex = Integer.parseInt(spanColArray[0]);
            int spanColEndIndex = Integer.parseInt(spanColArray[1]);

            CellRangeAddress region = new CellRangeAddress(spanRowStartIndex+this.tableTopStartIndex, spanRowEndIndex+this.tableTopStartIndex, spanColStartIndex, spanColEndIndex);
            sheet.addMergedRegion(region);
        }

		
		//判断是否有子集进入递归
        List<JsonData> children = jsonData.getChildren();
        if(children != null) {
            int rowIndex = row.getRowNum()+1;
            Row historyRow = this.tableTopRowMap.get(rowIndex);
            if(historyRow == null) {
                historyRow = sheet.createRow(rowIndex);
            }
            for(int i= 0; i< children.size(); i++) {
                JsonData sonJsonData = children.get(i);
                System.out.println(sonJsonData);
                this.recursionCreateTableTopCell(sonJsonData, historyRow);
            }
        }

        return cell;
    }

	/**
     * 解析的表头实例对象
     */
    @Data
    static class JsonData {
        /** 唯一标识 */
        private String code;
        /** 子集 */
        private List<JsonData> children;
        /** 父唯一标识 */
        private String parentCode;
        /** 单元格内容 */
        private String name;
        /** 列索引,从0开始 */
        private Long colIndex;
        /** 行索引,从0开始,使用时不考虑标题列所占行 */
        private Long rowIndex;
        /** 行合并,比如0,4 */
        private String spanRow;
        /** 列合并,比如0,4 */
        private String spanCol;
    }

    /**
     * 处理表头数据-深度优先
     * @param data
     * @return
     */
    private static List<List<String>> head(List<JsonData> data) {
        List<List<String>> result = new ArrayList<>();

        List<String> temp = new ArrayList<>();

        // 深度优先搜索 使用递归
        for (JsonData jsonData : data) {
            dfs(result, jsonData, temp);
        }

        return result;
    }

    /**
     * 深度搜索复制
     * @param result
     * @param jsonData
     * @param temp
     */
    private static void dfs(List<List<String>> result, JsonData jsonData, List<String> temp) {
        List<JsonData> datas = jsonData.getChildren();
        temp.add(jsonData.getName());
        if (datas == null) {
            //看似浅复制,其实是深复制
            List<String> head = new ArrayList<>(temp);
            result.add(head);
        } else {
            for (JsonData data : datas) {
                dfs(result, data, temp);
            }
        }

        // 移除最后一个
        temp.remove(temp.size() - 1);
    }

Controller使用

/**
     * 导出龙虎榜
     */
    @Log(title = "龙虎榜", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, ProDataReportQuery query)
    {
        try {
            List<WinnersVo> list = winnersService.getList(query);
            Map<Integer, Map<String, String>> alertBackgroundColorByDynamicRuleMap = new HashMap<>();
            ExcelExtendUtil<WinnersVo> util = new ExcelExtendUtil<>(WinnersVo.class);
			//设置表标题所占行索引
            util.setTitleRowIndex(0); 
			//设置是否开启多级表头合并
            util.setOpenMultilevelTableTop(true);
			//设置多级表头规则Json(可以通过json工具配置好后直接复制过来)
            util.setTableTopJsonData("[\n" +
                    "      {\n" +
                    "        \"code\":\"competitiveItem\",\n" +
                    "        \"name\":\"竞赛项\",\n" +
                    "        \"colIndex\": 0,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": \"0,1\",\n" +
                    "        \"spanCol\": null\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"code\":\"deptName\",\n" +
                    "        \"name\":\"单位名称\",\n" +
                    "        \"colIndex\": 1,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": \"0,1\",\n" +
                    "        \"spanCol\": null\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"code\":\"unit\",\n" +
                    "        \"name\":\"单位\",\n" +
                    "        \"colIndex\": 2,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": \"0,1\",\n" +
                    "        \"spanCol\": null\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"code\":\"monthPlanTarget\",\n" +
                    "        \"name\":\"月计划指标\",\n" +
                    "        \"colIndex\": 3,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": \"0,1\",\n" +
                    "        \"spanCol\": null\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"children\":[\n" +
                    "          {\n" +
                    "            \"code\":\"planTarget\",\n" +
                    "            \"name\":\"计划指标\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 4,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"performance\",\n" +
                    "            \"name\":\"完成量\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 5,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"overproduction\",\n" +
                    "            \"name\":\"超产量\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 6,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"overproductionRatio\",\n" +
                    "            \"name\":\"超产比例\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 7,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          }\n" +
                    "        ],\n" +
                    "        \"code\":\"-1\",\n" +
                    "        \"name\":\"日进度完成情况\",\n" +
                    "        \"colIndex\": 4,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": null,\n" +
                    "        \"spanCol\": \"4,7\"\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"children\":[\n" +
                    "          {\n" +
                    "            \"code\":\"rangePlanTarget\",\n" +
                    "            \"name\":\"应完成量\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 8,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"rangePerformance\",\n" +
                    "            \"name\":\"实际完成量\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 9,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"rangeOverproduction\",\n" +
                    "            \"name\":\"实际超产量\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 10,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"rangeOverproductionRatio\",\n" +
                    "            \"name\":\"实际超产比\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 11,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          }\n" +
                    "        ],\n" +
                    "        \"code\":\"-1\",\n" +
                    "        \"name\":\"按时间进度完成情况\",\n" +
                    "        \"colIndex\": 8,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": null,\n" +
                    "        \"spanCol\": \"8,11\"\n" +
                    "      }\n" +
                    "\n" +
                    "    ]");

            util.exportExcel(response, list, "龙虎榜", "龙虎榜");
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("导出失败!");
        }

    }

表头导出效果

注意:理论上任意层级深度,三级已通过验证,其他层级可进行尝试!

【原创】Ruoyi若依框架微服务架构ExcelUtil扩展(一)——ExcelExtendUtil

特殊说明

1、开启多级表头后,意味着真实数据渲染的开始索引需要往下移动,这里需要动态计算出来真实数据开始的索引位置,即表头的层级所占索引最大值加上标题所占索引;

2、所有的在json或集合中配置的表头信息,涉及到列、行索引都是从0开始计算配置就可以,因为在使用这些进行逻辑处理的时候会自动计算所占索引;

灵活的行、列合并

合并原理

行和列合并的原理是使用CellRangeAddress添加规则:


/**
	参数说明:
	int spanRowStartIndex: 起始行号  ,0是第一行
	int spanRowEndIndex: 终止行号
	int spanColStartIndex: 起始列号,0是第一列
	int spanColEndIndex: 终止列号
从0开始,不是从1开始	
*/
CellRangeAddress region = new CellRangeAddress(spanRowStartIndex, spanRowEndIndex, spanColStartIndex, spanColEndIndex);
sheet.addMergedRegion(region);

通用合并

@Excel注解新增参数

	/**
     * 字段索引
     * @return
     */
    public long index() default -1;
  	/**
     * 合并(行或列)
     * @return
     */
    public String spanCell() default "";

    /**
     * 合并行
     * @return
     */
    public String spanRow() default "";

ExcelExtendUtil实现代码

				//通用合并行
                if(StringUtils.isNotEmpty(spanRow)) {
                   String[] spanRowArray = spanRow.split("\\|");
                   List<String[]> spanRowRuleList = Arrays.stream(spanRowArray).map(i->{
                        return i.split(",");
                    }).collect(Collectors.toList());
                   spanRowRuleList.stream().forEach(i-> {
                       Integer spanRowStart = Integer.parseInt(i[0].trim());
                       Integer spanRowEnd = Integer.parseInt(i[1].trim());
                       Long fieldIndex = null;
                       if(i.length == 3) {
                           fieldIndex = Long.parseLong(i[2].trim());
                       } else if(index != -1){
                           fieldIndex = index;
                       } else {
                           throw new ServiceException("列索引规则配置错误,请检查字段索引值是否配置!");
                       }

                       CellRangeAddress region = new CellRangeAddress(spanRowStart+this.tableDataStartIndex, spanRowEnd+this.tableDataStartIndex, fieldIndex.intValue(), fieldIndex.intValue());
                       sheet.addMergedRegion(region);
                   });
                }
                //通用合并单元格(包括行合并和列合并)
                if(StringUtils.isNotEmpty(spanCell)) {
                    String[] spanCellArray = spanCell.split("\\|");
                    List<String[]> spanCellRuleList = Arrays.stream(spanCellArray).map(i->{
                        return i.split(",");
                    }).collect(Collectors.toList());
                    spanCellRuleList.stream().forEach(i->{
                        Integer spanRowStart = Integer.parseInt(i[0].trim());
                        Integer spanRowEnd = Integer.parseInt(i[1].trim());
                        Integer spanCellStart = Integer.parseInt(i[2].trim());
                        Integer spanCellEnd = Integer.parseInt(i[3].trim());
                        CellRangeAddress region = new CellRangeAddress(spanRowStart+this.tableDataStartIndex, spanRowEnd+this.tableDataStartIndex, spanCellStart, spanCellEnd);
                        sheet.addMergedRegion(region);
                    });
                }

VO对象使用

使用时最好对导出的VO对象的列增加上index字段索引参数,从0开始

  /**
   * 竞赛项
   */
  @Excel(name = "竞赛项", index = 0, spanRow = "0,4|5,8|9,11|12,15|18,22", spanCell = "17,17,0,3|18,18,2,3|19,19,2,3|20,20,2,3|21,21,2,3|22,22,2,3")
  private String competitiveItem;

规则使用说明

1、spanRow 实际是配置索引值,每个规则以|相隔,单个规则0,4表示从索引为0的行合并到索引为4的行,即为行合并开始索引和行合并结束索引

2、spanCell实际是配置索引值,每个规则以|相隔,单个17,17,0,3表示行索引为17的行从列索引为0到列索引为3进行列合并,即索引为17的行合并索引0到3的列

3、注意使用spanCell时,如果spanRow的配置规则已经包括,则使用spanCell进行列合并时最好一行一行的配置,如果不包括则可以只配置spanCell同时进行行和列合并,比如:23,25,0,3表示 行合并从索引23到25,列合并从索引0到3

导出效果

因工作保密原因,不贴图了,可自行测试,望伙计们海含!

快速合并

一个一个的配置索引是为了非常复杂的表格单元格合并,这种我称之为通用合并,但有时一个一个的配置也挺麻烦,所以又增加快速合并功能,目前只实现了相同行合并规则,伙计们可以进行扩展其他规则。

@Excel新增参数

 	/**
     * 是否快速合并
     */
    public boolean isQuickSpan() default false;
    /**
     * 快速合并规则
     * @return
     */
    public QuickSpanRule quickSpanRule() default QuickSpanRule.NULL ;

	/**
     * 快速合并规则枚举
     */
    public enum QuickSpanRule
    {
        NULL(-1, "无规则"),
        IDENTICAL_ROW_MERGE(0, "相同行合并");

        private final int value;
        private final String desc;

        QuickSpanRule(int value, String desc)
        {
            this.value = value;
            this.desc = desc;
        }

        public int value()
        {
            return this.value;
        }
    }

ExcelExtendUtil实现代码

1、新增属性和类

	/**
     * 快速合并计算附加参数
     */
    private Map<Long, ExcelFieldAdditionParams>  fieldAdditionParamsMap;


	/**
     * Excel列辅助参数对象类
     * 解决:
     * 多列同时设置这两个数据会混乱 解决方法:每一列生成一个单独的对象,相互独立
     * this.mergeRowStart = 0;
     * this.mergeRowEnd = 0;
     */
    @Data
    public class ExcelFieldAdditionParams {

        private int mergeRowStart = 0;
        private int mergeRowEnd = 0;
        private int mergeCellStart = 0;
        private int mergeCellEnd = 0;
    }

2、createExcelField()方法初始化fieldAdditionParamsMap参数,为每个列创建一个辅助计算对象ExcelFieldAdditionParams

/**
 * 得到所有定义字段
 */
private void createExcelField()
{
    this.fields = getFields();
    this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
    this.fieldAdditionParamsMap = this.fields.stream().map(i->{
        Excel excel = (Excel) i[1];
        return excel;
    }).filter(excel -> excel.index() != -1).collect(Collectors.toMap(Excel::index, i->new ExcelFieldAdditionParams()));
    this.maxHeight = getRowHeight();
}

3、fillExcelData()方法修改

  /**
     * 填充excel数据
     *
     * @param index 序号
     * @param row 单元格行
     */
    public void fillExcelData(int index, Row row){
		... ...

		//当前行
        int thisLine = startNo;
        for (int i = startNo; i < endNo; i++) {
            ... ...

			//主要新增
            T voPrevious = null;
            //得到上一个导出对象
            if (i != startNo) {
                voPrevious = (T) list.get(i - 1);
            }
            /**
             *取下一个对象   与当前对象对比,如果相同,记住当前列,再与下一个对比,一直对比到不相同,执行合并代码
             * 注解加入 合并行列标识
             */
            int column = 0;
            for (Object[] os : fields) {
                ... ...
                //每个列加入辅助参数对象
                this.addCell(excel, row, vo, field, column++, voPrevious, thisLine);

            }
        }
	}

4、addCell()方法新增参数voPrevious, thisLine和逻辑处理

这里在处理mergeRowStart和mergeRowEnd时,每个列的合并处理对应的值都应该是相互独立的,如果统一使用一个则合并会出现混乱,所以这里采用fieldAdditionParamsMap快速合并计算附加参数,每个列根据自己的ExcelFieldAdditionParams对象来进行独立计算,从而解决合并混乱问题。


    /**
     * 添加单元格
     */
    public Cell addCell(Excel attr, Row row, T vo, Field field, int column, T voPrevious, int thisLine){
		... ...
		// 用于读取对象中的属性
        Object value = getTargetValue(vo, field, attr);

		//主要新增
        //记录上一个对象属性
        Object valuePrevious = null;
        if(voPrevious != null) {
        	valuePrevious = getTargetValue(voPrevious, field, attr);
        }
		... ...

		... ...
				//主要新增
				//快速合并
                if(isQuickSpan) {

                    if (quickSpanRule.value() == Excel.QuickSpanRule.IDENTICAL_ROW_MERGE.value()) {
                        ExcelFieldAdditionParams fieldAdditionParams = this.fieldAdditionParamsMap.get(index);
                        if (value.equals(valuePrevious)) {
                            if (fieldAdditionParams.getMergeRowStart() == 0) {
                               fieldAdditionParams.setMergeRowStart(thisLine - 1);
                            }
                            fieldAdditionParams.setMergeRowEnd(thisLine);
                            //this.mergeRowEnd = thisLine;
                            System.out.println(fieldAdditionParams);
                        } else {
                            if (fieldAdditionParams.getMergeRowStart() != 0 && fieldAdditionParams.getMergeRowEnd()  != 0) {
                                if (fieldAdditionParams.getMergeRowStart() != fieldAdditionParams.getMergeRowEnd()) {
                                    CellRangeAddress region = new CellRangeAddress(fieldAdditionParams.getMergeRowStart(), fieldAdditionParams.getMergeRowEnd(), index.intValue(), index.intValue());
                                    sheet.addMergedRegion(region);
                                    //this.mergeRowStart = 0;
                                    //this.mergeRowEnd = 0;
                                }

                                //多列同时设置这两个数据会混乱 解决方法:每一列生成一个单独的对象,相互独立
                                //this.mergeRowStart = 0;
                                //this.mergeRowEnd = 0;
                                fieldAdditionParams.setMergeRowStart(0);
                                fieldAdditionParams.setMergeRowEnd(0);
                            }
                        }
                    }

                }
		... ...
	}

VO对象使用

主要是两个区域列

  /**
   * 主键
   */
  @JsonSerialize(using = ToStringSerializer.class)
  private Long id;

  /**
   * 项目行code
   */
  private Integer code;
  /**
   * 单位
   */
  @Excel(name = "单位", index = 0, spanRow = "0,13|14,17|21,27|35,36", spanCell = "18,20,0,1|28,30,0,1|31,31,0,1|32,33,0,1|34,34,0,8|37,37,1,4|37,37,6,7|38,38,1,8")
  @ExcelProperty("单位")
  private String mine;
  /**
   * 左侧-区域
   */
  @Excel(name = "区域", index = 1, isQuickSpan=true, quickSpanRule = Excel.QuickSpanRule.IDENTICAL_ROW_MERGE)
  @ExcelProperty("区域")
  private String firstQY;
  /**
   * 左侧-矿废石
   */
  @Excel(name = "矿废石", index = 2)
  @ExcelProperty("矿废石")
  private String firstKfs;
  /**
   * 左侧-车斗
   */
  @Excel(name = "车(斗)", index = 3)
  @ExcelProperty("车(斗)")
  private String firstCar;
  /**
   * 左侧-吨数
   */
  @Excel(name = "吨", index = 4)
  @ExcelProperty("吨")
  private String firstWeight;


  /**
   * 右侧-区域
   */
  @Excel(name = "区域", index = 5, isQuickSpan=true, quickSpanRule = Excel.QuickSpanRule.IDENTICAL_ROW_MERGE)
  @ExcelProperty("区域")
  private String secondQY;
  /**
   * 右侧-矿废石
   */
  @Excel(name = "矿废石", index = 6)
  @ExcelProperty("矿废石")
  private String secondKfs;
  /**
   * 右侧-车斗
   */
  @Excel(name = "车(斗)", index = 7)
  @ExcelProperty("车(斗)")
  private String secondCar;
  /**
   * 右侧-吨数
   */
  @Excel(name = "吨", index = 8)
  @ExcelProperty("吨")
  private String secondWeight;

规则使用说明

使用时最好先设置好index字段索引参数

1、开启快速合并isQuickSpan=true

2、设置快速合并规则quickSpanRule = Excel.QuickSpanRule.IDENTICAL_ROW_MERGE

导出效果

因工作保密原因,不贴图了,可自行测试,望伙计们海含!

灵活的单元格背景颜色渲染

改变单元格背景色方法

两种方式:

  • POI自带颜色根据IndexedColors

    //索引值背景色改变
    CellStyle style = wb.createCellStyle();
    style.setFillForegroundColor(IndexedColors.RED.getIndex());
    style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
    cell.setCellStyle(style);
    
  • 自定义16进制颜色代码

清楚的知道自定义16进制颜色代码非常灵活,则采用这种方式,但是若依自带的ExcelUtil不支持这种,好吧还是自己扩展,既然要扩展就要考虑灵活和扩展性,我理想的状态是:

1、如果你想要单独对某个单元格进行固定的自定义颜色值的背景色改变,直接可以在@Excel注解上设置参数,比较方便。我称之为自定义单元格颜色

2、有可能单元格的颜色是通过真实的业务数据计算所决定的,比如比某个数大就红色,反之就绿色等,这种情况下我希望是给ExcelExtendUtil类设置约定好的改变颜色规则,至于最终的颜色值可以是在业务层就计算处理好了,只需要组合成约定好的颜色规则Setter就可以了,我称之为自定义动态单元格颜色

自定义单元格颜色

@Excel注解新增参数

	/**
     * 是否根据自定义颜色值变色
     * @return
     */
    public boolean isAlertBackgroundColorByCustom() default false;

    /**
     * 自定义颜色值
     * @return
     */
    public String colorValueCustom() default "";

    /**
     * 自定改变颜色指定行索引
     * @return
     */
    public int alertBackgroundColorByCustomRowIndex() default 0;

ExcelExtendUtil实现代码

1、新增方法

 	/**
     * 处理单元格背景颜色根据16进制颜色代码
     * @param cell
     * @param color
     */
    public void handleHexBackgroundColor(Cell cell, String color) {

        try {
            if(StringUtils.isEmpty(color)) throw new ServiceException("颜色代码为空!");
            if(!color.contains("#")) throw new ServiceException("规则中的颜色代码格式不正确,请使用颜色16进制代码!");
            String rgbS = color.substring(1);
            byte[] rgbB = new byte[0]; // get byte array from hex string
            rgbB = Hex.decodeHex(rgbS);
            XSSFColor finalColor = new XSSFColor(rgbB, null); //IndexedColorMap has no usage until now. So it can be set null.
            XSSFCellStyle style = (XSSFCellStyle) wb.createCellStyle();
            style.setAlignment(HorizontalAlignment.CENTER);
            style.setVerticalAlignment(VerticalAlignment.CENTER);
            style.setBorderRight(BorderStyle.THIN);
            style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
            style.setBorderLeft(BorderStyle.THIN);
            style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
            style.setBorderTop(BorderStyle.THIN);
            style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
            style.setBorderBottom(BorderStyle.THIN);
            style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
            Font dataFont = wb.createFont();
            dataFont.setFontName("Arial");
            dataFont.setFontHeightInPoints((short) 10);
            style.setFont(dataFont);
            style.setWrapText(true);
            style.setFillForegroundColor(finalColor);
            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            cell.setCellStyle(style);
        } catch (DecoderException e) {
            e.printStackTrace();
        }

    }

2、addCell()方法逻辑修改

// 创建cell
cell = row.createCell(column);
int align = attr.align().value();

int rowIndex = row.getRowNum();


Cell finalCell = cell;
... ...

// TODO 处理自定义颜色
if(attr.isAlertBackgroundColorByCustom()) {
    if(StringUtils.isEmpty(attr.colorValueCustom())) throw new ServiceException("@Excel为设置自定义颜色值!");
    if(rowIndex == attr.alertBackgroundColorByCustomRowIndex() + this.tableDataStartIndex) {
        String color = attr.colorValueCustom();
        if(StringUtils.isNotEmpty(color)) {
            this.handleHexBackgroundColor(finalCell, color);
        }
    }
}


//默认样式
if(!attr.isAlertBackgroundColorByDynamic() && !attr.isAlertBackgroundColorByCustom()) {
    cell.setCellStyle(styles.get("data" + (align >= 1 && align <= 3 ? align : "")));
}

VO对象使用

package com.ruoyi.system.controller.dispatch.statistical.dto.winners;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.ruoyi.common.core.annotation.Excel;
import com.ruoyi.system.controller.dispatch.quality.centerdown.ScddQualityCenterdownVo;
import com.ruoyi.system.controller.dispatch.statistical.dto.proDataReport.ProductionDataOperationEnum;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

import java.math.BigDecimal;
import java.util.Map;
import java.util.Set;

/**
 * 龙虎榜展示模型
 *
 */
@Data
@Accessors(chain = true)
public class WinnersVo{

  /**
   * 主键
   */
  @JsonSerialize(using = ToStringSerializer.class)
  private Long id;

  /**
   * 项目行code
   */
  private Integer code;

  /**
   * 竞赛项
   */
  @Excel(name = "竞赛项", index = 0, spanRow = "0,4|5,8|9,11|12,15|18,22", spanCell = "17,17,0,3|18,18,2,3|19,19,2,3|20,20,2,3|21,21,2,3|22,22,2,3")
  private String competitiveItem;
  /**
   * 单位(部门名称)
   */
  @Excel(name = "单位名称", index = 1)
  private String deptName;


  /**
   * 单位(计量)
   */
  @Excel(name = "单位" , index = 2)
  private String unit;

  /**
   * 月计划指标
   */
  @Excel(name = "月计划指标", index = 3)
  private String monthPlanTarget;

  /**
   * 计划指标
   */
  @Excel(name = "计划指标", index = 4, isAlertBackgroundColorByCustom = true, colorValueCustom = "#ffff00", alertBackgroundColorByCustomRowIndex = 17)
  private String planTarget;


  /**
   * 完成量
   */
  @Excel(name = "完成量", index = 5, isAlertBackgroundColorByDynamic = true, colorValueField = "color", isAlertBackgroundColorByCustom = true, colorValueCustom = "#5b9bd5", alertBackgroundColorByCustomRowIndex = 17)
  private String performance;


  /**
   * 超产量
   */
  @Excel(name = "超产量", index = 6, isAlertBackgroundColorByCustom = true, colorValueCustom = "#00b050", alertBackgroundColorByCustomRowIndex = 17)
  private String overproduction;


  /**
   * 超产比例
   */
  @Excel(name = "超产比例", index = 7,  isAlertBackgroundColorByCustom = true, colorValueCustom = "#f3541a", alertBackgroundColorByCustomRowIndex = 17)
  private String overproductionRatio;


  /**
   * 固定 计划指标
   */

  private String finalPlanTarget;

  /**
   * 固定 进度
   */

  private String process;



  /**
   * 计划指标-时间范围
   */
  @Excel(name = "应完成量", index = 8, isAlertBackgroundColorByCustom = true, colorValueCustom = "#ffff00", alertBackgroundColorByCustomRowIndex = 17)
  private String rangePlanTarget;


  /**
   * 完成量-时间范围
   */
  @Excel(name = "实际完成量", index = 9, isAlertBackgroundColorByDynamic = true, colorValueField = "rangeColor", isAlertBackgroundColorByCustom = true, colorValueCustom = "#5b9bd5", alertBackgroundColorByCustomRowIndex = 17)
  private String rangePerformance;


  /**
   * 超产量-时间范围
   */
  @Excel(name = "实际超产量", index = 10, isAlertBackgroundColorByCustom = true, colorValueCustom = "#00b050", alertBackgroundColorByCustomRowIndex = 17)
  private String rangeOverproduction;


  /**
   * 超产比例-时间范围
   */
  @Excel(name = "实际超产比", index = 11,  isAlertBackgroundColorByCustom = true, colorValueCustom = "#f3541a", alertBackgroundColorByCustomRowIndex = 17)
  private String rangeOverproductionRatio;


  /**
   * 备注
   */
  private String remark;

  /**
   * 颜色
   */
  private String color;

  /**
   * 颜色-时间范围
   */
  private String rangeColor;

  /**
   * 小数位数
   */
  private Integer scale;

  /**
   * 处理规则
   * ROUND_DOWN
   * ROUND_CEILING
   * ROUND_FLOOR
   * ROUND_HALF_UP
   * ROUND_HALF_DOWN
   * ROUND_HALF_EVEN
   * ROUND_UNNECESSARY
   */
  private Integer scaleRule;

  /**
   * 计划量唯一标识简单
   * 规则:
   * 如果有字典id 规则为:模块名字_字典数据id
   * 如果没有字典id有code 规则为:模块名字_code
   * 如果以上两者都没有 规则为:模块名字_名称_类型
   */
  private Set<String> performanceSoleSign;


  /**
   * 完成量唯一标识简单
   * 规则:
   * 如果有字典id 规则为:模块名字_字典数据id
   * 如果没有字典id有code 规则为:模块名字_code
   * 如果以上两者都没有 规则为:模块名字_名称_类型
   */
  private Set<String> planTargetSoleSign;



  /**
   * 操作指令
   */
  private ProductionDataOperationEnum operationEnum;


  /**
   * 当操作指令为SUM_SIMPLE_COEFFICIENT时 可传入此参数进行参数计算
   */
  private BigDecimal simpleCoefficient;


  /**
   * 当操作指令为SUM_MAP_COEFFICIENT时 可传入此参数进行参数计算
   */
  private BigDecimal mapCoefficient;



  /**
   * 当其中操作指令为CUSTOM时 可传入自定义参数
   */
  private Map<String, Object> customParams;





}

规则使用说明

1、isAlertBackgroundColorByCustom = true 开启自定义单元格颜色

2、colorValueCustom = "#ffff00" 设置颜色值

3、alertBackgroundColorByCustomRowIndex = 17 指定改变颜色单元格所在行索引,从0开始(列索引不用传,会自动对应,也从0开始)

导出效果

主要看竞赛项一行的单元格颜色,即行索引为17

【原创】Ruoyi若依框架微服务架构ExcelUtil扩展(一)——ExcelExtendUtil

自定义动态单元格颜色

@Excel注解新增参数

 	/**
     * 是否根据动态数据改变背景色
     * @return
     */
    public boolean isAlertBackgroundColorByDynamic() default false;

    /**
     * 指定根据列表数据实体类的属性进行变色,默认为color属性
     *
     * @return
     */
    public String colorValueField() default "color";

ExcelExtendUtil实现代码

1、新增属性

/**
 * 根据动态数据改变单元格颜色规则
 * 需与isAlertBackgroundColorByDynamic配合使用
 */
private Map<Integer, Map<String, String>> alertBackgroundColorByDynamicRuleMap;

2、addCell()方法逻辑处理

				// 创建cell
                cell = row.createCell(column);
                int align = attr.align().value();


                int rowIndex = row.getRowNum();


                Cell finalCell = cell;
                // TODO 处理动态数据改变颜色
                if(attr.isAlertBackgroundColorByDynamic()) {
                    if(this.alertBackgroundColorByDynamicRuleMap == null) throw new ServiceException("未设置动态数据改变颜色规则集合!");
                    if(StringUtils.isEmpty(attr.colorValueField())) throw new ServiceException("@Excel为设置定数据实体类的颜色对应属性!");
                    this.alertBackgroundColorByDynamicRuleMap.entrySet().stream().forEach(i->{
                        Map<String, String> colorMap = i.getValue();
                        if(rowIndex == i.getKey()+this.tableDataStartIndex) {
                            String color = colorMap.get(attr.colorValueField());
                            if(StringUtils.isNotEmpty(color)) {

                               this.handleHexBackgroundColor(finalCell, color);

                            }
                        }
                    });

                }

               ... ...


                //默认样式
                if(!attr.isAlertBackgroundColorByDynamic() && !attr.isAlertBackgroundColorByCustom()) {
                    cell.setCellStyle(styles.get("data" + (align >= 1 && align <= 3 ? align : "")));
                }

VO对象使用

自定义单元格颜色参数与其公用并不冲突!

package com.ruoyi.system.controller.dispatch.statistical.dto.winners;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.ruoyi.common.core.annotation.Excel;
import com.ruoyi.system.controller.dispatch.quality.centerdown.ScddQualityCenterdownVo;
import com.ruoyi.system.controller.dispatch.statistical.dto.proDataReport.ProductionDataOperationEnum;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

import java.math.BigDecimal;
import java.util.Map;
import java.util.Set;

/**
 * 龙虎榜展示模型
 *
 */
@Data
@Accessors(chain = true)
public class WinnersVo{

 ... ...
  /**
   * 完成量
   */
  @Excel(name = "完成量", index = 5, isAlertBackgroundColorByDynamic = true, colorValueField = "color", isAlertBackgroundColorByCustom = true, colorValueCustom = "#5b9bd5", alertBackgroundColorByCustomRowIndex = 17)
  private String performance;


 

  /**
   * 完成量-时间范围
   */
  @Excel(name = "实际完成量", index = 9, isAlertBackgroundColorByDynamic = true, colorValueField = "rangeColor", isAlertBackgroundColorByCustom = true, colorValueCustom = "#5b9bd5", alertBackgroundColorByCustomRowIndex = 17)
  private String rangePerformance;


 ... ...

}

Controller使用

以下示例注意://list列表中每条数据存储着color和rangeColor属性,是根据业务已经计算好的对应的颜色值,color和rangeColor需与Vo对象属性@Excel注解的colorValueField参数值对应

 /**
     * 导出龙虎榜
     */
    @Log(title = "龙虎榜", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, ProDataReportQuery query)
    {
        try {
            List<WinnersVo> list = winnersService.getList(query);
            Map<Integer, Map<String, String>> alertBackgroundColorByDynamicRuleMap = new HashMap<>();
			
			//组合动态颜色规则			
			//list列表中每条数据存储着color和rangeColor属性,是根据业务已经计算好的对应的颜色值,color和rangeColor需与Vo对象属性@Excel注解的colorValueField参数值对应
            AtomicReference<Integer> index = new AtomicReference<>(0);
            list.stream().forEach(i-> {
                Map<String, String> temp = new HashMap<>();
                temp.put("color", i.getColor());
                temp.put("rangeColor", i.getRangeColor());
                alertBackgroundColorByDynamicRuleMap.put(index.get(), temp);
               index.getAndSet(index.get() + 1);
            });

            ExcelExtendUtil<WinnersVo> util = new ExcelExtendUtil<>(WinnersVo.class);
			//设置表标题所占行索引
            util.setTitleRowIndex(0);
			//设置动态颜色规则
            util.setAlertBackgroundColorByDynamicRuleMap(alertBackgroundColorByDynamicRuleMap);
			//开启多级表头
            util.setOpenMultilevelTableTop(true);
			//设置多级表头Json
            util.setTableTopJsonData("[\n" +
                    "      {\n" +
                    "        \"code\":\"competitiveItem\",\n" +
                    "        \"name\":\"竞赛项\",\n" +
                    "        \"colIndex\": 0,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": \"0,1\",\n" +
                    "        \"spanCol\": null\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"code\":\"deptName\",\n" +
                    "        \"name\":\"单位名称\",\n" +
                    "        \"colIndex\": 1,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": \"0,1\",\n" +
                    "        \"spanCol\": null\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"code\":\"unit\",\n" +
                    "        \"name\":\"单位\",\n" +
                    "        \"colIndex\": 2,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": \"0,1\",\n" +
                    "        \"spanCol\": null\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"code\":\"monthPlanTarget\",\n" +
                    "        \"name\":\"月计划指标\",\n" +
                    "        \"colIndex\": 3,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": \"0,1\",\n" +
                    "        \"spanCol\": null\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"children\":[\n" +
                    "          {\n" +
                    "            \"code\":\"planTarget\",\n" +
                    "            \"name\":\"计划指标\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 4,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"performance\",\n" +
                    "            \"name\":\"完成量\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 5,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"overproduction\",\n" +
                    "            \"name\":\"超产量\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 6,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"overproductionRatio\",\n" +
                    "            \"name\":\"超产比例\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 7,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          }\n" +
                    "        ],\n" +
                    "        \"code\":\"-1\",\n" +
                    "        \"name\":\"日进度完成情况\",\n" +
                    "        \"colIndex\": 4,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": null,\n" +
                    "        \"spanCol\": \"4,7\"\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"children\":[\n" +
                    "          {\n" +
                    "            \"code\":\"rangePlanTarget\",\n" +
                    "            \"name\":\"应完成量\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 8,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"rangePerformance\",\n" +
                    "            \"name\":\"实际完成量\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 9,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"rangeOverproduction\",\n" +
                    "            \"name\":\"实际超产量\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 10,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          },\n" +
                    "          {\n" +
                    "            \"code\":\"rangeOverproductionRatio\",\n" +
                    "            \"name\":\"实际超产比\",\n" +
                    "            \"parentCode\":\"-1\",\n" +
                    "            \"colIndex\": 11,\n" +
                    "            \"rowIndex\": 1,\n" +
                    "            \"spanRow\": null,\n" +
                    "            \"spanCol\": null\n" +
                    "          }\n" +
                    "        ],\n" +
                    "        \"code\":\"-1\",\n" +
                    "        \"name\":\"按时间进度完成情况\",\n" +
                    "        \"colIndex\": 8,\n" +
                    "        \"rowIndex\": 0,\n" +
                    "        \"spanRow\": null,\n" +
                    "        \"spanCol\": \"8,11\"\n" +
                    "      }\n" +
                    "\n" +
                    "    ]");
            System.out.println(util);
            util.exportExcel(response, list, "生产调度-统计分析-龙虎榜", "龙虎榜");
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("导出失败!");
        }

    }

规则使用说明

1、isAlertBackgroundColorByDynamic = true是否开启自定义动态单元格颜色

2、colorValueField = "rangeColor"动态颜色规则中颜色Map的key值,则取颜色Map中rangeColor的16进制颜色值

3、注意colorValueField和Controller传入的动态颜色规则Map中颜色Map取数的key的对应关系

导出效果

【原创】Ruoyi若依框架微服务架构ExcelUtil扩展(一)——ExcelExtendUtil

结语

如果有其他更好的思路或其他问题欢迎评论和留言,只是根据业务做了一些扩展,能力有限,大佬勿喷! 因最大行数限制,下一篇文章给出@Excel注解和ExcelExtendUtil完整代码

又是敲代码的一天~