excel 动态表头与合并列

news2024/11/23 15:32:57

零、希望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);

截图如下

荆轲刺秦王!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/905036.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Docker】存储卷Volume

Docker Volume概念 什么是存储卷 存储卷就是将宿主机的本地文件系统中存在的某个目录直接与容器内部的文件系统上的某一目录建立绑定关系。这就意味着&#xff0c;当我们在容器中的这个目录下写入数据时&#xff0c;容器会将其内容直接写入到宿主机上与此容器建立了绑定关系的…

1139. 最大的以 1 为边界的正方形;2087. 网格图中机器人回家的最小代价;1145. 二叉树着色游戏

1139. 最大的以 1 为边界的正方形 核心思想&#xff1a;枚举正方向的右下角坐标&#xff08;i&#xff0c;j&#xff09;&#xff0c;然后你只需要判断四条边的连续一的最小个数即可&#xff0c;这里是边求连续一的个数同时求解结果。 087. 网格图中机器人回家的最小代价 核心…

PHP自己的框架实现操作成功失败跳转(完善篇四)

1、实现效果&#xff0c;操作成功后失败成功自动跳转 2、创建操作成功失败跳转方法CrlBase.php /**成功后跳转*跳转地址$url* 跳转显示信息$msg* 等待时间$wait* 是否自动跳转$jump*/protected function ok($urlNULL,$msg操作成功,$wait3,$jump1){$code1;include KJ_CORE./tp…

FPGA原理与结构——可配置逻辑块CLB(Configurable Logic Block)

一、什么是CLB 1、CLB简介 可配置逻辑块CLB&#xff08;Configurable Logic Block&#xff09;是xilinx系类FPGA的基本逻辑单元&#xff08;在各系列中CLB可能有所不同&#xff0c;以下我们主要讨论Xilinx 7系类&#xff09;&#xff0c;是实现时序逻辑电路和组合逻辑电…

基于Pytorch构建DenseNet网络对cifar-10进行分类

DenseNet是指Densely connected convolutional networks&#xff08;密集卷积网络&#xff09;。它的优点主要包括有效缓解梯度消失、特征传递更加有效、计算量更小、参数量更小、性能比ResNet更好。它的缺点主要是较大的内存占用。 DenseNet网络与Resnet、GoogleNet类似&#…

如何下载英伟达NVIDIA旧版本驱动,旧版本驱动官方网址

https://www.nvidia.cn/Download/Find.aspx?langcn 也可以直接搜索英伟达官网&#xff0c;点击驱动程序&#xff0c;然后点击试用版驱动程序&#xff0c;里面不但有试用版的驱动&#xff0c;还有之前发布的所有驱动

redis乐观锁+启用事务解决超卖

乐观锁用于监视库存&#xff08;watch&#xff09;&#xff0c;然后接下来就启用事务。 启用事务&#xff0c;将减库存、下单这两个步骤&#xff0c;放到一个事务当中即可解决秒杀问题、防止超卖。 但是&#xff01;&#xff01;&#xff01;乐观锁&#xff0c;会带来" …

Docker 使用归纳总结

mongodb 的 terminal 可执行的命令是基于这个文件夹

【LeetCode】剑指 Offer Ⅱ 第4章:链表(9道题) -- Java Version

题库链接&#xff1a;https://leetcode.cn/problem-list/e8X3pBZi/ 类型题目解决方案双指针剑指 Offer II 021. 删除链表的倒数第 N 个结点双指针 哨兵 ⭐剑指 Offer II 022. 链表中环的入口节点&#xff08;环形链表&#xff09;双指针&#xff1a;二次相遇 ⭐剑指 Offer I…

5.7.webrtc线程的启动与运行

那在上一节课中呢&#xff1f;我向你介绍了web rtc的三大线程&#xff0c;包括了信令线程&#xff0c;工作线程以及网络线程。那同时呢&#xff0c;我们知道了web rtc 3大线程创建的位置以及运行的时机。 对吧&#xff0c;那么今天呢&#xff1f;我们再继续深入了解一下&#…

SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录(第一天)Mybatis的学习

SSM框架的学习与应用(Spring Spring MVC MyBatis)-Java EE企业级应用开发学习记录&#xff08;第一天&#xff09;Mybatis的学习 一、当前的主流框架介绍(这就是后期我会发出来的框架学习) Spring框架 ​ Spring是一个开源框架&#xff0c;是为了解决企业应用程序开发复杂…

【2023最新爬虫】用python爬取知乎任意问题下的全部回答

老规矩&#xff0c;先上结果&#xff1a; 爬取了前200多页&#xff0c;每页5条数据&#xff0c;共1000多条回答。&#xff08;程序设置的自动判断结束页&#xff0c;我是手动break的&#xff09; 共爬到13个字段&#xff0c;包含&#xff1a; 问题id,页码,答主昵称,答主性别,…

Baumer工业相机堡盟工业相机如何通过BGAPISDK设置相机的Bufferlist序列(C#)

Baumer工业相机堡盟工业相机如何通过BGAPISDK设置相机的Bufferlist序列&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机的Bufferlist序列功能的技术背景CameraExplorer如何查看相机Bufferlist功能在BGAPI SDK里通过函数设置相机固定帧率 Baumer工业相机通过BGAPI SDK…

文件同步工具rsync

文章目录 作用特性安装命令服务端启动增加安全认证及免密登录 实时推送源服务器配置结合inotify实现实时推送 参数详解 学些过程中遇到的问题 作用 rsync是linux系统下的数据镜像备份工具。使用快速增量备份工具Remote Sync可以远程同步&#xff0c;支持本地复制&#xff0c;或…

05有监督学习——神经网络

线性模型 给定n维输入&#xff1a; x [ x 1 , x 1 , … , x n ] T x {[{x_1},{x_1}, \ldots ,{x_n}]^T} x[x1​,x1​,…,xn​]T 线性模型有一个n维权重和一个标量偏差: w [ w 1 , w 1 , … , w n ] T , b w {[{w_1},{w_1}, \ldots ,{w_n}]^T},b w[w1​,w1​,…,wn​]T,b 输…

Elasticsearch 处理地理信息

1、GeoHash ​ GeoHash是一种地理坐标编码系统&#xff0c;可以将地理位置按照一定的规则转换为字符串&#xff0c;以方便对地理位置信息建立空间索引。首先要明确的是&#xff0c;GeoHash代表的不是一个点而是一个区域。GeoHash具有两个显著的特点&#xff1a;一是通过改变 G…

7-6 统计字符出现次数

分数 20 全屏浏览题目 切换布局 作者 C课程组 单位 浙江大学 本题要求编写程序&#xff0c;统计并输出某给定字符在给定字符串中出现的次数。 输入格式&#xff1a; 输入第一行给出一个以回车结束的字符串&#xff08;少于80个字符&#xff09;&#xff1b;第二行输入一个…

Android JNI系列详解之JNI、NDK环境搭建和编译工具安装

本文主要介绍JNI、NDK环境变量的搭建&#xff0c;以及CMake工具的安装和ndk-build工具的安装。 一、JNI环境 JNI属于Java中的一部分&#xff0c;所以只需要搭建Java环境就有了JNI的环境&#xff0c;安装Java的环境可以网上查找教程&#xff0c;很多的安装JDK的博客。我电脑是安…

检测输电线上的鸟巢,用SSD结合HSV色彩空间滤波器相结合的检测方法--论文中图还少一张,欠点意思

Detection of Bird Nests on Power Line Patrol Using Single Shot Detector Abstract 电力塔上鸟巢的存在对输电线路的安全稳定构成了威胁。近年来&#xff0c;利用无人机探测输电线路上的鸟巢已成为电力巡检的重要任务之一。图像处理方法从计算机视觉向功率图像识别的迁移日…

MySQL数据库第十四课--------sql优化---------层层递进

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…