Java实现Excel多表头动态数据导出
好久没时间写帖子了,由于工作需要,写一个基于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); }
内容接口: