前言:关于该方法呢主要是来源于该博主提供的资料,我主要是稍微修改下并记录。额外添加的内容就是表格标题,理论上支持导出一级、二级、三级等多级表头Excel文档,测试一级、二级是OK的,先上效果图如下:
这是导出一级表头的Excel文档效果图:
这是导出二级表头的Excel文档效果图:
首先呢说下一级表头是实现,从简单的开始:
先上工具类封装的统一方法:
package com.ylz.packcommon.common.util;
import com.ylz.bizDo.statisticalReports.vo.Record;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/***
* @ClassName: ExcelUtils
* @Description:POI实现导出含多级表头和含有表尾部信息的excel
* @Auther: lyb
* @Date: 2019/12/17 11:21
* @version : V1.0
*/
public class ExcelUtils {
/**
* @Author lyb
* @Description //TODO 多级表头Excel文件导出
* @Date 11:24 2019/12/17
* @Param [sheetName, head, dataList, type, tableEndData,out,titles] sheet名,多级表头,导出数据,导出类型,表尾,输出文件对象,首行标题
* @return org.apache.poi.xssf.usermodel.XSSFWorkbook
**/
public static XSSFWorkbook exportMultilevelHeader(String sheetName, String[][] head, List<?> dataList, Class type, String[][] tableEndData, OutputStream out,String titles) {
/*变量*/
String[] properties;
Object[] rowValue;
List<Object[]> values;
Field[] fields;
XSSFCell cell;
String vo;
/*导出Excel*/
// 第一步,创建一个workBook,对应一个Excel文件
XSSFWorkbook wb = new XSSFWorkbook();
// 表头 标题样式
XSSFFont titleFont = wb.createFont();
titleFont.setFontName("微软雅黑");//字体
titleFont.setFontHeightInPoints((short) 15);// 字体大小
XSSFCellStyle titleStyle = wb.createCellStyle();
titleStyle.setFont(titleFont);
titleStyle.setAlignment(XSSFCellStyle.ALIGN_CENTER);// 左右居中
titleStyle.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);// 上下居中
titleStyle.setLocked(true);
// 第二步,在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.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);//垂直居中
int mergerNum = 0; //合并数
//添加表格标题
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, type.getDeclaredFields().length- 1));
row = sheet.createRow(0);//创建一行表格
row.setHeight((short) 0x349);//设置高度
cell = row.createCell(0);//创建单元格
cell.setCellStyle(titleStyle);//设置样式
cell.setCellValue(titles);//设置标题
//给单元格设置值
for (int i = 0; i < head.length; i++) {
row = sheet.createRow(i+1);
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+1, head.length, 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+1, i+1, j - mergerNum - 1, j - 1)); // 合并单元格
mergerNum = 0;
}
}
}
}
}
//解析导出类型
Class<Record> recordClass = Record.class;
if (null == type) {
//导出失败
return null;
} else if (type.equals(recordClass)) {
//导出List
//获取Record中包含的properties,用于生成表格头及创建Cell
properties = getRecordProperties(dataList, null);
vo = "record";
} else {
//导出List
//获取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+1); //创建行
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);
sheet.addMergedRegion(new CellRangeAddress(dataList.size() + m + i, dataList.size() + m + i, 0, type.getDeclaredFields().length- 1));
for (int j = 0; j < tableEndData[i].length; j++) {
cell = row.createCell(j);
cell.setCellStyle(style);
setCellValue(cell, tableEndData[i][j]);
}
}
}
try {
wb.write(out);
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return wb;
}
/**
* @Author lyb
* @Description //TODO 获取Record包含的所有properties
* @Date 11:30 2019/12/17
* @Param [list, fields] 列名,属性
* @return java.lang.String[] 包含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];
}
/**
* @Author lyb
* @Description //TODO 转换列表数据
* @Date 11:33 2019/12/17
* @Param [list, properties, vo] 数据列表,属性列表,类型
* @return java.util.List 转换后的数据
**/
private static List<Object[]> getRowValue(List<?> list, String[] properties, String vo) {
List<Object[]> resultList = new ArrayList<>();
Record record;
if (StringUtils.isBlank(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;
}
/**
* @Author lyb
* @Description //TODO 设置单元格值
* @Date 11:34 2019/12/17
* @Param [cell, value] 单元格,值
* @return void
**/
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);
}
}
/**
* @Author lyb
* @Description //TODO 测试方法
* @Date 13:26 2019/12/19
* @Param [args]
* @return void
**/
public static void main(String[] args) throws Exception{
// //标题
// String titles="小脆皮";
//
// //表头名
// String[][] headNames = {{"鲁班","小乔","安琪拉","甑姬","王昭君"}};
//
// //表尾名
// String[][] tableEnd = {{"不准看: "}};
//
// List list = new ArrayList<>();
// for (int i=0;i<5;i++) {
// Testvo vo=new Testvo();
// vo.setNo("1");
// vo.setName("鲁班大师");
// vo.setSex("男");
// vo.setAge(26);
// vo.setMoney("13888");
// list.add(vo);
// }
//
// OutputStream out = new FileOutputStream("C:\\Users\\lyb\\Desktop\\测试汇总表.xls");
//
// //导出
// exportMultilevelHeader("测试汇总",headNames,list,Testvo.class,tableEnd,out,titles);
}
}
这里有一个类需要说明下,就是Record类进行重写了,作用其实就是中间接收我们需导出数据的实体类属性,该类贴码如下:
package com.ylz.bizDo.statisticalReports.vo;
/***
* @ClassName: Record
* @Description:该类用于POI多级表头Excel文件导出,用于接收导出实体属性
* @Auther: lyb
* @Date: 2019/12/17 11:30
* @version : V1.0
*/
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);
}
}
然后呢就是一级表头文档导出的测试方法了:
public static void main(String[] args) throws Exception{
//标题
String titles="小脆皮";
//表头名
String[][] headNames = {{"鲁班","小乔","安琪拉","甑姬","王昭君"}};
//表尾名
String[][] tableEnd = {{"不准看: "}};
List<Testvo> list = new ArrayList<>();
for (int i=0;i<5;i++) {
Testvo vo=new Testvo();
vo.setNo("1");
vo.setName("鲁班大师");
vo.setSex("男");
vo.setAge(26);
vo.setMoney("13888");
list.add(vo);
}
OutputStream out = new FileOutputStream("C:\\Users\\lyb\\Desktop\\测试汇总表.xls");
//导出
exportMultilevelHeader("测试汇总",headNames,list,Testvo.class,tableEnd,out,titles);
}
至于TestVo类就不需要我贴了吧,只是一个测试类只有get、set方法,属性就循环的那些。
至于二级表头的测试方法如下,主要内容都在工具类里面。基本上都有写注释相信应该可以看明白。
/**
* @Author lyb
* @Description //TODO 家庭签约登记统计Excel导出
* @Date 9:43 2019/12/17
* @Param []
* @return java.lang.String
**/
public String findRegistrationStatisticalToExcel() {
try {
//查询条件
RegistrationListQvo qvo = (RegistrationListQvo) getJsonLay(RegistrationListQvo.class);
if(qvo==null){
qvo=new RegistrationListQvo();
}
//登录人信息
CdUser user = this.getSessionUser();
//导出数据
List<RegistrationStatisticalExportVo> listData = sysDao.getStattisticalReportsDao().registrationListExport(qvo);
//Excel导出标题
String titles = "登记表";
//表头名
String[][] headNames = {{"姓名","性别","身份证号","联系电话","签约编码","签约家庭类别", "", "", "", "","","", "重点人群签约服务项目", "","","","","","", "其他人群", "","","","","签约服务包类型","备注"},
{"","","","","","计生失独伤残家庭", "计生独生子女", "计生双女", "五保户", "低保户","建档立卡贫困人口","其他", "老年人","高血压患者","2型糖尿病患者","严重精神障碍患者","结核病患者","孕产妇","0-6岁儿童", "残疾人", "脑血管病患者", "冠心病患者","癌症患者","其他","",""}};
//表尾名
String[][] tableEnd = {{"填报人: 分管院长: 填报时间: 年 月 日"}};
getResponse().reset();
getResponse().setContentType("application/vnd..ms-excel");
getResponse().setHeader("content-Disposition","attachment;filename="+ URLEncoder.encode("汇总表.xls","utf-8"));
ExcelUtils.exportMultilevelHeader("汇总表",headNames,listData, RegistrationStatisticalExportVo.class,tableEnd,getResponse().getOutputStream(),titles);
}catch (Exception e) {
e.printStackTrace();
new ActionException(getClass(), getAct(), getJsons(), e);
}
return null;
}
代码什么的都已经贴完了,也没别的东西了。
本次记录到此结束,欢迎订阅、关注、收藏、评论、点赞哦~~( ̄▽ ̄~)~
哇咔咔(∪。∪)。。。zzz