好久没时间写帖子了,由于工作需要,写一个基于JAVA实现的Excel多表头动态导出功能,首先可能这个叫法比较啰嗦,下面我们先看看什么是Excel多表头动态导出(效果图):
它包含两部分:1、是表头,就像大家看到的样子它的表头是多行合并的(多表头);2.内容部分,就是下方的数据数据内容。
两个内容都是有后台人员分别由两个接口返回的(也可以一个,但一个会显得格式冗余),因此他的表头数据是不固定的,可能有甲方要求,甲方、乙方、丙方要求等,是随机看后台数据的,所以我们就不能事先画好Excel模板进行导出,好以上就是我对java实现Excel多表头动态导出的说明下面开始吧。
一、在开发前我们要撸一下思路,该怎么去做?会遇到什么坑?首先不用多说肯定识这个多表头的问题了,后端返回的Json数据一般都:
不难发现一个list<Map>发现虽然都有一、二级目录的区分,但是分开的不符合我们的需要,因此我们需要转换分组为想要的数据类型
Map<String, List<Map>>格式的数据:既把同一个一级目录下的二级目录放到一个List<Map>中方便后面的Excel渲染
二、就是在Excel做数据渲染时的数据角标的计算问题,以及如何让经过我们分组后的表头数据与内容匹配上,因为数据是动态生成的,所以在生成数据是一定要注意,后几列的数据再前几列数据生成的基础上向后推的。
三、主要的坑应该就是上面两个问题了,那我们开发撸代码吧:
1、我使用的实现语言是JAVA中的servlet,关于servlet不太熟悉的小伙伴可以百度自行科普一下,简单说就是(1):
集成HttpServlet并实现其几个方法;(2)配置web.xlm文件等。
代码:
/**
* 自定义表头导出方法
* @throws: l
* @param :resultParam 导出excel名称、模板名称、导出地址
*/
public void exportExcelByDiyTitle(HttpServletRequest request, HttpServletResponse response)throws IOException{
System.out.println("进入自定义导出表头方法");
response.reset();// 清空输出流
String templateName ="按异常原因统计";
//获取导出参数进行查询orgId=06&date=2020-07
String orgId = request.getParameter("orgId");//组织ID
String date = request.getParameter("date");//日期
String type = request.getParameter("type");//日期
List<Map<String,Object>> listtitle=null;
List<Map<String,Object>> list=null;
WorkOrderInforShowService workOrderInforShowService = new WorkOrderInforShowServiceImpl();
//这是调用后台Execl表头数据接口
listtitle = workOrderInforShowService.getYCLXTypeOfYY(orgId,date);
//获取Excell内容数据接口
list = workOrderInforShowService.getAppWorkOrderStatisticsOfYY(orgId,date);
//设置谷歌和Ie浏览器导出表单乱码问题
String userAgent = request.getHeader("User-Agent");
if (userAgent.contains("MSIE")||userAgent.contains("Trident")){
templateName = java.net.URLEncoder.encode(templateName, "UTF-8");
}else{
templateName = new String(templateName.getBytes("UTF-8"),"ISO-8859-1");
}
response.setHeader("Content-disposition", "attachment; filename="
+ templateName+ ".xls");// 设定输出文件头
response.setContentType("application/msexcel");
// 创建一个工作薄
HSSFWorkbook workbook = new HSSFWorkbook();
// 生成一个表格
HSSFSheet sheet = workbook.createSheet("数据明细");
HSSFCellStyle cellStyle = workbook.createCellStyle();
//设置表头字体
HSSFFont font2 = workbook.createFont();
font2.setFontName("仿宋_GB2312");
font2.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);//粗体显示
font2.setFontHeightInPoints((short) 10);
//TODO 水平垂直居中 列宽
cellStyle.setAlignment(HorizontalAlignment.CENTER_SELECTION);
cellStyle.setFont(font2);
// 产生表格标题行
HSSFRow firrow = sheet.createRow(0);
// 产生表格标题行
HSSFRow row = sheet.createRow(1);
CellRangeAddress cellRange = null;
//一级表头
String manKey=null;
//判断表头是否有数据
if(listtitle.size()==0){
return;
}
//这就是我们对无序的表头数据进行分组得等满足要求的Map<String, List<Map>> 格式数据
Map<String, List<Map>> temp =new HashMap<String, List<Map>>();
for(Map item:listtitle){
String key = item.get("VAL_ONE").toString();
if(temp.get(key) == null){
List<Map> list1 = new ArrayList<>();
temp.put(key,list1);
}else{
temp.put(key,temp.get(key));
}
temp.get(key).add(item);
}
//设置固定单位列
HSSFCell organizeCell = firrow.createCell(0);
organizeCell.setCellStyle(cellStyle);
//由于后台数据并没有返回项目名称这一列接口数据,因此我们只能自己生成
organizeCell.setCellValue("项目名称");
sheet.addMergedRegion(new CellRangeAddress(0, 1, 0, 0));
//遍历生成一、二级表头
int count = 1;
for(Map.Entry<String, List<Map>> entry : temp.entrySet()){
List<Map> mapValue = entry.getValue();
manKey=entry.getKey();
if(mapValue.size()>0){
//设置一级表头
HSSFCell fircell = firrow.createCell(count);
fircell.setCellStyle(cellStyle);
fircell.setCellValue(manKey);
//对合并列进行判断,当二级目录为一时既不需要合并
if(mapValue.size()>1){
//设置单元格合并操作,可以查询Excel导出api
cellRange = new CellRangeAddress(0, 0, count, count + mapValue.size()-1);
try {
sheet.addMergedRegion(cellRange);
addBorderLine(sheet,cellRange,workbook);
}catch (Exception e){
e.printStackTrace();
}
}
for (short i = 0; i < mapValue.size(); i++) {
try {
//设置二级表头
HSSFCell cell = row.createCell(count+i);
HSSFRichTextString text = new HSSFRichTextString(mapValue.get(i).get("VAL_TWO").toString());
cell.setCellValue(text);
//设置二级表头宽度
sheet.setColumnWidth(count+i, text.toString().getBytes("UTF-8").length*256);
cell.setCellStyle(cellStyle);
CellRangeAddress cellRange1 = new CellRangeAddress(1, 1, count-1, count+i);
//sheet.addMergedRegion(cellRange1);
//设置水平位置
addBorderLine(sheet, cellRange1, workbook);
}catch (Exception e){
e.printStackTrace();
}
}
count = count + mapValue.size();
}
}
// 遍历集合数据,产生数据行
if(list!=null && list.size() > 0){
int index = 1;
int count1 = 1;
for (short i = 0; i < list.size(); i++) {
index++;
// 从表头下一行开始插入数据
row = sheet.createRow(index);
row.createCell(0).setCellValue(list.get(i).get("XLMC").toString());
for(Map.Entry<String, List<Map>> entry : temp.entrySet()){
List<Map> mapValue = entry.getValue();
manKey=entry.getKey();
if(mapValue.size()>0){
for (short ii = 0; ii < mapValue.size(); ii++) {
try {
HSSFCell cell = row.createCell(count1 + ii);
//通过遍历表头二级表单“CODE_TWO”值 匹配内容接口数据
if(null==list.get(i).get(mapValue.get(ii).get("CODE_TWO").toString())||("null").equals(list.get(i).get(mapValue.get(ii).get("CODE_TWO").toString()))){
cell.setCellValue("0");
}else {
cell.setCellValue(list.get(i).get(mapValue.get(ii).get("CODE_TWO").toString()).toString());
}
}catch (Exception e){
e.printStackTrace();
}
}
count1 = count1 + mapValue.size();
}
}//当该行数据遍历完成之后,下一行从第二列开始
count1=1;
}
}
OutputStream outputStream = response.getOutputStream();// 打开流
workbook.write(outputStream);// HSSFWorkbook写入流
workbook.close();// HSSFWorkbook关闭
outputStream.flush();// 刷新流
outputStream.close();// 关闭流
System.out.println("导出成功");
}
private void addBorderLine(HSSFSheet sheet,CellRangeAddress cellRangeAddress,HSSFWorkbook workbook){
RegionUtil.setBorderTop(1, cellRangeAddress, sheet, workbook);
RegionUtil.setBorderBottom(1, cellRangeAddress, sheet, workbook);
RegionUtil.setBorderLeft(1, cellRangeAddress, sheet, workbook);
RegionUtil.setBorderRight(1, cellRangeAddress, sheet, workbook);
}
内容接口: