excel 动态表头与合并列

零、希望Springboot-java导出excel文件,包括动态表头与下边合并的列

使用 org.apache.poi 与自己封装工具类实现相关功能。代码如下

一、代码

1、依赖

    implementation(group: 'org.apache.poi',name: 'poi-ooxml',version: '4.1.0')
    implementation(group: 'org.apache.poi',name: 'poi',version: '4.1.0')
    implementation(group: 'cn.hutool', name: 'hutool-all', version: '5.8.3')

2、工具类 ExcelMergeUtil.java

import cn.hutool.json.JSONUtil;
import com.longze.fengqx.HeaderNode;
import com.longze.fengqx.PoiModel;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author Fengqx
 * @version 1.0
 * @description: excel文件合并
 * @date 2023/8/20 13:13
 */
public class ExcelMergeUtil {
    public static SXSSFSheet createExcelHead(SXSSFWorkbook book, String sheetName, String headJson){
        List<HeaderNode> headerNodes = JSONUtil.toList(headJson, HeaderNode.class);
        SXSSFSheet sxssfSheet = book.createSheet(sheetName);
        CellStyle headStyle = book.createCellStyle();
        defaultHeadStyle(headStyle);
        //表头层级
        int deep = headerNodes.stream().map(HeaderNode::getRow).reduce(Integer::max).orElse(1);
        for (int i = 0; i < deep; i++) {
            sxssfSheet.createRow(i);
        }
        //创建单元格
        for (HeaderNode headerNode : headerNodes) {
            int row = headerNode.getRow();
            int col = headerNode.getColumn();
            SXSSFCell sxssfCell = sxssfSheet.getRow(row).createCell(col);
            sxssfSheet.setColumnWidth(col, headerNode.getWidth() * 256);
            sxssfCell.setCellStyle(headStyle);
            sxssfCell.setCellValue(headerNode.getHeaderName());

            CellRangeAddress region;
            //是否跨列
            if (headerNode.isOverNode()) {
                region = new CellRangeAddress(row, deep, col, col);
            } else {
                region = new CellRangeAddress(row, row, col, (col + headerNode.getOverNodeCount() - 1));
            }
            if (region.getNumberOfCells() > 1) {
                sxssfSheet.addMergedRegionUnsafe(region);
                //合并后设置下边框
                RegionUtil.setBorderTop(BorderStyle.THIN, region, sxssfSheet);
                RegionUtil.setBorderLeft(BorderStyle.THIN, region, sxssfSheet);
                RegionUtil.setBorderBottom(BorderStyle.THIN, region, sxssfSheet);
                RegionUtil.setBorderRight(BorderStyle.THIN, region, sxssfSheet);
            }
        }
        return sxssfSheet;
    }
    public static void mergeCellFunc(Sheet sheet, String[] title, String[] field, List<Map<String, String>> list, Integer deep,List<Integer> mergeIndex){
        Map<String, List<Map<String, String>>> map = Maps.newHashMap();
        map.put("测试合并数据", list);
        // 模拟大数据量情况下,任务中心可分页查询接口,分批返回数据
        List<List<Map<String, String>>> listList =excelPageByNum(list, 5);
        // 数据总数 + 表头
        int size = listList.stream().mapToInt(List::size).sum() + deep;

        List<PoiModel> poiModels = Lists.newArrayList();
        for (List<Map<String, String>> listmid : listList) {
            for (Map<String, String> mapMid : listmid) {
                int index = sheet.getLastRowNum()+1;
                Row row = sheet.createRow(index);
                for (int i = 0; i < title.length; i++) {
                    String titleField = field[i];
                    String old = null;
                    if (index > deep+1) {
                        old = poiModels.get(i) == null ? null : poiModels.get(i).getContent();
                    }
                    for (int k : mergeIndex) {
                        if (index == deep+1) {
                            PoiModel poiModel =new PoiModel(mapMid.get(titleField),mapMid.get(titleField),null,deep+1,i);
                            poiModels.add(poiModel);
                            break;
                        }
                        PoiModel poiModel = poiModels.get(i);
                        String content = mapMid.get(titleField);
                        // 当前行的当前列与上一行的当前列的内容不一致时,则把当前行以上的合并
                        if (i > 0 && k == i) {
                            // 如果不需要考虑当前行与上一行内容相同,但是它们的前一列内容不一样则不合并的情况,把或条件删除
                            if (!StringUtils.equalsIgnoreCase(content, poiModel.getContent())
//                                    || (StringUtils.equalsIgnoreCase(content, poiModel.getContent()) && !StringUtils.equalsIgnoreCase(poiModels.get(i - 1).getOldContent(),mapMid.get(field[i - 1])))
                            ) {
                                get(poiModel, content, index, i, sheet);
                            }
                        }
                        // 处理第一列的情况
                        if (k == i && i == 0 && !StringUtils.equalsIgnoreCase(content,poiModel.getContent())) {
                            get(poiModel, content, index, i, sheet);
                        }
                        // 最后一行没有后续的行与之比较,所有当到最后一行时则直接合并对应列的相同内容
                        if (k == i && index == size && poiModels.get(i).getRowIndex() != index) {
                            CellRangeAddress cra = new CellRangeAddress(poiModels.get(i).getRowIndex(), index, poiModels.get(i).getCellIndex(), poiModels.get(i).getCellIndex());
                            sheet.addMergedRegion(cra);
                        }
                    }
                    Cell cell = row.createCell(i);
                    cell.setCellValue(mapMid.get(titleField));
                    poiModels.get(i).setOldContent(old);
                }
            }
        }
    }

    /**
     * 表头样式
     *
     * @param headStyle
     */
    private static void defaultHeadStyle(CellStyle headStyle) {
        headStyle.setBorderTop(BorderStyle.THIN);
        headStyle.setBorderLeft(BorderStyle.THIN);
        headStyle.setBorderBottom(BorderStyle.THIN);
        headStyle.setBorderRight(BorderStyle.THIN);
        headStyle.setAlignment(HorizontalAlignment.CENTER);
        headStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        headStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
    }
    //合并单元格
    private static void get(PoiModel poiModel, String content, int index, int i, Sheet sheet) {
        if (poiModel.getRowIndex() != index - 1) {
            CellRangeAddress cra = new CellRangeAddress(poiModel.getRowIndex(), index - 1, poiModel.getCellIndex(), poiModel.getCellIndex());
            //在sheet里增加合并单元格
            sheet.addMergedRegion(cra);
        }
        /*重新记录该列的内容为当前内容,行标记改为当前行标记,列标记则为当前列*/
        poiModel.setContent(content);
        poiModel.setRowIndex(index);
        poiModel.setCellIndex(i);
    }
    public static <T> List<List<T>> excelPageByNum(List<T> list, int pageSize) {
        return IntStream.range(0, list.size()).boxed().filter(t -> t % pageSize == 0).map(t -> list.stream().skip(t).limit(pageSize).collect(Collectors.toList())).collect(Collectors.toList());
    }
}

3、实体对象

HeaderNode.java 和 PoiModel.java

public class PoiModel {
    private String content;

    private String oldContent;

    private String primaryKey;

    private int rowIndex;

    private int cellIndex;

    public PoiModel() {
    }

    public PoiModel(String content, String oldContent, String primaryKey, int rowIndex, int cellIndex) {
        this.content = content;
        this.oldContent = oldContent;
        this.primaryKey = primaryKey;
        this.rowIndex = rowIndex;
        this.cellIndex = cellIndex;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getOldContent() {
        return oldContent;
    }

    public void setOldContent(String oldContent) {
        this.oldContent = oldContent;
    }

    public String getPrimaryKey() {
        return primaryKey;
    }

    public void setPrimaryKey(String primaryKey) {
        this.primaryKey = primaryKey;
    }

    public int getRowIndex() {
        return rowIndex;
    }

    public void setRowIndex(int rowIndex) {
        this.rowIndex = rowIndex;
    }

    public int getCellIndex() {
        return cellIndex;
    }

    public void setCellIndex(int cellIndex) {
        this.cellIndex = cellIndex;
    }
}




public class HeaderNode {

    /**
     * 标题头
     */
    private String headerName;

    /**
     * 层级
     */
    private int row;

    /**
     * 非叶子节点列跨度
     */
    private int overNodeCount;
    /**
     * 当前列没有子节点
     */
    private boolean overNode = true;

    /**
     * 列
     */
    private int column;

    /**
     * 宽度
     */
    private int width = 13;

    public String getHeaderName() {
        return headerName;
    }

    public void setHeaderName(String headerName) {
        this.headerName = headerName;
    }

    public int getRow() {
        return row;
    }

    public void setRow(int row) {
        this.row = row;
    }

    public int getOverNodeCount() {
        return overNodeCount;
    }

    public void setOverNodeCount(int overNodeCount) {
        this.overNodeCount = overNodeCount;
    }

    public boolean isOverNode() {
        return overNode;
    }

    public void setOverNode(boolean overNode) {
        this.overNode = overNode;
    }

    public int getColumn() {
        return column;
    }

    public void setColumn(int column) {
        this.column = column;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }
}

4、下载Controller

 @GetMapping(value = "/downExcel")
    @ResponseBody
    public void downExcel(HttpServletResponse response,@RequestParam(required = true) String type) throws Exception {
        try {
            tengxunService.downExcel(response, type);
        } catch (Exception ex) {
            throw ex;
        }
    }

5、下载service

  @Override
    public void downloadExcel(HttpServletResponse response, String type)throws Exception {
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码
        String fileName = URLEncoder.encode("腾讯充值文件", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
 OutputStream os = response.getOutputStream();
//加工数据
        List<Map<String, String>> list = Lists.newArrayList();
  for(int i=0;i<chongzhiList.size();i++){
            Chongzhi dto=new Chongzhi ();
            list.add(JSONObject.parseObject(JSON.toJSONString(dto),Map.class));
        }

        String weekStart ="08.01";
        String weekEnd ="08.07";
        String nextWeekStart = "08.08";
        String nextWeekEnd ="08.15";
        //合并单元格方法
        try {
            String customizeLabel = "[{\"headerName\":\"区域\",\"column\":0,\"row\":0}," +
                    "{\"headerName\":\"用户姓名\",\"column\":1,\"row\":0}," +
                    "{\"headerName\":\"本周("+weekStart+"-"+weekEnd+")充值记录\",\"column\":2,\"row\":0}," +
                    "{\"headerName\":\"本周("+weekStart+"-"+weekEnd+")充值详情\",\"column\":3,\"row\":0,\"overNodeCount\":4,\"overNode\":false},{\"headerName\":\"充值时间\",\"column\":3,\"row\":1}," +
                    "{\"headerName\":\"充值项目\",\"column\":4,\"row\":\"1\"}," +
                    "{\"headerName\":\"充值方式\",\"column\":5,\"row\":1}," +
                    "{\"headerName\":\"充值金额\",\"column\":6,\"row\":1}," +
                    "{\"headerName\":\"下周("+nextWeekStart+"-"+nextWeekEnd+")充值计划\",\"column\":7,\"row\":0}]";
            //1、生book
            SXSSFWorkbook book = new SXSSFWorkbook();
            //2、生成动态标题
            SXSSFSheet sxssfSheet = ExcelMerge.createExcelHead(book,"Sheet1", customizeLabel);
            //3、取数据对应字段值  汇总
            String[] title = {"区域", "用户姓名", "本周("+weekStart+"-"+weekEnd+")充值记录", "充值时间", "充值项目", "充值方式", "充值金额", "下周("+nextWeekStart+"-"+nextWeekEnd+")充值计划"};
            //4、取数据对应属性的字段值  汇总
            String[] field = {"areaName", "name", "chongzjilu", "chongzTime", "chongzProject", "chongzMethod", "chongzMoney", "nextChongz"};
            //5、需要合并的列
            List<Integer> mergeIndex = Arrays.asList(0,1,7);
            //6、合并
            ExcelMerge.mergeCellFunc(sxssfSheet,title,field, list, sxssfSheet.getLastRowNum(),mergeIndex);
            //创建excel文件 下载
            book.write(os);
        } catch (IOException e){
            logger.error("文件导出失败,错误信息{}",e);
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = new HashMap<>();
            map.put("statusCode", "500");
            map.put("message", "下载文件失败" + e.getMessage());
            response.getWriter().println(JSON.toJSONString(map));
        }finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

三、下载

完事通过controller调用下载接口,直接可以下载出文件

可以任意改表头,与选择是否要合并的字段,当做参数传入,将需要合并的列顺序传入即可完成合并,一步到位,十分方便

复制代码
//5、需要合并的列  
List<Integer> mergeIndex = Arrays.asList(0,1,7);
复制代码
ExcelMerge.mergeCellFunc(sxssfSheet,title,field, list, sxssfSheet.getLastRowNum(),mergeIndex);

截图如下

荆轲刺秦王!

相关推荐
是小崔啊1 小时前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
Excel_easy2 小时前
批量识别工作表中二维码信息-Excel易用宝
excel·wps
m0_7482552611 小时前
easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
前端·excel
小张认为的测试11 小时前
Linux性能监控命令_nmon 安装与使用以及生成分析Excel图表
linux·服务器·测试工具·自动化·php·excel·压力测试
PieroPc17 小时前
Python 写的 智慧记 进销存 辅助 程序 导入导出 excel 可打印
开发语言·python·excel
不吃鱼的羊1 天前
Excel生成DBC脚本源文件
服务器·网络·excel
chenchihwen1 天前
数据分析时的json to excel 转换的好用小工具
数据分析·json·excel
lxxxxl2 天前
C#调用OpenXml,读取excel行数据,遇到空单元跳过现象处理
excel
m0_748246352 天前
前端通过new Blob下载文档流(下载zip或excel)
前端·excel