Java实现Excel多表头动态数据导出

   好久没时间写帖子了,由于工作需要,写一个基于JAVA实现的Excel多表头动态导出功能,首先可能这个叫法比较啰嗦,下面我们先看看什么是Excel多表头动态导出(效果图):

Java实现Excel多表头动态数据导出

 它包含两部分:1、是表头,就像大家看到的样子它的表头是多行合并的(多表头);2.内容部分,就是下方的数据数据内容。

两个内容都是有后台人员分别由两个接口返回的(也可以一个,但一个会显得格式冗余),因此他的表头数据是不固定的,可能有甲方要求,甲方、乙方、丙方要求等,是随机看后台数据的,所以我们就不能事先画好Excel模板进行导出,好以上就是我对java实现Excel多表头动态导出的说明下面开始吧。

一、在开发前我们要撸一下思路,该怎么去做?会遇到什么坑?首先不用多说肯定识这个多表头的问题了,后端返回的Json数据一般都:

Java实现Excel多表头动态数据导出

不难发现一个list<Map>发现虽然都有一、二级目录的区分,但是分开的不符合我们的需要,因此我们需要转换分组为想要的数据类型

Map<String, List<Map>>格式的数据:既把同一个一级目录下的二级目录放到一个List<Map>中方便后面的Excel渲染

Java实现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);
}

内容接口:

Java实现Excel多表头动态数据导出