easy-poi 一对多导出

news2025/7/19 9:08:33

1. 需求:

某一列上下两行单元格A,B值一样且这两个单元格, 前面所有列对应单元格值一样的话,

就对A,B 两个单元格进行纵向合并单元格

1. 核心思路:

先对数据集的国家,省份,城市...... id 身份证进行排序

国家一列,值相同就合并单元格(直接调用:2 是指的下标 是2 开始;0 是0列

PoiMergeCellUtil.mergeCells(sheet,2,0);

省份一列:

两行数据,前一列的值相同(国家列相同),且当前列对应值也相同就合并单元格

城市一列:

两行数据,第一列+第二列值相同(国家省份值相同),且当前列对应值也相同就合并单元格

其他类似:

POM文件如下:

  <!-- EasyPoi 核心库 -->
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-base</artifactId>
            <version>4.2.0</version>
        </dependency>
        <!-- EasyPoi Web 支持 -->
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-web</artifactId>
            <version>4.2.0</version>
        </dependency>
        <!-- 如果需要使用注解 -->
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-annotation</artifactId>
            <version>4.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.2</version>
        </dependency>

代码如下:

实体类:

package com.example.demo.entity;


import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author guoyiguang
 * @description $
 * @date 2025/4/5$
 */
@Data
public class Boy {

    @Excel(name = "国家",orderNum = "1")
    private String country;

    @Excel(name = "省份",orderNum = "2")
    private String province;

    @Excel(name = "城市",orderNum = "3")
    private String city;

    @Excel(name = "县",orderNum = "4")
    private String county;

    @Excel(name = "城镇",orderNum = "5")
    private String town; // 镇

    @Excel(name = "村",orderNum = "6")
    private String village; // 村

    @Excel(name = "街道",orderNum = "7")
    private String street;

    @Excel(name = "性别",orderNum = "8")
    private String sex;

    @Excel(name = "名称",orderNum = "9")
    private String name;

    @Excel(name = "出生年份",orderNum = "10")
    private String birthYear;

    @Excel(name = "出生月份份",orderNum = "11")
    private String birthMonth; //

    @Excel(name = "ID身份证",orderNum = "12")
    private String idCard; // 身份证标识









}

模拟从数据库获取业务数据:

    public List<Boy> getBoysList(){
        List<Boy> boyList = new ArrayList<>();
        Boy boy = new Boy();
        boy.setCountry("中国");
        boy.setProvince("山西省");
        boy.setCity("晋中市");
        boy.setCounty("平遥县");
        boy.setTown("岳壁乡");
        boy.setVillage("金村");
        boy.setStreet("向阳街道");
        boy.setBirthYear("1990");
        boy.setBirthMonth("02");
        boy.setSex("男");
        boy.setName("张三1");
        boy.setIdCard("张三1");
        boyList.add(boy);



        Boy boy7 = new Boy();
        boy7.setCountry("中国");
        boy7.setProvince("山西省");
        boy7.setCity("晋中市");
        boy7.setCounty("平遥县");
        boy7.setTown("岳壁乡");
        boy7.setVillage("金村");
        boy7.setStreet("向阳街道");
        boy7.setBirthYear("1990");
        boy7.setBirthMonth("02");
        boy7.setSex("男");
        boy7.setName("张三1");
        boy7.setIdCard("张三111");
        boyList.add(boy7);


        Boy boy2 = new Boy();
        boy2.setCountry("中国");
        boy2.setProvince("山西省");
        boy2.setCity("晋中市");
        boy2.setCounty("平遥县");
        boy2.setTown("岳壁乡");
        boy2.setVillage("金村");
        boy2.setStreet("向阳街道-2");
        boy2.setBirthYear("1990");
        boy2.setBirthMonth("02");
        boy2.setSex("男");
        boy2.setName("张三2");
        boy2.setIdCard("张三2");
        boyList.add(boy2);


        Boy boy8 = new Boy();
        boy8.setCountry("中国");
        boy8.setProvince("山西省");
        boy8.setCity("晋中市");
        boy8.setCounty("平遥县");
        boy8.setTown("岳壁乡");
        boy8.setVillage("金村");
        boy8.setStreet("向阳街道-3");
        boy8.setBirthYear("1990");
        boy8.setBirthMonth("02");
        boy8.setSex("男");
        boy8.setName("张三3");
        boy8.setIdCard("张三33");
        boyList.add(boy8);

        Boy boy4 = new Boy();
        boy4.setCountry("中国");
        boy4.setProvince("陕西省");
        boy4.setCity("渭南市");
        boy4.setCounty("渭南县");
        boy4.setTown("渭南乡");
        boy4.setVillage("渭南村");
        boy4.setStreet("渭南向阳街道");
        boy4.setBirthYear("1990");
        boy4.setBirthMonth("02");
        boy4.setSex("男");
        boy4.setName("张三1");
        boy4.setIdCard("渭南张三1");
        boyList.add(boy4);


        Boy boy10 = new Boy();
        boy10.setCountry("中国");
        boy10.setProvince("陕西省");
        boy10.setCity("渭南市");
        boy10.setCounty("渭南县");
        boy10.setTown("渭南乡");
        boy10.setVillage("渭南村");
        boy10.setStreet("渭南向阳街道");
        boy10.setBirthYear("1990");
        boy10.setBirthMonth("02");
        boy10.setSex("男");
        boy10.setName("李四");
        boy10.setIdCard("渭南李四");
        boyList.add(boy10);


        Boy boy5 = new Boy();
        boy5.setCountry("中国");
        boy5.setProvince("陕西省");
        boy5.setCity("渭南市");
        boy5.setCounty("渭南县2");
        boy5.setTown("渭南乡2");
        boy5.setVillage("渭南村2");
        boy5.setStreet("渭南向阳街道");
        boy5.setBirthYear("1990");
        boy5.setBirthMonth("02");
        boy5.setSex("男");
        boy5.setName("张三1");
        boy5.setIdCard("渭南张三1");
        boyList.add(boy5);


        Boy boy9 = new Boy();
        boy9.setCountry("中国");
        boy9.setProvince("陕西省");
        boy9.setCity("咸阳市");
        boy9.setCounty("咸阳县2");
        boy9.setTown("咸阳乡2");
        boy9.setVillage("咸阳村2");
        boy9.setStreet("咸阳向阳街道");
        boy9.setBirthYear("1990");
        boy9.setBirthMonth("02");
        boy9.setSex("男");
        boy9.setName("张三1");
        boy9.setIdCard("咸阳张三1");
        boyList.add(boy9);


        Boy boy3 = new Boy();
        boy3.setCountry("美国");
        boy3.setProvince("美国省");
        boy3.setCity("美国市");
        boy3.setCounty("美国县");
        boy3.setTown("美国乡");
        boy3.setVillage("美国村");
        boy3.setStreet("美国街道");
        boy3.setBirthYear("1990");
        boy3.setBirthMonth("02");
        boy3.setSex("男");
        boy3.setName("美国张三2");
        boy3.setIdCard("美国张三2");
        boyList.add(boy3);

        Boy boy6 = new Boy();
        boy6.setCountry("美国");
        boy6.setProvince("美国省");
        boy6.setCity("美国市");
        boy6.setCounty("美国县");
        boy6.setTown("美国乡");
        boy6.setVillage("美国村-2");
        boy6.setStreet("美国街道");
        boy6.setBirthYear("1990");
        boy6.setBirthMonth("02");
        boy6.setSex("男");
        boy6.setName("美国张三2");
        boy6.setIdCard("美国张三2");
        boyList.add(boy6);



        return boyList;
    }

某一列两个单元格是否合并的工具方法:

    public void setMergeStartEndRow(LinkedList<Pair> list,int curRow,String preLastContent, String preCurContents,String lastContent,String curContent){
        if(!ObjectUtils.isEmpty(preLastContent) && !ObjectUtils.isEmpty(preCurContents) &&  preLastContent.equals(preCurContents)){
            if(lastContent.equals(curContent)){
                if(!CollectionUtils.isEmpty(list)){
                    Pair lastPair = list.getLast();
                    // 某一列要合并的单元格增加了一行
                    if((int)lastPair.getValue() == curRow-1 ){
                        Pair pair = list.removeLast();
                        list.add(Pair.of(pair.getLeft(),curRow));
                    }else{
                        // 某一列这两行要合并
                        list.add(Pair.of(curRow-1,curRow));
                    }
                }else{
                    // 某一列这两行要合并
                    list.add(Pair.of(curRow-1,curRow));
                }
            }else{

            }
        }else{
            // 不相等不处理
        }
    }

测试方法:

 public void exportBoys(HttpServletResponse response) throws IOException {

        List<Boy> boysList = getBoysList();
        //  0 行,0列
        // TOTO 根据字段排序
        boysList.sort(Comparator.comparing(Boy::getCountry)
                        .thenComparing(Boy::getProvince)
                        .thenComparing(Boy::getCity)
                        .thenComparing(Boy::getCounty)
                        .thenComparing(Boy::getTown));

        // 第二列到第十列合并依据:两行数据前一列值相同(更准确的说法:某两行某一列,之前所有列对应的两行数据都相同)且两行数据当前列的value一样
        // eg
        //row1: 中国  北京市  海淀区  西二旗(当前列)
        //row2: 中国  北京市  海淀区  西二旗(当前列)
        // 核心代码:构建 sheet.addMergedRegion(new CellRangeAddress(startRow, endRow, column, column)); 的 startRow 和 endRow
        LinkedList<Pair> secondList = new LinkedList<>();
        LinkedList<Pair> list2 = new LinkedList<>();
        LinkedList<Pair> list3 = new LinkedList<>();
        LinkedList<Pair> list4 = new LinkedList<>();
        LinkedList<Pair> list5 = new LinkedList<>();
        LinkedList<Pair> list6 = new LinkedList<>();
        LinkedList<Pair> list7 = new LinkedList<>();
        LinkedList<Pair> list8 = new LinkedList<>();
        LinkedList<Pair> list9 = new LinkedList<>();
        LinkedList<Pair> list10 = new LinkedList<>();
        for(int row = 0;row <= boysList.size()-1;row++){
            if(row ==0 ){
                continue;
            }
            Boy curBoy = boysList.get(row);
            Boy lastBoy = boysList.get(row-1); // 上一行数据
            // 省份合并(要看前面国家和当前省份是否一样+当前行值和上一行值一样)
           setMergeStartEndRow(secondList,row,lastBoy.getCountry(),curBoy.getCountry(),lastBoy.getProvince(),curBoy.getProvince());

//          城市合并(要看前面国家和前面省份是否一样(前面所有字段值都一样才合并)+当前行值和上一行值一样)
            setMergeStartEndRow(list2,row,lastBoy.getProvince(),curBoy.getProvince(),lastBoy.getCity(),curBoy.getCity());
            // 县合并
            setMergeStartEndRow(list3,row,lastBoy.getCity(),curBoy.getCity(),lastBoy.getCounty(),curBoy.getCounty());
            // 城镇合并
            setMergeStartEndRow(list4,row,lastBoy.getCounty(),curBoy.getCounty(),lastBoy.getTown(),curBoy.getTown());
            // 村合并()
            setMergeStartEndRow(list5,row,lastBoy.getTown(),curBoy.getTown(),lastBoy.getVillage(),curBoy.getVillage());
            // 街道合并
            setMergeStartEndRow(list6,row,lastBoy.getVillage(),curBoy.getVillage(),lastBoy.getStreet(),curBoy.getStreet());
            // 性别合并(城镇+村+街道 都一样才认为横向条件满足)
            setMergeStartEndRow(list7,row,getPrexStr(lastBoy.getTown(),lastBoy.getVillage(),lastBoy.getStreet()),getPrexStr(curBoy.getTown(),curBoy.getVillage(),curBoy.getStreet()),lastBoy.getSex(),curBoy.getSex());
            // name 合并 (城镇+村+街道+性别 都一样才认为横向条件满足)
            setMergeStartEndRow(list8,row,getPrexStr(lastBoy.getTown(),lastBoy.getVillage(),lastBoy.getStreet(),lastBoy.getSex()),getPrexStr(curBoy.getTown(),curBoy.getVillage(),curBoy.getStreet(),curBoy.getSex()),lastBoy.getName(),curBoy.getName());
            //年份合并
            setMergeStartEndRow(list9,row,lastBoy.getName(),curBoy.getName(),lastBoy.getBirthYear(),curBoy.getBirthYear());
            // 月份合并 (城镇+村+街道+性别+名称+年份 都一样才认为横向条件满足)
            setMergeStartEndRow(list10,row,getPrexStr(lastBoy.getTown(),lastBoy.getVillage(),lastBoy.getStreet(),lastBoy.getSex(),lastBoy.getName(),lastBoy.getBirthYear()),getPrexStr(curBoy.getTown(),curBoy.getVillage(),curBoy.getStreet(),curBoy.getSex(),curBoy.getName(),curBoy.getBirthYear()),lastBoy.getBirthMonth(),curBoy.getBirthMonth());

        }
        Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("标题", "副标题"), Boy.class, boysList);
        Sheet sheet = workbook.getSheet("副标题");

        // 第一列
        PoiMergeCellUtil.mergeCells(sheet,2,0);

        secondList.forEach(pair->{
            // 标题占了两行,+2
            sheet.addMergedRegion(new CellRangeAddress((int)pair.getLeft()+2, (int)pair.getValue()+2, 1, 1));

        });
        list2.forEach(pair->{
            // 标题占了两行,+2
            sheet.addMergedRegion(new CellRangeAddress((int)pair.getLeft()+2, (int)pair.getValue()+2, 2, 2));

        });
        list3.forEach(pair->{
            // 标题占了两行,+2
            sheet.addMergedRegion(new CellRangeAddress((int)pair.getLeft()+2, (int)pair.getValue()+2, 3, 3));

        });
        list4.forEach(pair->{
            // 标题占了两行,+2
            sheet.addMergedRegion(new CellRangeAddress((int)pair.getLeft()+2, (int)pair.getValue()+2, 4, 4));

        });
        list5.forEach(pair->{
            // 标题占了两行,+2
            sheet.addMergedRegion(new CellRangeAddress((int)pair.getLeft()+2, (int)pair.getValue()+2, 5, 5));

        });
        list6.forEach(pair->{
            // 标题占了两行,+2
            sheet.addMergedRegion(new CellRangeAddress((int)pair.getLeft()+2, (int)pair.getValue()+2, 6, 6));

        });
        list7.forEach(pair->{
            // 标题占了两行,+2
            sheet.addMergedRegion(new CellRangeAddress((int)pair.getLeft()+2, (int)pair.getValue()+2, 7, 7));

        });
        list8.forEach(pair->{
            // 标题占了两行,+2
            sheet.addMergedRegion(new CellRangeAddress((int)pair.getLeft()+2, (int)pair.getValue()+2, 8, 8));

        });
        list9.forEach(pair->{
            // 标题占了两行,+2
            sheet.addMergedRegion(new CellRangeAddress((int)pair.getLeft()+2, (int)pair.getValue()+2, 9, 9));

        });
        list10.forEach(pair->{
            // 标题占了两行,+2
            sheet.addMergedRegion(new CellRangeAddress((int)pair.getLeft()+2, (int)pair.getValue()+2, 10, 10));

        });
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-disposition", "attachment;filename=data.xlsx");
        workbook.write(response.getOutputStream());


    }
 // 获取前面列集合对应的值字符串
public String getPrexStr(String... strs){
        StringBuilder sb = new StringBuilder();
        for(String str:strs){
            if(!ObjectUtils.isEmpty(str)){
                sb.append("[");
                sb.append(str);
                sb.append("]");
            }else{
                sb.append("[");
                sb.append(str);
                sb.append("]");
            }

        }
        return sb.toString();

    }

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

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

相关文章

python通过调用海康SDK打开工业相机(全流程)

首先打开海康机器人-机器视觉-下载中心 下载最新版的 MVS 安装后打开目录找到 ...\MVS\Development\Samples\Python 将MvImport内所有文件拷贝至工作目录 然后到 C:\Program Files (x86)\Common Files\MVS\Runtime 找到适合自己系统的版本&#xff0c;将整个文件夹拷贝至工…

manim,制作专业的数学公式动画

manim是一个Python第三方库,全称是mathematical animation engine(数学动画引擎)。manim用于解说线性代数、微积分、神经网络、黎曼猜想、傅里叶变换以及四元数等数学概念。 manim使你能够以编程的方式创建精确的数学图形、动画和场景。与传统的几何画板等绘图软件不同,man…

小刚说C语言刷题——第15讲 多分支结构

1.多分支结构 所谓多分支结构是指在选择的时候有多种选择。根据条件满足哪个分支&#xff0c;就走对应分支的语句。 2.语法格式 if(条件1) 语句1; else if(条件2) 语句2; else if(条件3) 语句3; ....... else 语句n; 3.示例代码 从键盘输入三条边的长度&#xff0c;…

[ctfshow web入门] web6

前置知识 入口点(目录)爆破 还记得之前说过网站的入口的吗&#xff0c;我们输入url/xxx&#xff0c;其中如果url/xxx存在&#xff0c;那么访问成功&#xff0c;证明存在这样一个入口点&#xff1b;如果访问失败则证明不存在此入口点。所以我们可以通过遍历url/xxx&#xff0c;…

简单程序语言理论与编译技术·22 实现一个从AST到RISCV的编译器

本文是记录专业课“程序语言理论与编译技术”的部分笔记。 LECTURE 22&#xff08;实现一个从AST到RISCV的编译器&#xff09; 一、问题分析 1、完整的编译器&#xff08;如LLVM&#xff09;需先完成AST到IR的转换&#xff0c;并进行代码优化&#xff0c;再到汇编&#xff0…

lua和C的交互

1.C调用lua例子 #include <iostream> #include <lua.hpp>int main() {//用于创建一个新的lua虚拟机lua_State* L luaL_newstate();luaL_openlibs(L);//打开标准库/*if (luaL_dofile(L, "test.lua") ! LUA_OK) {std::cerr << "Lua error: &…

Css:如何解决绝对定位子元素内容被父级元素overflow:hidden属性剪裁

一、问题描述 今天小伙伴提了一个bug&#xff0c;在点击列表项的“…”按钮应该出现的悬浮菜单显示不完整&#xff1a; 二、问题排查 一般这种问题&#xff0c;是由于悬浮菜单采用的是绝对定位&#xff0c;而父级采用了overflow:hidden属性。但需要注意的是&#xff0c;这里的…

RoMo: Robust Motion Segmentation Improves Structure from Motion

前言 看起来像是一篇投稿CVPR的文章&#xff0c;不知道被哪个瞎眼审稿人拒了。同期还有一篇CVPR被接收的工作Segment Any Motion in Videos&#xff0c;看起来不如这篇直白&#xff08;也可能是因为我先看过spotlesssplats的缘故&#xff09;&#xff0c;后面也应该一并介绍了…

MCP 极简入门 - 三分钟 Cline + Smithery 运行 time 服务

文章目录 一、&#x1f680; 初识Smithery&#xff1a;AI服务的新大陆找到心仪的服务 二、Cline 编辑配置文件&#x1f527;1、打开配置文件2. 添加Time Server配置3. 验证配置效果 三、&#x1f4ac; 实战对话&#xff1a;让AI告诉你时间四、服务管理小技巧&#x1f504;&…

基本机动飞行性能

机动飞行时描述飞机在给定构型和发动机工作状态下改变飞行速度、飞行高度和飞行方向的能力 1. 水平加&#xff08;减&#xff09;速 水平加&#xff08;减&#xff09;速性能反映飞机在水平面内改变直线飞行速度的能力。描述水平加&#xff08;减&#xff09;速性能的参数包括…

【Linux】进程间通信、匿名管道、进程池

一.什么是通信 进程间通信(Inter-Process Communication&#xff0c;IPC),是指在操作系统中&#xff0c;不同进程之间进行数据交换和同步的机制。由于每个进程通常拥有独立的内存空间&#xff0c;进程间无法直接访问对方的内存&#xff0c;因此需要通过特定的机制来实现通信和…

【MATLAB定位例程】TDOA(到达时间差)的chan-tylor,三维环境,附完整代码

该代码实现了基于三维空间的动态目标TDOA定位,结合了Chan算法(解析解)与Taylor级数展开法(迭代优化)的双重优势。 文章目录 运行结果MATLAB代码代码讲解代码功能概述核心算法原理代码结构解析可视化与结果分析运行结果 定位示意图: 三轴状态曲线: 三轴误差曲线: MA…

数字化转型中的开源AI智能客服与S2B2C商城小程序的融合创新

摘要 数字经济时代&#xff0c;企业需通过技术重构用户交互与供应链体系。本文以“开源AI智能客服”“AI智能名片”及“S2B2C商城小程序”为核心&#xff0c;研究三者如何通过技术协同与场景化应用实现企业营销、客户服务与供应链管理的智能化升级。通过案例分析、技术架构设…

重生之我是去噪高手——diffusion model

diffusion model是如何运作的&#xff1f; 想象一下&#xff0c;你有一张清晰的图片。扩散模型的核心思想分为两个过程&#xff1a; 前向过程&#xff08;Forward Process / Diffusion Process&#xff09;&#xff1a;逐步加噪反向过程&#xff08;Reverse Process / Denois…

【C#】.net core 6.0 依赖注入常见问题之一,在构造函数使用的类,都需要注入到容器里,否则会提示如下报错,让DeepSeek找找原因,看看效果

&#x1f339;欢迎来到《小5讲堂》&#x1f339; &#x1f339;这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。&#x1f339; &#x1f339;温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01;&#…

论文阅读笔记——RDT-1B: A DIFFUSION FOUNDATION MODEL FOR BIMANUAL MANIPULATION

RDT-1B 论文 模型表达与泛化能力&#xff1a;由于双臂操作中动作空间维度是单臂空间的两倍&#xff0c;传统方法难以建模其多模态分布。 数据&#xff1a;双臂数据少且不同机器人的物理结构和动作空间差异&#xff08;如关节数、运动范围&#xff09;导致数据分布不一致&#x…

Vue中将pdf文件转为图片

平时开发中,我们经常遇到的场景应该是调用后端接口返回给前端pdf格式的文件流,然后我们可以通过URL.createObjectURL的方式转为object url临时路径然后可以通过window.open的方式来打开一个新的浏览器页签来进行预览,效果如下图: 但有时候这样满足不了的需求,它不想这样预…

day39——输入操作:多值输入

数组输入&#xff1a; int main() {//***** 1、多值输入&#xff08;C&#xff09;/*输入&#xff1a;3 --> 3个值5 4 9*/int n;cin >> n; //输入个数const int MAX_SIZE 0xFFFF;//限定最大个数int a[MAX_SIZE];for (int i 0; i < n; i) {//用 n 作控制输入…

微软的 Copilot 现在可以浏览网页并为您执行操作

在庆祝其 50 岁生日之际&#xff0c;微软正在向其人工智能驱动的 Copilot 聊天机器人传授一些新技巧。 从 BASIC 到 AI&#xff0c;改变世界的公司&#xff1a;微软 微软表示&#xff0c;Copilot 现在可以在“大多数网站”上采取行动&#xff0c;使其能够预订门票、预订餐厅等…

深入理解Python元组:从基础到高级应用

1. 元组基础认知 1.1 什么是元组 不可变序列&#xff1a;元组(tuple)是Python内置的不可变序列类型异构容器&#xff1a;可以存储不同类型的数据&#xff08;与列表类似&#xff09;语法特征&#xff1a;使用圆括号()定义&#xff0c;元素间用逗号分隔 # 基本示例 t1 (1, 2…