easyexcel实现类似统计类型内容加样式导出

前提:为啥要做这么个折磨人的导出呢?因为我们是乙方!好了,废话不多说了,直接进入正题吧。

1、先说一下客户要达到一个什么样的效果吧,直接上图

easyexcel实现类似统计类型内容加样式导出_第1张图片

1.1、 要求
  • 标题根据数据存在N个年份,动态填充N年份+12个月(这是我弄的测试数据,正常是一年12个月)
  • 左侧两列根据内容动态合并单元格
  • 左侧四列固定,右侧年月内容可以左右滑动
  • 内容动态填充,样式根据判断条件填充颜色
  • 整体单元格根据内容自动设置列宽高

2、客户的需求清晰明了,接下来就是我要研究的事情了

2.1、一个excel最先开始的地方肯定是表头

版本

implementation "com.alibaba:easyexcel:3.1.0"
  • 这是一个组合标题,在easyexcel里面用集合双层嵌套就可以解决,它会自动去检测合并,还是很方便的
List<List<String>> headTitles = Lists.newArrayList();

headTitles.add(Lists.newArrayList("项目名称"));
headTitles.add(Lists.newArrayList("品牌"));
headTitles.add(Lists.newArrayList("车身形式"));
headTitles.add(Lists.newArrayList("阀点组"));
headTitles.add(Lists.newArrayList("2021", "1"));
headTitles.add(Lists.newArrayList("2021", "2"));
headTitles.add(Lists.newArrayList("2021", "3"));
headTitles.add(Lists.newArrayList("2021", "4"));   
headTitles.add(Lists.newArrayList("2021", "5"));
headTitles.add(Lists.newArrayList("2021", "6"));
headTitles.add(Lists.newArrayList("2022", "1"));
headTitles.add(Lists.newArrayList("2022", "2"));
headTitles.add(Lists.newArrayList("2022", "3"));
headTitles.add(Lists.newArrayList("2022", "4"));
headTitles.add(Lists.newArrayList("2022", "5"));
headTitles.add(Lists.newArrayList("2022", "6"));
2.2、接下来就是内容和样式了
2.2.1、先看内容吧,比较得先有内容才能去调样式
  1. 老规矩,直接上代码,弄的都是测试数据,正式导出的时候可以根据内容进行调整。
//定义数据体
        List<List<Object>> dataList = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            Object[] data = new Object[16];
            //a1代表蓝色 a2代表绿色
            if (i % 2 == 0) {
                data[0] = "油电混动jy";
                data[1] = "L";
                data[2] = "SUV";
                data[3] = "整车阀点";
                data[6] = "a1G" + i;
                data[8] = "a1G" + i;
                data[10] = "a2G" + i;
                data[12] = "a2G" + i;
            } else {
                data[0] = "纯电动jy";
                data[1] = "MG";
                data[2] = "三厢车";
                data[3] = "整车阀点";
                data[4] = "a1G" + i;
                data[7] = "a2G" + i;
                data[14] = "a2G" + i;
            }
            dataList.add(Arrays.asList(data));
        }
  1. 这里的a1和a2是为了后面内容动态加样式准备的,也可以自定义其他的,只要不和内容重复就可以。
2.2、好了,终于来到最折磨人的时候了———调样式
  1. 先放最后调用整体的代码吧
public void export(HttpServletResponse response) throws IOException {
        //表头
        List<List<String>> headTitles = getTitles();
        //数据
        List<List<Object>> data = getData();
        try {
            String sheetName = URLEncoder.encode("表名", "UTF-8");
            String fileName = sheetName.concat(String.valueOf(System.currentTimeMillis())).concat(".xlsx");
            //response输出文件流
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
            //需要合并的列
            int[] mergeColumnIndex = {0, 1};
            //从哪一行开始合并
            int mergeRowIndex = 2;
            //写入表头,表数据
            EasyExcel.write(response.getOutputStream()).excelType(ExcelTypeEnum.XLSX).head(headTitles).sheet("Sheet0")
                    //设置整体表头和内容的样式
                    .registerWriteHandler(new ProjectExportUtil().cell())
                    //自适应高度
                    .registerWriteHandler(new CustomCellWriteHeightConfig())
                    //自适应宽度
                    .registerWriteHandler(new CustomCellWriteWidthConfig(0))
                    //合并策略、自定义单元格样式
                    .registerWriteHandler(new ExcelFillCellMergeStrategy(mergeColumnIndex, mergeRowIndex))
                    .doWrite(data);
        } catch (Exception e) {
            log.error("", e);
        }
    }
  • 整体表头和内容的样式代码:
public HorizontalCellStyleStrategy cell() {
        //1、 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景设置为蓝灰色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.DARK_TEAL.getIndex());
        //字体
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 14);
        headWriteFont.setColor(IndexedColors.WHITE.getIndex());
        headWriteCellStyle.setWriteFont(headWriteFont);
        //居中
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 2、内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontName("宋体");
        contentWriteFont.setFontHeightInPoints((short) 12);
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }
  • 自适应高度和宽度的代码大同小异,我就只放一个了
public class CustomCellWriteWidthConfig extends AbstractColumnWidthStyleStrategy {
    private Map<Integer, Map<Integer, Integer>> CACHE = new HashMap(8);

    @Override
    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
        if (needSetWidth) {
            Map maxColumnWidthMap = (Map) CACHE.get(writeSheetHolder.getSheetNo());
            if (maxColumnWidthMap == null) {
                maxColumnWidthMap = new HashMap(16);
                CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
            }

            Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
            if (columnWidth >= 0) {
                if (columnWidth > 255) {
                    columnWidth = 255;
                }

                Integer maxColumnWidth = (Integer) ((Map) maxColumnWidthMap).get(cell.getColumnIndex());
                if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                    ((Map) maxColumnWidthMap).put(cell.getColumnIndex(), columnWidth);
                    writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 250);
                }

            }
        }
    }

    private Integer dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData cellData = (CellData) cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return 6;
            } else {
                switch (type) {
                    case STRING:
                        return cellData.getStringValue().getBytes().length;
                    case BOOLEAN:
                        return cellData.getBooleanValue().toString().getBytes().length;
                    case NUMBER:
                        return cellData.getNumberValue().toString().getBytes().length;
                    default:
                        return 6;
                }
            }
        }
    }

}

这里我把默认值给的是6,是为了调整月份单元格的大小,可以根据实际需要进行调整

  • 合并策略和自定义单元格样式这里有点小坑,有更好方案的小伙伴欢迎留言
public class ExcelFillCellMergeStrategy implements CellWriteHandler {

    /**
     * 合并列的下标
     */
    private int[] mergeColumnIndex;
    /**
     * 合并列开始行下标
     */
    private int mergeRowIndex;


    public ExcelFillCellMergeStrategy() {
    }

    public ExcelFillCellMergeStrategy(int[] mergeColumnIndex, int mergeRowIndex) {
        this.mergeColumnIndex = mergeColumnIndex;
        this.mergeRowIndex = mergeRowIndex;
    }

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
    }


    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        //锁定前四列
        Sheet sheet = writeSheetHolder.getSheet();
        sheet.createFreezePane(4, 0);
    }

    @Override
    public void afterCellDispose(CellWriteHandlerContext context) {
        Head headData = context.getHeadData();
        List<String> nameList = headData.getHeadNameList();
        String headName = null;
        if (null != nameList && nameList.size() == 2) {
            headName = nameList.get(1);
        }
        Cell cell = context.getCell();
        Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();
        //自定义单元格样式
        if (BooleanUtils.isNotTrue(context.getHead())) {
            Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
            short colorIndex = 0;
            if (!ObjectUtils.isEmpty(curData)) {
                String value = String.valueOf(curData);
                //蓝色
                if (value.startsWith("a1")) {
                    value = value.replace("a1", "");
                    cell.setCellValue(value);
                    //加样式
                    colorIndex = IndexedColors.SKY_BLUE.getIndex();
                } else if (value.startsWith("a2")) {
                    value = value.replace("a2", "");
                    cell.setCellValue(value);
                    //加样式
                    colorIndex = IndexedColors.BRIGHT_GREEN1.getIndex();
                }
                context.getFirstCellData().setWriteCellStyle(null);
            }
            cell.setCellStyle(getCellStyle(workbook, colorIndex, headName));
        }
        //合并单元格
        WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
        int curRowIndex = cell.getRowIndex();
        int curColIndex = cell.getColumnIndex();
        if (curRowIndex > mergeRowIndex) {
            for (int i = 0; i < mergeColumnIndex.length; i++) {
                if (curColIndex == mergeColumnIndex[i]) {
                    mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
                    break;
                }
            }
        }
    }

    /**
     * 获取自定义样式
     *
     * @param workbook
     * @param bg       背景色
     * @return
     */
    private CellStyle getCellStyle(Workbook workbook, short bg, String headName) {
        CellStyle cellStyle = workbook.createCellStyle();
        if (bg != 0) {
            cellStyle.setFillForegroundColor(bg);
            cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        }
        if (StringUtils.isNotBlank(headName) && StringUtils.equals(headName, "1")) {
            cellStyle.setBorderLeft(BorderStyle.THIN);
            cellStyle.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        }
        return cellStyle;
    }

    /**
     * 当前单元格向上合并
     *
     * @param writeSheetHolder
     * @param cell             当前单元格
     * @param curRowIndex      当前行
     * @param curColIndex      当前列
     */
    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
        Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
        if (null == preCell) {
            return;
        }
        Object preData = preCell.getCellType() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();
        // 将当前单元格数据与上一个单元格数据比较
        boolean dataBool = preData.equals(curData);
        //如果内容相同,则合并
        if (dataBool) {
            Sheet sheet = writeSheetHolder.getSheet();
            List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
            boolean isMerged = false;
            for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
                CellRangeAddress cellRangeAddr = mergeRegions.get(i);
                // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
                if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
                    sheet.removeMergedRegion(i);
                    cellRangeAddr.setLastRow(curRowIndex);
                    sheet.addMergedRegion(cellRangeAddr);
                    isMerged = true;
                }
            }
            // 若上一个单元格未被合并,则新增合并单元
            if (!isMerged) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
                sheet.addMergedRegion(cellRangeAddress);
            }
        }
    }
}
  • 整体上来说已经达到了那个效果,但可以还有些细枝末节的地方需要再调整调整吧,比如那个excel固定列之后的那条竖线能不能根据内容动态延伸之类的可能还需要时间再去做研究了。

  • 再就是,easyexcel本身的功能就已经能够满足大部分场景下的导出了。少部分比较特别的,也有很多方法和策略让我们自己去实现,总体来说很赞了!

  • 好了,文章到这里就结束了,欢迎各位小伙伴提出宝贵的建议!

  • 最后,如果转载请记得注明出处哦!

你可能感兴趣的:(java,java,spring,boot,easyexcel,导出)