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);

截图如下

荆轲刺秦王!

相关推荐
inxunoffice1 小时前
批量将多个 Excel 合并成单个文件|批量按文件夹合并 Excel
数据库·excel
初级代码游戏1 小时前
VSTO(C#)Excel开发4:打印设置
excel·vba·vsto
MiniFlyZt1 小时前
excel的导入和下载(poi)
spring boot·spring·excel
weifexie4 小时前
perl、python、tcl语法中读写Excel的模块
linux·python·excel·perl·tcl
TDengine (老段)5 小时前
TDengine 数据对接 EXCEL
大数据·数据库·sql·excel·时序数据库·tdengine
inxunoffice5 小时前
批量将多个 Excel 拆分成多个文件
excel
七月初七7714 小时前
Excel多级联动下拉菜单设置
python·excel·pandas
inxunoffice14 小时前
批量给 Excel 表格添加文字和图片水印
excel
青涩小鱼14 小时前
Excel中国式排名,3种方法!
excel
inxunoffice1 天前
批量删除或修改 Excel 表格的背景图片
excel