最近由于业务需要,在做导出Excel的业务需求,要求导出的Excel含有多级表头,和含有表尾部数据,如下图的表格:
运用java的POI来实现上述需求:
//表格头数组
String[][] names = {{"合作费审批单", "", "", "", "", "合作费", " ", "基金", " "},
{"回款日期", "发货日期", "省份", "代理商名称", "数量", "合作费金额", "基金", "基金金额", "结算日期"}};
/*表格尾部 动态 数据*/
//个人汇总数据(根据自己的具体业务需求拿到一个list集合)
List<Business> summaryData = businessService.summaryDate(business);
//总计数据 (根据自己的具体业务需求拿到一个对象)
Business total = businessService.totalDate(business);
//固定尾部数据
String[][] tableEnd = {{"", "", "", "总计",total.getSumAmount().toString(), "","", "", ""},
{"编号:"},
{"制单人: 运营总监审批:"}};
//所有表尾部数据存放该二维数组中
String[][] tableEndDate = new String[summaryData.size() + 3][];
int j = 0;
for (int i = 0; i < summaryData.size() + 3; i++) {
if (i < summaryData.size()) {
tableEndDate[i] = new String[]{"", "", "",summaryData.get(i).getAgentName() + " 汇总", summaryData.get(i).getSumAmount().toString(), "", "", "", ""};
} else {
tableEndDate[i] = tableEnd[j];
j = j + 1;
}
}
/*导出Excel*/
XSSFWorkbook workbook = ExcelUtil.exportMultilevelHeader("业务合作协议", names, list, Record.class, tableEndDate);
ExcelUtil工具类中封装的处理多级表头和表尾部的方法为:
/**
* 导出含多级表头和表尾的excel
* @param sheetName
* @param head 多级表头
* @param dataList 表数据
* @param type
* @param tableEndData 表尾
*/
public static XSSFWorkbook exportMultilevelHeader(String sheetName, String[][] head, List<?> dataList, Class type, String[][] tableEndData) {
/*变量*/
String[] properties;
Object[] rowValue;
List<Object[]> values;
Field[] fields;
XSSFCell cell;
String vo;
/*导出Excel*/
// 第一步,创建一个workBook,对应一个Excel文件
XSSFWorkbook wb = new XSSFWorkbook();
// 第二步,在workBook中添加一个sheet,对应Excel文件中的sheet
XSSFSheet sheet = wb.createSheet(sheetName);
// 第三步,在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制short
XSSFRow row;
// 第四步,创建单元格,并设置值表头 设置表头居中
//生成一个Style
XSSFCellStyle style = wb.createCellStyle();
style.setWrapText(true);
style.setAlignment(XSSFCellStyle.ALIGN_CENTER);
//循环创建表格头,设置style,名称
/*for (int i = 0; i < head.length; i++) {
cell = row.createCell(i);
cell.setCellStyle(style);
cell.setCellValue(head[i]);
}*/
int mergerNum = 0; //合并数
//给单元格设置值
for (int i = 0; i < head.length; i++) {
row = sheet.createRow(i);
row.setHeight((short) 700);
for (int j = 0; j < head[i].length; j++) {
cell = row.createCell(j);
cell.setCellStyle(style);
cell.setCellValue(head[i][j]);
}
}
Map<Integer, List<Integer>> map = new HashMap<Integer, List<Integer>>(); // 合并行时要跳过的行列
//合并列
for (int i = 0; i < head[head.length - 1].length; i++) {
if ("".equals(head[head.length - 1][i])) {
for (int j = head.length - 2; j >= 0; j--) {
if (!"".equals(head[j][i])) {
sheet.addMergedRegion(new CellRangeAddress(j, head.length - 1, i, i)); // 合并单元格
break;
} else {
if (map.containsKey(j)) {
List<Integer> list = map.get(j);
list.add(i);
map.put(j, list);
} else {
List<Integer> list = new ArrayList<Integer>();
list.add(i);
map.put(j, list);
}
}
}
}
}
//合并行
for (int i = 0; i < head.length - 1; i++) {
for (int j = 0; j < head[i].length; j++) {
List<Integer> list = map.get(i);
if (list == null || (list != null && !list.contains(j))) {
if ("".equals(head[i][j])) {
mergerNum++;
if (mergerNum != 0 && j == (head[i].length - 1)) {
sheet.addMergedRegion(new CellRangeAddress(i, i, j - mergerNum, j)); // 合并单元格
mergerNum = 0;
}
} else {
if (mergerNum != 0) {
sheet.addMergedRegion(new CellRangeAddress(i, i, j - mergerNum - 1, j - 1)); // 合并单元格
mergerNum = 0;
}
}
}
}
}
//解析导出类型
Class<Record> recordClass = Record.class;
if (null == type) {
//导出失败
return null;
} else if (type.equals(recordClass)) { //导出List<Record>
//获取Record中包含的properties,用于生成表格头及创建Cell
properties = getRecordProperties(dataList, null);
vo = "record";
} else { //导出List<Bean>
//获取Bean的Field
fields = type.getDeclaredFields();
properties = getRecordProperties(null, fields);
vo = "bean";
}
if (null == head) {
int i = 0;
if (head.length > 0) {
i = head.length - 1;
}
head[i] = properties;
}
// 第五步,写入实体数据
/*表头行数*/
int m = 1;
if (head.length > 0) {
m = head.length;
}
values = getRowValue(dataList, properties, vo);
for (int i = 0; i < dataList.size(); i++) {
row = sheet.createRow(i + m); //创建行
rowValue = values.get(i);
// 第四步,创建单元格,并设置值
for (int j = 0; j < properties.length; j++) {
cell = row.createCell(j);
cell.setCellStyle(style);
setCellValue(cell, rowValue[j]);
}
}
//第六步,处理表格尾部的数据
if (tableEndData != null && tableEndData.length > 0) {
for (int i = 0; i < tableEndData.length; i++) {
row = sheet.createRow(dataList.size() + m + i);
for (int j = 0; j < tableEndData[i].length; j++) {
cell = row.createCell(j);
/*cell.setCellStyle(style);*/
setCellValue(cell, tableEndData[i][j]);
}
}
}
return wb;
}
补充工具类方法:
/**
* 获取Record包含的所有properties
*
* @param list 列表
* @param fields 属性
* @return 包含properties
*/
private static String[] getRecordProperties(List<?> list, Field[] fields) {
if (null != list && null == fields) {
Record record = (Record) list.get(0);
Set<String> keySet = record.keySet();
List<String> keysList = new ArrayList<>(keySet);
return keysList.toArray(new String[keysList.size()]);
} else if (null != fields && null == list) {
String[] properties = new String[fields.length];
for (int i = 0; i < fields.length; i++) {
properties[i] = fields[i].getName();
}
return properties;
}
return new String[0];
}
/**
* 转换列表数据
*
* @param list 数据列表
* @param properties 属性列表
* @param vo 类型
* @return 转换后的数据
*/
private static List<Object[]> getRowValue(List<?> list, String[] properties, String vo) {
List<Object[]> resultList = new ArrayList<>();
Record record;
if (Strings.isNullOrEmpty(vo)) {
return resultList;
} else if ("record".equals(vo)) {
for (Object object : list) {
record = (Record) object;
Object[] values = new Object[properties.length]; //定义在外部数组值会被最后写入的覆盖
for (int i = 0; i < properties.length; i++) {
values[i] = record.get(properties[i]);
}
resultList.add(values);
}
return resultList;
} else if ("bean".equals(vo)) {
for (Object object : list) {
Class cf = object.getClass();
Object[] values = new Object[properties.length]; //定义在外部数组值会被最后写入的覆盖
for (int i = 0; i < properties.length; i++) {
char[] name = properties[i].toCharArray();
name[0] -= 32;
try {
Method method = cf.getMethod("get" + String.valueOf(name));
values[i] = method.invoke(object);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
resultList.add(values);
}
return resultList;
}
return resultList;
}
/**
* 设置单元格值
*
* @param cell 单元格
* @param value 值
*/
private static void setCellValue(XSSFCell cell, Object value) {
if (value instanceof String) {
cell.setCellValue((String) value);
cell.setCellType(XSSFCell.CELL_TYPE_STRING);
} else if (value instanceof Date) {
cell.setCellValue((Date) value);
cell.setCellType(XSSFCell.CELL_TYPE_STRING);
} else if (value instanceof Boolean) {
cell.setCellValue((Boolean) value);
cell.setCellType(XSSFCell.CELL_TYPE_BOOLEAN);
} else if (value instanceof Double) {
cell.setCellValue((Double) value);
cell.setCellType(XSSFCell.CELL_TYPE_NUMERIC);
} else if (value instanceof Calendar) {
cell.setCellValue((Calendar) value);
cell.setCellType(XSSFCell.CELL_TYPE_STRING);
} else if (value instanceof RichTextString) {
cell.setCellValue((RichTextString) value);
cell.setCellType(XSSFCell.CELL_TYPE_STRING);
} else {
cell.setCellValue(String.valueOf(value));
cell.setCellType(XSSFCell.CELL_TYPE_STRING);
}
}
补充Record类:
import java.math.BigDecimal;
import java.util.Date;
import java.util.LinkedHashMap;
public class Record extends LinkedHashMap<String,Object> {
public void set(String field,Object value){
put(field,value);
}
public String getString(String field){
return (String)get(field);
}
public Integer getInteger(String field){
return (Integer)get(field);
}
public Long getLong(String field){
return (Long)get(field);
}
public BigDecimal getBigDecimal(String field){
return (BigDecimal)get(field);
}
public Date getDate(String field){
return (Date)get(field);
}
public Boolean getBoolean(String field){
return (Boolean) get(field);
}
}
如果没有表尾数据,可在传参数时,直接给它一个null就好了!
好的,处理完成啦!希望能给需要的朋友提供帮助!
另外,如果大家有更好更简洁的办法,欢迎在下方留言!!