首页 >> 大全

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

2023-11-13 大全 28 作者:考证青年

一、前言

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

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

二、导出功能的实现 2. 对象

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 层

@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 自定义拦截器(r)

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> 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 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.方法(用于模拟层拿到的数据)

public static List getData() throws Exception {List 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、、、byte[]、URL、)。这里简要介绍一下 和 URL。

1、 类型

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

2、URL类型

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

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

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

3.2 图片单元格合并问题

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

3.3 表格样式

表格的样式既可以表格样式类(例如:)来设置,也可以通过注解(例如:@)来设置,两者互补,不冲突。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了