EasyExcel复杂表头导出(一对多)升级版

这篇具有很好参考价值的文章主要介绍了EasyExcel复杂表头导出(一对多)升级版。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

        在之前写的 EasyExcel复杂表头导出(一对多)的博客的结尾,受限于当时的能力和精力,留下一些问题及展望。现在写下此博客,目的就是解决之前遗留的问题。

        背景介绍,见上述链接指向的博客,这里主要通过自定义拦截器的形式来完美解决。

二、导出功能的实现

2.1 Entity 对象

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.converters.string.StringImageConverter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.net.URL;

@Data
@EqualsAndHashCode
@HeadRowHeight(30)
@ContentRowHeight(80)
@ColumnWidth(15)
@HeadStyle(fillForegroundColor = 44)
@NoArgsConstructor
@AllArgsConstructor
class Customer {

    @ExcelProperty({"客户编号"})
    private String userCode;

    @ExcelProperty({"客户名称"})
    private String userName;

    @ColumnWidth(25)
    @ExcelProperty({"客户所在地址"})
    private String address;

    @ExcelProperty({"联系人信息", "联系人姓名"})
    private String personName;

    @ExcelProperty({"联系人信息", "联系电话"})
    private String telephone;

    @ExcelProperty({"图片"})
    private URL picture;

    /**
     * 你也可以通过字符串的形式来保存图片,具体说明见注意事项3.1
     */
    //@ExcelProperty(converter = StringImageConverter.class, value = {"本地图片"})
    //private String localPic;
}

2.2 Controller 层

@PostMapping("/exportExcel")
@ApiOperation("导出Excel")
public void exportExcel(HttpServletResponse response) throws Exception {
	// 查询需要导出的数据
	List result = getData();

	// 1设置表头样式
	WriteCellStyle headStyle = new WriteCellStyle();
	// 1.1设置表头数据居中
	headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

	// 2设置表格内容样式
	WriteCellStyle bodyStyle = new WriteCellStyle();
	// 2.1设置表格内容水平居中
	bodyStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
	// 2.2设置表格内容垂直居中
	bodyStyle.setVerticalAlignment(VerticalAlignment.CENTER);

	// 3设置表格sheet样式
	WriteSheet sheet = EasyExcel.writerSheet("客户信息").head(Customer.class).sheetNo(1).build();
	// 4拿到表格处理对象
	ExcelWriter writer = EasyExcel.write(response.getOutputStream()).needHead(true).excelType(ExcelTypeEnum.XLSX)
            // 设置需要待合并的行和列。参数1:数值数组,指定需要合并的列;参数2:数值,指定从第几行开始合并
			.registerWriteHandler(new ExcelMergeCellHandler(new int[]{0, 1, 2, 5}, 0))
            // 设置单元格的风格样式
			.registerWriteHandler(new HorizontalCellStyleStrategy(headStyle, bodyStyle))
			.build();

	// 5写入excel数据
	writer.write(result, sheet);
	// 6通知浏览器以附件的形式下载处理,设置返回头要注意文件名有中文
	response.setHeader("Content-disposition", "attachment;filename=" + new String("客户信息表".getBytes("gb2312"), "ISO8859-1") + ".xlsx");
	response.setContentType("multipart/form-data");
	response.setCharacterEncoding("utf-8");
	writer.finish();
}

2.3 自定义拦截器(ExcelMergeCellHandler)

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.List;

/**
 * @author DaHuaJia
 * @Description 自定义单元格合并处理Handler类
 * @Date 2022-08-18 19:25:58
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExcelMergeCellHandler implements CellWriteHandler {
    // 需要合并的列,从0开始算
    private int[] mergeColIndex;
    // 从指定的行开始合并,从0开始算
    private int mergeRowIndex;

    /**
     * 在单元格上的所有操作完成后调用,遍历每一个单元格,判断是否需要向上合并
     */
    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // 获取当前单元格行下标
        int currRowIndex = cell.getRowIndex();
        // 获取当前单元格列下标
        int currColIndex = cell.getColumnIndex();
        // 判断是否大于指定行下标,如果大于则判断列是否也在指定的需要的合并单元列集合中
        if (currRowIndex > mergeRowIndex) {
            for (int i = 0; i < mergeColIndex.length; i++) {
                if (currColIndex == mergeColIndex[i]) {
                    /**
                     * 获取列表数据的唯一标识。不同集合的数据即使数值相同也不合并
                     * 注意:我这里的唯一标识为客户编号(Customer.userCode),在第一列,即下标为0。大家需要结合业务逻辑来做修改
                     */
                    // 获取当前单元格所在的行数据的唯一标识
                    Object currCode = cell.getRow().getCell(0).getStringCellValue();
                    // 获取当前单元格的正上方的单元格所在的行数据的唯一标识
                    Object preCode = cell.getSheet().getRow(currRowIndex - 1).getCell(0).getStringCellValue();
                    // 判断两条数据的是否是同一集合,只有同一集合的数据才能合并单元格
                    if(preCode.equals(currCode)){
                        // 如果都符合条件,则向上合并单元格
                        mergeWithPrevRow(writeSheetHolder, cell, currRowIndex, currColIndex);
                        break;
                    }
                }
            }
        }
    }

    /**
     * 当前单元格向上合并
     *
     * @param writeSheetHolder 表格处理句柄
     * @param cell             当前单元格
     * @param currRowIndex     当前行
     * @param currColIndex     当前列
     */
    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int currRowIndex, int currColIndex) {
        // 获取当前单元格数值
        Object currData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
        // 获取当前单元格正上方的单元格对象
        Cell preCell = cell.getSheet().getRow(currRowIndex - 1).getCell(currColIndex);
        // 获取当前单元格正上方的单元格的数值
        Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();

        // 将当前单元格数值与其正上方单元格的数值比较
        if (preData.equals(currData)) {
            Sheet sheet = writeSheetHolder.getSheet();
            List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
            // 当前单元格的正上方单元格是否是已合并单元格
            boolean isMerged = false;
            for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
                CellRangeAddress address = mergeRegions.get(i);
                // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
                if (address.isInRange(currRowIndex - 1, currColIndex)) {
                    sheet.removeMergedRegion(i);
                    address.setLastRow(currRowIndex);
                    sheet.addMergedRegion(address);
                    isMerged = true;
                }
            }
            // 若上一个单元格未被合并,则新增合并单元
            if (!isMerged) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(currRowIndex - 1, currRowIndex, currColIndex, currColIndex);
                sheet.addMergedRegion(cellRangeAddress);
            }
        }
    }
}

2.4 getDate方法(用于模拟service层拿到的数据)

public static List<Customer> getData() throws Exception {
	List<Customer> data = new ArrayList<>();

	Customer customer = new Customer("JiangXi", "江西电信公司", "江西省南昌市东湖区", "张三", "12345678910", new URL("https://m.360buyimg.com/babel/jfs/t1/221733/11/14107/61280/62fde84dE467522ce/79bbd42aa93f5a83.jpg"));
	data.add(customer);
	Customer customer2 = new Customer("JiangXi", "江西电信公司", "江西省南昌市东湖区", "李四", "15848563521", new URL("https://m.360buyimg.com/babel/jfs/t1/221733/11/14107/61280/62fde84dE467522ce/79bbd42aa93f5a83.jpg"));
	data.add(customer2);

	Customer customer3 = new Customer("GuangDong", "广东电信公司", "广东省广州市花都区", "小明", "15847953624", new URL("https://m.360buyimg.com/babel/jfs/t1/215924/36/19623/23344/62baa985E4df523c6/4893237860b306d6.jpg"));
	data.add(customer3);
	Customer customer4 = new Customer("GuangDong", "广东电信公司", "广东省广州市天河区", "小红", "16849531548", new URL("https://m.360buyimg.com/babel/jfs/t1/189640/15/26493/35837/62baa97eE6abda209/461f91e682d0e81a.jpg"));
	data.add(customer4);
	Customer customer5 = new Customer("GuangDong", "广东电信公司", "广东省广州市天河区", "小华", "16985632481", new URL("https://m.360buyimg.com/babel/jfs/t1/189640/15/26493/35837/62baa97eE6abda209/461f91e682d0e81a.jpg"));
	data.add(customer5);

	Customer customer6 = new Customer("BeiJing", "北京电信公司", "北京市东城区", "姜维", "16598645874", new URL("https://m.360buyimg.com/babel/jfs/t1/31481/11/16081/24873/62baa97dE6f3991d0/94ae13d66b9bbfdd.jpg"));
	data.add(customer6);

	return data;
}

2.5 效果

EasyExcel复杂表头导出(一对多)升级版

 三、注意事项

3.1 图片导出问题

对于图片的导出,其字段可以有多种数据类型,官网就介绍了5种(File、InputStream、String、byte[]、URL、WriteCellData<Void>)。这里简要介绍一下String 和 URL。

1、String 类型

/**
*  如果图片地址通过String类型保存,则需要加一个自带的类型转换器(StringImageConverter)
*/
@ExcelProperty(converter = StringImageConverter.class, value = {"本地图片"})
private String localPic;

2、URL类型 

@ExcelProperty({"网络图片"})
private URL picture;

        经过测试发现,String类型只能保存本地图片地址,如果保存网络图片地址,则会导致图片无法下载。原因则是EasyExcel会把“//” 转换成 “\”,导致地址错误。

        因此,可以约定String类型用于保存本地图片地址,URL类型用于保存网络图片地址。

3.2 图片单元格合并问题

        图片类型单元格无法做到相同的图片合并单元格,主要是因为无法通过单元格对象拿到图片的序列化值。

3.3 表格样式

        表格的样式既可以表格样式类(例如:WriteCellStyle)来设置,也可以通过注解(例如:@HeadStyle)来设置,两者互补,不冲突。文章来源地址https://www.toymoban.com/news/detail-463939.html

到了这里,关于EasyExcel复杂表头导出(一对多)升级版的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 单身狗问题(初级)兼(升级版)

    所属专栏:经典算法题❤️ 🚀 博主首页:初阳785❤️ 🚀 代码托管:chuyang785❤️ 🚀 感谢大家的支持,您的点赞和关注是对我最大的支持!!!❤️ 🚀 博主也会更加的努力,创作出更优质的博文!!❤️ 🚀 关注我,关注我,关注我,重要的事情说三遍!!!!!!!!

    2024年02月16日
    浏览(41)
  • 爬虫练习-12306自动购票升级版

    hello兄弟们,偷懒归来了。别问为啥这么久没更,问就是失踪了 最近一直在学习Django以及爬虫进阶之类的知识,加上快期末了,一直没有想起来自己还有一个账号没有更新,sorry啦 言归正传,今天抽空把前面的文章升级了一下。这里先把整理好的代码提前放给大家 代码放上,

    2024年02月06日
    浏览(59)
  • P1553 数字反转(升级版)(JAVA)

    以下为原题面,仅供参考: 给定一个数,请将该数各个位上数字反转得到一个新数。 这次与 NOIp2011 普及组第一题不同的是:这个数可以是小数,分数,百分数,整数。整数反转是将所有数位对调;小数反转是把整数部分的数反转,再将小数部分的数反转,不交换整数部分与小

    2024年02月13日
    浏览(40)
  • 云计算中网络基础知识(升级版)

    网络相连:电脑-交换机-电脑 需要配置两个地址:(1) IP地址、子网掩码、网关(网络的出口)。 (2) MAC 地址(物理地址)不需要配置,电脑网卡自带的(天生就有) 电脑必须要配置IP地址、子网掩码 才可以通信、网关可以不用(前提是两台电脑处在同一个网段下) 网关

    2023年04月27日
    浏览(39)
  • Python数据挖掘 | 升级版自动查核酸

    📕作者简介: 热爱跑步的恒川 ,致力于C/C++、Java、Python等多编程语言,热爱跑步,喜爱音乐的一位博主。 📗本文收录于恒川的日常汇报系列,大家有兴趣的可以看一看 📘相关专栏C语言初阶、C语言进阶系列、恒川等,大家有兴趣的可以看一看 📙Python零基础入门系列,J

    2024年02月08日
    浏览(43)
  • 【Unity+MySQL】实现注册登录系统(升级版)

    接着 上篇文章所谈到的系统缺陷,这篇文章进行升级解决。 问题 :注册界面与登录界面是同一个界面,导致用户输入用户密码进行注册后,即可点击登录。 解决 :在同一个场景中分别创建注册界面和登录界面,使用SetActive控制注册/登录成功后UI的显示与隐藏。 整体的UI框

    2024年02月09日
    浏览(43)
  • 洛谷-P1478-陶陶摘苹果(升级版)(贪心)

    又是一年秋季时,陶陶家的苹果树结了 n n n 个果子。陶陶又跑去摘苹果,这次他有一个 a a a 公分的椅子。当他手够不着时,他会站到椅子上再试试。 这次与 NOIp2005 普及组第一题不同的是:陶陶之前搬凳子,力气只剩下 s s s 了。当然,每次摘苹果时都要用一定的力气。陶陶

    2024年02月21日
    浏览(40)
  • Python-敲木鱼升级版(真手动版敲木鱼)

    演示效果 需要安装的第三方库: pip install pygame # 加载音乐 pip install pillow # 加载图片 pip install mediapipe # 判断手势的模型 pip install opencv # 模型要用来处理图形 建议有独显和摄像头的可以尝试! 想着升级一下玩法,只有真敲才能真积徳!于是找了个能判断手势的模型。 源码(

    2024年02月05日
    浏览(39)
  • 无门槛访问ChatGPT升级版-数据指北AI

    大家好,我是脚丫先生 (o^^o) 给小伙伴们介绍ChatGPT升级版不需要任何门槛,不需要单独搞账号,只要 邮箱 登录的方式,即可访问平台,以用户体验为首要,让所有人都能无门槛的使用目前市面上最强大的AI智能聊天,以及市面上最优秀的AI绘画等等功能,接下来给大家介绍下

    2024年02月10日
    浏览(43)
  • SpringCloud是SpringBoot 的升级版吗?有什么区别?

    目录 一、什么是SpringBoot 二、什么是SpringCloud 三、SpringCloud是SpringBoot 的升级版吗 四、SpringCloud和SpringBoot 有什么区别   Spring Boot是一种用于快速构建基于Spring框架的Java应用程序的开发框架。它简化了Spring应用程序的配置和部署过程,提供了一种便捷的开发方式,同时也提供了

    2024年02月15日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包