一、前言

在之前写的 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;/*** 在单元格上的所有操作完成后调用,遍历每一个单元格,判断是否需要向上合并*/@Overridepublic 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 效果

三、注意事项

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)来设置,两者互补,不冲突。

EasyExcel复杂表头导出(一对多)升级版相关推荐

  1. EasyExcel自定义表头导出模板并封装数据下拉选择

    EasyExcel自定义表头导出模板 首先查询可变数据 动态数据Controller 表头封装 定义导出模板时的下拉数据 最终结果 首先查询可变数据 动态数据Controller @ApiOperat ...

  2. easyExcel自定义表头导出

    当页面列表数据过多,而我们真实需要导出Excel表格的列没有那么多时,则需要支持,仅导出用户选定的列,老样子直接上图上代码. 如:页面全量数据如图,有9列 但实际上,业务有时只需要三列,每次下载后,还 ...

  3. EasyExcel复杂表头导入(一对多)

    一.前言 当前我使用过的导入导出框架有EasyPoi 和 EasyExcel,我用EasyPoi比较多. EasyPoi 框架的导入导出功能,乃至复杂表头的导入导出,网上都有很多示例,我也写过几篇博客 ...

  4. easyexcel 复杂表头、动态表头、复杂数据导出(非注解方式)

    easyexcel 复杂表头.动态表头.复杂数据导出 easyexcel 生成动态复杂表头(非注解)+数据填充(非注解) 实现代码 生成效果图 easyexcel 生成动态复杂表头(非注解)+数据填充 ...

  5. easyExcel动态复杂表头导出

    easyExcel动态复杂表头导出(可直接用) 一.前端代码(仅供参考) 页面代码 <el-button type="primary" icon="el-icon- ...

  6. EasyExcel 动态表头 + 数据单元格合并

    前言 本文想要达到以及最终实现的效果: 要实现这种效果,包含两个部分的操作: 1. 动态表头 EasyExcel 生成 Excel 时要使表头有合并效果,可以采用**注解和非注解(动态表头)**的方法 ...

  7. easypoi导出一对多,合并单元格,且根据内容自适应行高

    easypoi导出一对多,合并单元格,且根据内容自适应行高 EasyPoi一对多导出 一.pom引入依赖 二.导出实体类 excelPoi常用注解说明 @Excel注解 @ExcelCollectio ...

  8. EasyExcel 复杂数据导出

    EasyExcel实现 多标题.动态标题.单元格合并[动态合并最后一行数据],并且实现一个Excel表有多个sheet,一个sheet有多个表: 一.根据文档写了个实体类 二.参照文档写的两个方法,分 ...

  9. 使用EasyExcel实现导入导出功能

    使用EasyExcel实现导入导出功能 一.导出 1.使用ideal新建一个maven项目,并在pom.xml文件中引入EasyExcel依赖 <!--easyexcel实现导入导出--> ...

  10. EASYEXCEL 根据模板导出

    因为EASYEXCEL 里面会默认带出表头,自带的表头如果不需要的话 就用 .needHead(false) 表示不带表头导出 .excelType(ExcelTypeEnum.XLS) 表示导出文件 ...

最新文章

  1. 【开源】博客园文章编辑器4.0版发布
  2. 【自然语言处理】正向、逆向、双向最长匹配算法的 切分效果与速度测评
  3. Oracle 12c 多租户 CDB 与 PDB之 shared undo 与 Local undo 切换
  4. oracle中的数据集合操作
  5. NVIDIA cuda7在centos6.5中的安装
  6. 用GCD来处理大量for loop任务
  7. jstorm 读取mysql_jstorm运维经验转载
  8. 【李宏毅机器学习】Tips for Deep Learning(p14) 学习笔记
  9. 【转】应聘时最漂亮的回答
  10. BZOJ 3571: [Hnoi2014]画框
  11. [Hadoop]SSH免密码登录以及失败解决方案
  12. 关于项目部署到外网后,访问域名失败的原因之一
  13. SwiftyJSON之使用分析
  14. vue 统计中英文字符串长度_Ant Design Vue实现区分中英文分全角/半角字符长度校验功能...
  15. linux新建用户退格键(删除键)无法正常使用的问题总结
  16. 基于verilog的 PRBS编码
  17. 数据库---学生选课查询案例---经典查询题
  18. 年产1万吨L-赖氨酸干粉工厂的设计-发酵工段及车间的设计(lunwen+CAD图纸)
  19. java反射通俗解释,谁来帮用通俗易懂的语言解释下java的反射机制
  20. 【Java】有一头母牛,它每年年初生一头小母牛。每头小母牛从第四个年头开始,每年年初也生一头小母牛。请编程实现在第n年的时候,共有多少头母牛?

热门文章

  1. CentOS7 Oracle11g安装+图示
  2. 分枝杆菌有关的都有哪些文献?
  3. 人工智能算法也许可以面部图像中推断出性取向
  4. Graphics User Guide(Rockchip Linux)
  5. (在腾讯上看到的一片文章,希望在java和Android踌躇或徘徊的人一些启示)国内手机应用开发者6成亏损 广告或是突破口
  6. 当solidworks软件版本低时如何打开高版本文件?
  7. 用编程思维秒懂英语中的五大简单句
  8. 2019-8-2 [Java_JSP] request对象 getRequestDispatcher进阶 MVC模式 EL与JSTL response对象 转发与重定向的具体应用
  9. [BZOJ3714]-[PA2014]Kuglarz-这题还行
  10. 深度学习碰见的报错信息汇总