1 需求背景

最近公司需要做一个动态字段的Excel导出,大致的样式如下:

实体类如下:

// 部门实体类
public class Department {private String companyName;private String name;private String fullName;private String leaderName;private String business;private Long count;private String location;private String status;private List<Staff> staffList;
}
//员工实体类
public class Staff {private Long id;private String name;private Long age;private String position;private BigDecimal salary;private String code;
}

具体要求如下:

  1. 标题部分需要合并单元格;
  2. 展示部门信息的表格需要横向展示数据,且用户可以选择导出哪些字段的数据;
  3. 展示员工信息部分的数据为一个表格,用户可以选择导出哪些字段的数据。

如图所示,Excel可以被分成三个部分:

这三个部分都需要动态生成,理由如下:

  1. 标题部分需要展示公司信息以及导出的是什么Excel(例如销售单、物流单等等),后续可能需要展示更多的信息,也就是说行数是动态的。并且图中的第三个部分也就是列表数据部分字段的个数是动态的,也就是说标题部分的列数也是动态的;
  2. 公共部分同理,且公共部分的字段也需要可选择的导出;
  3. 列表数据部分的字段个数以及数据的条数都是动态的,那么行和列都是动态的;

综上,我们现在的问题如下:

  1. 三个部分的表格行、列都是动态的;
  2. 三个部分在一个Excel中导出,列宽都会互相影响,如何自适应列宽?
  3. 公共部分的表格并不是传统的竖向表格,是横向排列的。
  4. 如何对应字段名和在Excel中显示的字段名的关系?比如某个字段在代码中为name,但Excel中应该显示为姓名
  5. 如何确定字段之间在Excel导出时的顺序,譬如name age salary三个字段,我希望它们按照name-age-salary的顺序显示,如果用户只导出name和salary字段,那顺序也应该是name-salary而不是其他的。

2 抽象导出实体类

如之前所说,可以将Excel分为三个部分,那么此时就可以构建一个类用以支持Excel的动态导出,具体代码如下:

// Excel实体类
public class ExportCustomCommon {// 标题部分,应该有几行就有几条List<String> headerTable;// 公共部分ExcelCommonData commonTable;// 列表数据部分List<? extends ExcelListData> listTable;// 列表数据的实体类,没有任何属性,仅作为一个对象的静态类型,方便统一处理public static class ExcelListData{}// 公共数据的实体类,没有任何属性,仅作为一个对象的静态类型,方便统一处理public static class ExcelCommonData{}
}

3 EasyExcel实现

引入EasyExcel依赖,我在这里使用的版本是2.2.10 。

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.10</version>
</dependency>

首先需要知道,EasyExcel中有一个@ExcelProperty的注解,代码如下:

package com.alibaba.excel.annotation;
import ......
public @interface ExcelProperty {// 显示在Excel中的字段名String[] value() default {""};// 排序,值越小,优先级越高int order() default Integer.MAX_VALUE;
…………
}

其余还有一些属性,但在本案例中没有用到,就不多做介绍。如此,字段在Excel中展示的名称问题和字段排序的问题通过EasyExcel自带的注解就已经可以解决。

接下来就是将需要导出的实体类加上注解:

public class DepartmentExcel extends ExportCustomCommon.ExcelCommonData {@ExcelProperty(value = "公司名", order = 1)private String companyName;@ExcelProperty(value = "部门名", order = 2)private String name;@ExcelProperty(value = "部门全名", order = 3)private String fullName;@ExcelProperty(value = "部门领导名", order = 4)private String leaderName;@ExcelProperty(value = "业务类型", order = 5)private String business;@ExcelProperty(value = "人数", order = 6)private Long count;@ExcelProperty(value = "地址", order = 7)private String location;@ExcelProperty(value = "状态", order = 8)private String status;
}
public class StaffExcel extends ExportCustomCommon.ExcelListData {private Long id;@ExcelProperty(value = "姓名", order = 1)private String name;@ExcelProperty(value = "年龄", order = 2)private Long age;@ExcelProperty(value = "职位", order = 3)private String position;@ExcelProperty(value = "工资", order = 4)private BigDecimal salary;@ExcelProperty(value = "编码", order = 5)private String code;
}

需要注意:Department中的数据用于公共部分表格,所以需要继承ExcelCommonData;Staff中数据是列表数据的展示,所以继承ExcelListData。

接下来是数据的获取及导出,标题部分很好解决,有几条数据就写几行,然后合并单元格就行了;列表数据部分也好解决,只需要使用EasyExcel自带的动态导出即可;关键是公共部分,因为是横向排列,并且列数不定,所以需要我们自己来做实现,具体代码如下:

package com.kazusa.excel.demo;import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;import java.beans.PropertyDescriptor;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;/*** @Description EasyExcel导出工具类* @Author Kazusa*/
public class EasyExcelUtil {public void export(OutputStream os, Map<String, Object> params, List<String> fields) {//获取标题部分数据List<String> headerTable = (List) params.get("headerTable");//获取公共部分数据ExportCustomCommon.ExcelCommonData commonTable = (ExportCustomCommon.ExcelCommonData) params.get("commonTable");//获取列表部分数据List<ExportCustomCommon.ExcelListData> listTable = (List) params.get("listTable");//获取列表部分数据的类对象Class<? extends ExportCustomCommon.ExcelListData> listDataClass = listTable.get(0).getClass();//构建EasyExcel Writer对象ExcelWriter writer = null;try {writer = EasyExcel.write(os, listDataClass)//指定写入的流,以及需要EasyExcel自带动态生成的类的类对象.excelType(ExcelTypeEnum.XLSX)//指定Excel文件类型,如xlsx、xls等.build();WriteSheet sheet = EasyExcel.writerSheet("sheet1")//指定写入的sheet.needHead(false)//是否需要head,也就是每一个字段对应的字段名,这里为不需要,我们需要EasyExcel去生成字段名的地方只有列表数据部分.build();//使用一个计数器记录当前已经写了几个表格AtomicInteger tableNoCounting = new AtomicInteger(1);//需要知道列数的最大值是多少int maxColumn = fields.size();this.buildHeader(maxColumn, headerTable, sheet, writer, tableNoCounting);this.buildCommon(maxColumn, commonTable, sheet, writer, tableNoCounting);this.buildList(listTable, sheet, writer, tableNoCounting, fields);} finally {assert writer != null;// 关闭流writer.finish();}}/*** 构建标题*/private void buildHeader(int maxColumn, List<String> headerList, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting) {//自定义到处样式WriteCellStyle cellStyle = new WriteCellStyle();//水平居中cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);WriteFont writeFont = new WriteFont();//加粗writeFont.setBold(Boolean.TRUE);//字体大小writeFont.setFontHeightInPoints((short) 15);cellStyle.setWriteFont(writeFont);//遍历标题部分的Listfor (String header : headerList) {WriteTable table = EasyExcel.writerTable(tableNoCounting.get())//指定写入表格的序号,EasyExcel会将多个表格按照序号从小到大、由上到下的排列.needHead(Boolean.FALSE)//也不需要标题.registerWriteHandler(//合并标题的单元格new OnceAbsoluteMergeStrategy(tableNoCounting.get() - 1, tableNoCounting.getAndIncrement() - 1, 0, maxColumn))//将自定义样式应用与该表.registerWriteHandler(new HorizontalCellStyleStrategy(cellStyle, cellStyle)).build();//在这里,由于EasyExcel使用List<List<String>>这样的数据来构建一个表,里面的List<String>表示一行数据。//所以我们这里一次只构建一行数据,一行数据中只有一个单元格的数据,一行数据就作为一个表格写入//故有几个标题就需要构建几次表格List<String> cellList = new ArrayList<>();cellList.add(header);//因为需要合并单元格到列数最大的单元格处,这里如果不添加空字符串,EasyExcel不会构建单元格,在合并单元格的时候就会报错for (int i = 0; i < maxColumn - 1; ++i) {cellList.add("");}List<List<String>> rowList = new ArrayList<>();rowList.add(cellList);//写入表格writer.write(rowList, sheet, table);}}/*** 属性实体类*/private static class ExcelField {//属性名private String fieldName;//Excel中显示名private String showName;//排序private int order;//属性值private Object value;public String getFieldName() {return fieldName;}public void setFieldName(String fieldName) {this.fieldName = fieldName;}public String getShowName() {return showName;}public void setShowName(String showName) {this.showName = showName;}public int getOrder() {return order;}public void setOrder(int order) {this.order = order;}public Object getValue() {return value;}public void setValue(Object value) {this.value = value;}}/*** 构建公共部分*/private void buildCommon(int maxColumn, ExportCustomCommon.ExcelCommonData commonTable, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting) {if (ObjectUtil.isNotEmpty(commonTable)) {//获取公共数据的类对象Class<?> commonDataClass = commonTable.getClass();//通过类对象获取该类中的所有属性List<Field> fields = this.getAllField(commonDataClass);List<ExcelField> fieldList = new ArrayList<>();try {for (Field field : fields) {//如果在Maven打包时报错,Spring项目中可以替换为Spring中的BeanUtils.getPropertyDescriptor()PropertyDescriptor pd = new PropertyDescriptor(field.getName(), commonDataClass);Assert.notNull(pd, Exception::new);//反射获取读方法Method readMethod = pd.getReadMethod();//读到属性值Object fieldValue = readMethod.invoke(commonTable);//获取属性注解ExcelProperty property = field.getAnnotation(ExcelProperty.class);//获取Excel显示名称String excelFieldName = property.value()[0];//获取Excel中排序int excelFieldOrder = property.order();//构建对象ExcelField excelField = new ExcelField();excelField.setFieldName(field.getName());excelField.setShowName(excelFieldName);excelField.setOrder(excelFieldOrder);excelField.setValue(fieldValue);fieldList.add(excelField);}} catch (Exception e) {e.printStackTrace();}//根据Order排序fieldList.sort(Comparator.comparingInt(ExcelField::getOrder));int count = fieldList.size();//计算一行显示属性的个数,除以3是因为一个属性需要属性名--属性值--空字符串三个单元格int lineCount = maxColumn / 3;//计算行数int rows = (count + lineCount - 1) / lineCount;List<Object> cellList = new ArrayList<>();List<List<Object>> rowList = new ArrayList<>();//自定义样式WriteCellStyle cellStyle = new WriteCellStyle();//水平靠左cellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);//遍历所有行,一行作为一个表for (int row = 0; row < rows; ++row) {WriteTable table = EasyExcel.writerTable(tableNoCounting.getAndIncrement()).needHead(Boolean.FALSE).registerWriteHandler(new HorizontalCellStyleStrategy(cellStyle, cellStyle)).build();//构建List<List<String>>类型的数据给EasyExcel导出for (int i = 0; i < lineCount && row * lineCount + i < count; ++i) {ExcelField field = fieldList.get(row * lineCount + i);cellList.add(field.getShowName() + ":");cellList.add(field.getValue());cellList.add("");}rowList.add(cellList);//指定写入的sheet和tablewriter.write(rowList, sheet, table);cellList.clear();rowList.clear();}}}/*** 构建列表部分*/private void buildList(List<ExportCustomCommon.ExcelListData> listTable, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting, List<String> fields) {//自定义样式WriteCellStyle headStyle = new WriteCellStyle();//设置header背景颜色为透明headStyle.setFillForegroundColor(IndexedColors.AUTOMATIC.getIndex());//水平居中headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//上下左右四个边框headStyle.setBorderBottom(BorderStyle.THIN);headStyle.setBorderTop(BorderStyle.THIN);headStyle.setBorderLeft(BorderStyle.THIN);headStyle.setBorderRight(BorderStyle.THIN);WriteFont writeFont = new WriteFont();//字体加粗writeFont.setBold(Boolean.TRUE);//字号writeFont.setFontHeightInPoints((short) 12);headStyle.setWriteFont(writeFont);WriteCellStyle contentStyle = new WriteCellStyle();//内容上下左右四个边框contentStyle.setBorderBottom(BorderStyle.THIN);contentStyle.setBorderTop(BorderStyle.THIN);contentStyle.setBorderLeft(BorderStyle.THIN);contentStyle.setBorderRight(BorderStyle.THIN);//水平居中contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);WriteTable table = EasyExcel.writerTable(tableNoCounting.getAndIncrement()).needHead(Boolean.TRUE)//需要Header.registerWriteHandler(new HorizontalCellStyleStrategy(headStyle, contentStyle))//传入自定义样式.includeColumnFiledNames(fields)//选择需要哪些属性.build();writer.write(listTable, sheet, table);}//获取该类的所有属性,包括父类中不重名的属性private List<Field> getAllField(Class<?> clazz) {List<Field> resultList = new ArrayList<>();for (List<String> fieldNameList = new ArrayList<>(); clazz != null && !clazz.getName().toLowerCase().equals(ExportCustomCommon.class.getName());clazz = clazz.getSuperclass()) {List<Field> subFields = Arrays.asList(clazz.getDeclaredFields());List<Field> list = subFields.stream().filter((f) -> !fieldNameList.contains(f.getName())).collect(Collectors.toList());List<String> nameList = list.stream().map(Field::getName).collect(Collectors.toList());resultList.addAll(list);fieldNameList.addAll(nameList);}return resultList;}
}

接下来构建测试类

package com.kazusa.excel.demo;import java.io.IOException;
import java.math.BigDecimal;import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** @Description Excel导出测试类* @Author kazusa*/
public class TestMain {public static void main(String[] args) throws IOException {EasyExcelUtil excelUtil = new EasyExcelUtil();//构建标题List<String> header = Arrays.asList("标题1", "标题2");//构建公共部分DepartmentExcel department = new DepartmentExcel();department.setCompanyName("TestCompany");department.setName("Name");department.setFullName("FullName");department.setLeaderName("LeaderName");department.setBusiness("Business");department.setCount(1000L);department.setLocation("Location");department.setStatus("Status");//构建列表部分List<StaffExcel> staffs = new ArrayList<>();for (int i = 0; i < 10; i++) {StaffExcel staff = new StaffExcel();staff.setId((long) i);staff.setName("staff-name" + i);staff.setAge((long) (i + 20));staff.setPosition("position" + i);staff.setSalary(new BigDecimal(i));staff.setCode("code" + i);staff.setField1("field1" + i);staff.setField2("field2" + i);staff.setField3("field3" + i);staff.setField4("field4" + i);staffs.add(staff);}//构建属性List<String> fieldList = Arrays.asList("name", "age", "position", "salary", "code", "field1", "field2", "field3", "field4");ExportCustomCommon common = new ExportCustomCommon();common.setCommonTable(department);common.setHeaderTable(header);common.setListTable(staffs);excelUtil.export(Files.newOutputStream(Paths.get("C:\\Users\\DELL\\Desktop\\test-excel.xlsx")), common, fieldList);}
}

导出结果如下:

可以看到有一些问题,就是我们之前说过的自适应列宽的问题,如果不能根据单元格内文本的长度自适应列宽的话,就会出现某些内容显示不全的问题。万幸EasyExcel已经提供了解决方案,我们在EasyExcelUtil的34、221、272行添加了三行代码使导出的Excel可以自适应列宽。

package com.kazusa.excel.demo;import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;import java.beans.PropertyDescriptor;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;/*** @Description EasyExcel导出工具类* @Author Kazusa*/
public class EasyExcelUtil {//在这里新增了一个ThreadLocal类变量,用以存储一个自适应列宽的策略private static final ThreadLocal<LongestMatchColumnWidthStyleStrategy> matchStrategy = new ThreadLocal();public void export(OutputStream os, ExportCustomCommon params, List<String> fields) {//获取标题部分数据List<String> headerTable = params.getHeaderTable();//获取公共部分数据ExportCustomCommon.ExcelCommonData commonTable = params.getCommonTable();//获取列表部分数据List<? extends ExportCustomCommon.ExcelListData> listTable = params.getListTable();//获取列表部分数据的类对象Class<? extends ExportCustomCommon.ExcelListData> listDataClass = listTable.get(0).getClass();//每次构建一个新的Excel文件时,新建一个自适应列宽策略对象,并存入ThreadLocal中LongestMatchColumnWidthStyleStrategy matchWidthStrategy = new LongestMatchColumnWidthStyleStrategy();matchStrategy.set(matchWidthStrategy);//构建EasyExcel Writer对象ExcelWriter writer = null;try {writer = EasyExcel.write(os, listDataClass)//指定写入的流,以及需要EasyExcel自带动态生成的类的类对象.excelType(ExcelTypeEnum.XLSX).build();WriteSheet sheet = EasyExcel.writerSheet("sheet1")//指定写入的sheet.needHead(false)//是否需要head,也就是每一个字段对应的字段名,这里为不需要,我们需要EasyExcel去生成字段名的地方只有列表数据部分.build();//使用一个计数器记录当前已经写了几个表格AtomicInteger tableNoCounting = new AtomicInteger(1);//需要知道列数的最大值是多少int maxColumn = fields.size();this.buildHeader(maxColumn, headerTable, sheet, writer, tableNoCounting);this.buildCommon(maxColumn, commonTable, sheet, writer, tableNoCounting);this.buildList(listTable, sheet, writer, tableNoCounting, fields);} finally {assert writer != null;// 关闭流writer.finish();matchStrategy.remove();}}/*** 构建标题*/private void buildHeader(int maxColumn, List<String> headerList, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting) {//自定义到处样式WriteCellStyle cellStyle = new WriteCellStyle();//水平居中cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);WriteFont writeFont = new WriteFont();//加粗writeFont.setBold(Boolean.TRUE);//字体大小writeFont.setFontHeightInPoints((short) 15);cellStyle.setWriteFont(writeFont);//遍历标题部分的Listfor (String header : headerList) {WriteTable table = EasyExcel.writerTable(tableNoCounting.get())//指定写入表格的序号,EasyExcel会将多个表格按照序号从小到大、由上到下的排列.needHead(Boolean.FALSE)//也不需要标题.registerWriteHandler(//合并标题的单元格new OnceAbsoluteMergeStrategy(tableNoCounting.get() - 1, tableNoCounting.getAndIncrement() - 1, 0, maxColumn - 1))//将自定义样式应用与该表.registerWriteHandler(new HorizontalCellStyleStrategy(cellStyle, cellStyle)).build();//在这里,由于EasyExcel使用List<List<String>>这样的数据来构建一个表,里面的List<String>表示一行数据。//所以我们这里一次只构建一行数据,一行数据中只有一个单元格的数据,一行数据就作为一个表格写入//故有几个标题就需要构建几次表格List<String> cellList = new ArrayList<>();cellList.add(header);//因为需要合并单元格到列数最大的单元格处,这里如果不添加空字符串,EasyExcel不会构建单元格,在合并单元格的时候就会报错for (int i = 0; i < maxColumn - 1; ++i) {cellList.add("");}List<List<String>> rowList = new ArrayList<>();rowList.add(cellList);//写入表格writer.write(rowList, sheet, table);}}/*** 属性实体类*/private static class ExcelField {//属性名private String fieldName;//Excel中显示名private String showName;//排序private int order;//属性值private Object value;public String getFieldName() {return fieldName;}public void setFieldName(String fieldName) {this.fieldName = fieldName;}public String getShowName() {return showName;}public void setShowName(String showName) {this.showName = showName;}public int getOrder() {return order;}public void setOrder(int order) {this.order = order;}public Object getValue() {return value;}public void setValue(Object value) {this.value = value;}}/*** 构建公共部分*/private void buildCommon(int maxColumn, ExportCustomCommon.ExcelCommonData commonTable, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting) {if (ObjectUtil.isNotEmpty(commonTable)) {//获取公共数据的类对象Class<?> commonDataClass = commonTable.getClass();//通过类对象获取该类中的所有属性List<Field> fields = this.getAllField(commonDataClass);List<ExcelField> fieldList = new ArrayList<>();try {for (Field field : fields) {//如果在Maven打包时报错,Spring项目中可以替换为Spring中的BeanUtils.getPropertyDescriptor()PropertyDescriptor pd = new PropertyDescriptor(field.getName(), commonDataClass);Assert.notNull(pd, Exception::new);//反射获取读方法Method readMethod = pd.getReadMethod();//读到属性值Object fieldValue = readMethod.invoke(commonTable);//获取属性注解ExcelProperty property = field.getAnnotation(ExcelProperty.class);//获取Excel显示名称String excelFieldName = property.value()[0];//获取Excel中排序int excelFieldOrder = property.order();//构建对象ExcelField excelField = new ExcelField();excelField.setFieldName(field.getName());excelField.setShowName(excelFieldName);excelField.setOrder(excelFieldOrder);excelField.setValue(fieldValue);fieldList.add(excelField);}} catch (Exception e) {e.printStackTrace();}//根据Order排序fieldList.sort(Comparator.comparingInt(ExcelField::getOrder));int count = fieldList.size();//计算一行显示属性的个数,除以3是因为一个属性需要属性名--属性值--空字符串三个单元格int lineCount = maxColumn / 3;//计算行数int rows = (count + lineCount - 1) / lineCount;List<Object> cellList = new ArrayList<>();List<List<Object>> rowList = new ArrayList<>();//自定义样式WriteCellStyle cellStyle = new WriteCellStyle();//水平靠左cellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);//遍历所有行,一行作为一个表for (int row = 0; row < rows; ++row) {WriteTable table = EasyExcel.writerTable(tableNoCounting.getAndIncrement()).needHead(Boolean.FALSE).registerWriteHandler(new HorizontalCellStyleStrategy(cellStyle, cellStyle))//添加自适应列宽策略.registerWriteHandler(matchStrategy.get()).build();//构建List<List<String>>类型的数据给EasyExcel导出for (int i = 0; i < lineCount && row * lineCount + i < count; ++i) {ExcelField field = fieldList.get(row * lineCount + i);cellList.add(field.getShowName() + ":");cellList.add(field.getValue());cellList.add("");}rowList.add(cellList);//指定写入的sheet和tablewriter.write(rowList, sheet, table);cellList.clear();rowList.clear();}}}/*** 构建列表部分*/private void buildList(List<? extends ExportCustomCommon.ExcelListData> listTable, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting, List<String> fields) {//自定义样式WriteCellStyle headStyle = new WriteCellStyle();//设置header背景颜色为透明headStyle.setFillForegroundColor(IndexedColors.AUTOMATIC.getIndex());//水平居中headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//上下左右四个边框headStyle.setBorderBottom(BorderStyle.THIN);headStyle.setBorderTop(BorderStyle.THIN);headStyle.setBorderLeft(BorderStyle.THIN);headStyle.setBorderRight(BorderStyle.THIN);WriteFont writeFont = new WriteFont();//字体加粗writeFont.setBold(Boolean.TRUE);//字号writeFont.setFontHeightInPoints((short) 12);headStyle.setWriteFont(writeFont);WriteCellStyle contentStyle = new WriteCellStyle();//内容上下左右四个边框contentStyle.setBorderBottom(BorderStyle.THIN);contentStyle.setBorderTop(BorderStyle.THIN);contentStyle.setBorderLeft(BorderStyle.THIN);contentStyle.setBorderRight(BorderStyle.THIN);//水平居中contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);WriteTable table = EasyExcel.writerTable(tableNoCounting.getAndIncrement()).needHead(Boolean.TRUE)//需要Header//添加自适应列宽策略.registerWriteHandler(matchStrategy.get()).registerWriteHandler(new HorizontalCellStyleStrategy(headStyle, contentStyle))//传入自定义样式.includeColumnFiledNames(fields)//选择需要哪些属性.build();writer.write(listTable, sheet, table);}//获取该类的所有属性,包括父类中不重名的属性private List<Field> getAllField(Class<?> clazz) {List<Field> resultList = new ArrayList<>();for (List<String> fieldNameList = new ArrayList<>();clazz != null && !clazz.getName().toLowerCase().equals(ExportCustomCommon.class.getName());clazz = clazz.getSuperclass()) {List<Field> subFields = Arrays.asList(clazz.getDeclaredFields());List<Field> list = subFields.stream().filter((f) -> !fieldNameList.contains(f.getName())).collect(Collectors.toList());List<String> nameList = list.stream().map(Field::getName).collect(Collectors.toList());resultList.addAll(list);fieldNameList.addAll(nameList);}return resultList;}
}

看完自适应列宽部分代码的小伙伴应该会有一个疑问,为什么需要使用一个线程本地变量来存储这个策略对象?

就像我们之前说过的该代码导出Excel表格使用了多个Table组合成一个Excel的形式,那么每个table中的每一个单元格都会影响跟它同列但是属于其他Table的列宽,查看LongestMatchColumnWidthStyleStrategy的源码可以看到,其内部有一个cache Map,作用就是存储该列的最大长度,从而保证该列上的每一个单元格中的内容都可以显示完全,所以在这里我们需要保证一个Excel文件导出过程中使用的LongestMatchColumnWidthStyleStrategy对象为同一个对象。当然不一定使用线程本地变量来存储,也可以使用其他的方法。

package com.alibaba.excel.write.style.column;import ............public class LongestMatchColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {private static final int MAX_COLUMN_WIDTH = 255;//这里就是存储某一列最大列宽的Map,如果构建同一个Excel时使用了不同的Map,最终的结果会出现错误private Map<Integer, Map<Integer, Integer>> cache = new HashMap<Integer, Map<Integer, Integer>>(8);@Overrideprotected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head,Integer relativeRowIndex, Boolean isHead) {boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);if (!needSetWidth) {return;}Map<Integer, Integer> maxColumnWidthMap = cache.get(writeSheetHolder.getSheetNo());if (maxColumnWidthMap == null) {maxColumnWidthMap = new HashMap<Integer, Integer>(16);cache.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);}Integer columnWidth = dataLength(cellDataList, cell, isHead);if (columnWidth < 0) {return;}if (columnWidth > MAX_COLUMN_WIDTH) {columnWidth = MAX_COLUMN_WIDTH;}Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());if (maxColumnWidth == null || columnWidth > maxColumnWidth) {maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);}}private Integer dataLength(List<CellData> cellDataList, Cell cell, Boolean isHead) {if (isHead) {return cell.getStringCellValue().getBytes().length;}CellData cellData = cellDataList.get(0);CellDataTypeEnum type = cellData.getType();if (type == null) {return -1;}switch (type) {case STRING:return cellData.getStringValue().getBytes().length;case BOOLEAN:return cellData.getBooleanValue().toString().getBytes().length;case NUMBER:return cellData.getNumberValue().toString().getBytes().length;default:return -1;}}
}

最后导出的结果如图:

最后,本代码只实现了列表部分可以选择字段导出,在公共部分没有办法选择指定字段导出,但是由于整个Excel都是动态生成的,在构建公共部分表格时只需要把公共部分的字段自己做一个筛选即可。

使用EasyExcel实现无模板、全动态excel导出相关推荐

  1. Excel的读写(EasyExcel的无模板读写)

    文章目录 Excel读写 EasyExcel的读写 EasyExcel的同步读 EasyExcel的异步读 EasyExcel的写 ExcelUtil的读 Excel读写 对于读Excel,因为场景是 ...

  2. java 动态导出excel表单 无模板本地生成

    java 动态导出excel表单 无模板本地生成 这里使用的是alibaba的公共类excelWriter,注意在pom文件中要引入easyExcel的依赖 public void exportExc ...

  3. java 动态导出excel表单 无模板文件下载

    java 动态导出excel表单 无模板文件下载 public ResponseEntity<byte[]> exportStanding(@PathVariable Long signu ...

  4. easyexcel生成动态模板(模板支持下拉框),动态字段导出excel表格,常规字段导出excel表格

    备注:动态字段导出主要是用了反射的原理,跟excel需要导出的字段一一映射.话不多说,直接上代码: 1.生成的动态模板如图: 如上图,如果下拉框里不是选择的值,会给用户提示,下拉框用来限制用户导入只能 ...

  5. 【工具集】【后端】【EasyExcel】动态Excel模版生成Excel

    1.需求描述 将数据动态保存到Excel中,其中数据包含文本文字.图片base64的格式 Excel中支持sheet之间的跳转.字体颜色的控制 sheet数量不固定,需要动态创建sheet模板 2.实 ...

  6. 史上最全的Excel导入导出(easyexcel版)

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/qq_32258777/article/details/89031479 喝水不忘挖井人,感谢阿里 ...

  7. 321套Excel可视化图表、Excel模板(在Excel里面实现数据可视化),史上最全Excel可视化图表,模板

    321套Excel可视化图表.Excel模板(在Excel里面实现数据可视化),史上最全Excel可视化图表,模板 下载地址:321套Excel可视化图表.Excel模板 运行效果 下载地址:321套 ...

  8. 80款中国风 全动态PPT模板

    还在等神马? 康康人家的动不动就付费下载,这里精品80套免费分享它不香吗??还不速速点赞!(卑微) 免费获取链接:https://pan.baidu.com/s/1n8cXXwLoSW07-ECj0g ...

  9. poi读取excel多层表头模板写入数据并导出

    poi读取excel多层表头模板写入数据并导出 这两天刚好写excel,写了一份自定义表头的,写了一份模板的,这里展示一份读取excel模板写入数据并导出的 //title excel的名称 head ...

最新文章

  1. MYSQL二级表的管理_MySQL库和表的管理
  2. mysql命令行大全
  3. mysql运用索引写出高效sql_从SQL Server到MySql(5) : 高性能的MySql 索引策略
  4. 全球时报英语新闻爬虫
  5. ISSCC 2018 13.2论文笔记
  6. 初中计算机考试素材,初中信息技术素材.ppt
  7. 【NodeJS】20 koa 企业级Cms内容管理系统-XMind功能分析、ERStudio设计数据库ER图
  8. 数学建模学习笔记:层次分析法
  9. ideaIU安装教程
  10. Java知识点_类锁和对象锁的区别?
  11. 02_安装nginx-银河麒麟V10(Kylin Linux Advanced Server V10 (Tercel))操作系统
  12. Flask 框架 网页跳转详解。
  13. iPad/iPhone 邮件 设置浙大邮箱
  14. ppt矩形里面的图片怎么放大缩小_PPT中图片点击放大效果的实现
  15. 无线数据包的破解——跑包
  16. xcode11 The app delegate must implement the window property if it wants to use a main storyboard fil
  17. 软考中项第三章 信息系统集成专业知识
  18. 计算机图形学:机器人的画法与填充
  19. 吕海楠201552216
  20. Zynq-Linux移植学习-通过IIC访问RXS2448交换芯片

热门文章

  1. 怎样规划自己的人生?
  2. java使用ecdh密钥协商
  3. “打农药”都不省心:勒索病毒冒充王者荣耀外挂
  4. linux下FTP服务器启动与关闭命令
  5. 用Tkinter打造自己的Python IDE开发工具(6)Python多文件共享变量与智能插件设计
  6. endnote导入参考文献期刊名不能显示解决办法
  7. IIS与sxd的问题解决方法
  8. 如何从零开始学游戏建模?附指南
  9. 国货当潮,扒一扒那些新锐美妆品牌俘获海外消费者芳心的套路
  10. 两张电梯卡到期时间是哪天?哪种加密方法?