目录一、工单服务申请明细表

发布时间:2025-06-24 19:07:38  作者:北方职教升学中心  阅读量:903


最后通过【字段映射列及列数】和实际数据,构造【内容】列。工单结论、【字段映射列及列数】分别单独设置。费用合计、复杂表单分析

    • 1.表单示例
    • 2.复杂表单拆解
    • 3.准备模板
  • 二、EasyExcel文档
    • 1.最简单的填充Excel
    • 2.使用table去写入
  • 三、

    3.准备模板

    因为表格的前7行是固定格式的,为了简化操作,我们直接将格式定好作为模板。
    (4)14行:表格动态写入的部分,由【工单结论】的字段列标题组成
    (5)15-16行:表格动态写入的部分,由【费用计算】的字段列标题组成
    (6)17-19行:表格固定的部分,但是因为无法往表格中间插入table,所以此处只能将固定的格式数据以table方法写入,由【表格固定结尾】的字段内容组成。代码示例

    1.实体类

    工单模板的信息来源于工单信息、

    1)工单信息表

    /** * 工单信息表 */@Builder@NoArgsConstructor@AllArgsConstructor@ToString(callSuper =true)@EqualsAndHashCode(callSuper =false)@Accessors(chain =true)@DatapublicclassWorkOrderInfoimplementsSerializable{// 工单IDprivateLong workOrderId;// 工单号码privateString workOrderCode;// 发起人名称privateString creatorName;// 处理人名称privateString handerName;// 工单标题privateString workOrderTitle;// 工单内容privateString workOrderContent;// 工单结论privateString workOrderResult;}

    2)工单申请信息表

    /** * 工单申请信息表 */@Builder@NoArgsConstructor@AllArgsConstructor@ToString(callSuper =true)@EqualsAndHashCode(callSuper =false)@Accessors(chain =true)@DatapublicclassWorkOrderApplyInfoimplementsSerializable{// 工单申请idprivateLong workOrderApplyId;// 工单idprivateLong workOrderId;// 工单上门次数privateString visitNum;// 工单单次支付(元)privateString singlePay;// 工单总计费用(元)privateString totalPay;// 设备申请明细privateList<WorkOrderDevice>deviceList;// 服务申请明细privateList<WorkOrderService>serviceList;}

    3)工单设备申请明细表

    /** * 工单设备申请明细表 */@Builder@NoArgsConstructor@AllArgsConstructor@ToString(callSuper =true)@EqualsAndHashCode(callSuper =false)@Accessors(chain =true)@DatapublicclassWorkOrderDeviceimplementsSerializable{// 工单设备申请明细idprivateLong workOrderDeviceId;// 工单idprivateLong workOrderId;// 工单申请idprivateLong workOrderApplyId;// 设备名称privateString deviceName;// 设备明数privateString deviceContent;// 申请数量privateString applyNum;}

    4)工单服务申请表

    /** * 工单服务申请明细表 */@Builder@NoArgsConstructor@AllArgsConstructor@ToString(callSuper =true)@EqualsAndHashCode(callSuper =false)@Accessors(chain =true)@DatapublicclassWorkOrderServiceimplementsSerializable{// 工单设备申请明细idprivateLong workOrderServiceId;// 工单idprivateLong workOrderId;// 工单申请idprivateLong workOrderApplyId;// 服务名称privateString serviceName;// 服务描述privateString serviceContent;// 申请工时(时)privateString hourNum;}

    2.工具类

    1)内容导出工具类

    因为该模板中不同的table,标题不同,占用合并的列数

    tips:excel写入实际上是一行一行的写入数据一行的数据结构是List<String>多行的数据结构是List<List<String>>
    /** * 工单确认单内容导出工具类 */@Component@Slf4jpublicclassWorkOrderDataUtils{// >>>>>>>>>>>>>>> 标题构造 >>>>>>>>>>>>>>>>>>>>>>>>>>>/**     * 设备列表标题     * @return     */publicstaticList<List<String>>deviceHead(){List<List<String>>list =newArrayList<List<String>>();List<String>head0 =newArrayList<String>();head0.add("工单器材");// 占用3列list.add(head0);list.add(head0);list.add(head0);List<String>head1 =newArrayList<String>();head1.add("器材描述");// 占用2列list.add(head1);list.add(head1);List<String>head2 =newArrayList<String>();head2.add("数量");// 占用两列list.add(head2);list.add(head2);returnlist;}/**     * 服务列表标题     * @return     */publicstaticList<List<String>>serviceHead(){List<List<String>>list =newArrayList<List<String>>();List<String>head0 =newArrayList<String>();head0.add("工单服务");// 占用3列list.add(head0);list.add(head0);list.add(head0);List<String>head1 =newArrayList<String>();head1.add("服务描述");// 占用2列list.add(head1);list.add(head1);List<String>head2 =newArrayList<String>();head2.add("工时");// 占用两列list.add(head2);list.add(head2);returnlist;}/**     * 工单结论标题:     * 将结论传入作为标题     * @return     */publicstaticList<List<String>>workOrderResultHead(String workOrderResult){List<List<String>>list =newArrayList<List<String>>();List<String>head0 =newArrayList<String>();head0.add("工单结论");// 占用3列list.add(head0);list.add(head0);list.add(head0);List<String>head1 =newArrayList<String>();head1.add(workOrderResult);// 占用4列list.add(head1);list.add(head1);list.add(head1);list.add(head1);returnlist;}/**     * 费用合计标题     * 将上门次数,每次支付(元),总费用传入作为标题     * @return     */publicstaticList<List<String>>totalHead(String visitNum,String singlePay,String totalPay){List<List<String>>list =newArrayList<List<String>>();List<String>head0 =newArrayList<String>();head0.add("上门次数");head0.add(visitNum);// 占用3列list.add(head0);list.add(head0);list.add(head0);List<String>head1 =newArrayList<String>();head1.add("每次支付(元)");head1.add(singlePay);// 占用2列list.add(head1);list.add(head1);List<String>head2 =newArrayList<String>();head2.add("总费用");head2.add(totalPay);// 占用两列list.add(head2);list.add(head2);returnlist;}// >>>>>>>>>>>>>>> 结尾固定内容 >>>>>>>>>>>>>>>>>>>>>>>>>>>/**     * 结尾固定内容     * @return     */publicstaticList<List<Object>>getFinalData(String content){List<List<Object>>list =newArrayList<List<Object>>();List<Object>row0 =newArrayList<Object>();row0.add(content);row0.add(content);row0.add(content);row0.add(content);row0.add(content);row0.add(content);row0.add(content);List<Object>row5 =newArrayList<Object>();row5.add("发起人签字");row5.add("");row5.add("");row5.add("");row5.add("处理人签字");row5.add("");row5.add("");list.add(row0);list.add(row0);list.add(row5);returnlist;}// >>>>>>>>>>>>>> 内容构造 >>>>>>>>>>>>>>>>>>>>>>>>>>>>/**     * 设备字段映射及占用列数     * @return     */publicstaticLinkedHashMap<String,Integer>fieldRowNumMapByDevice(){LinkedHashMap<String,Integer>fieldRowNumMap =newLinkedHashMap<>();// 器材名称fieldRowNumMap.put("deviceName",3);// 器材描述fieldRowNumMap.put("deviceContent",2);// 申请数量fieldRowNumMap.put("applyNum",2);returnfieldRowNumMap;}/**     * 服务字段映射及占用列数     * @return     */publicstaticLinkedHashMap<String,Integer>fieldRowNumMapByDevice(){LinkedHashMap<String,Integer>fieldRowNumMap =newLinkedHashMap<>();// 服务名称fieldRowNumMap.put("serviceName",3);// 服务描述fieldRowNumMap.put("serviceContent",2);// 服务工时fieldRowNumMap.put("hourName",2);returnfieldRowNumMap;}// >>>>>>>>>>>>>> 内容构造 >>>>>>>>>>>>>>>>>>>>>>>>>>>>/**     * 设备/服务列表内容构造     * 通过反射获取对应字段的内容值     * @param detailsList 设备/服务列表     * @param fieldRowNumMap 对应的字段及列数对象     * @return     */publicstaticList<List<Object>>contentRowData(List<?>applyList,Class<?>clazz            ,LinkedHashMap<String,Integer>fieldRowNumMap)throws NoSuchFieldException,IllegalAccessException {List<List<Object>>list =newArrayList<List<Object>>();for(BusinessInternalDetailsExcelVO deviceDTO :detailsList){List<Object>rown =newArrayList<Object>();// 防止物料名称和规格列内容相同合并String previouFieldContent ="";// {"key":"productName", value:"3"} 字段和对应设置的个数,后续设置了相同内容合并的策略for(Map.Entry <String,Integer>entry :fieldRowNumMap.entrySet()){String fieldName =entry.getKey();Integer fieldRowNum =entry.getValue();// 通过反射,根据字段名获取该对象中的字段值Field declaredField =clazz.class.getDeclaredField(fieldName);declaredField.setAccessible(true);String fieldContent =" ";if(declaredField.get(deviceDTO)!=null){// 当为空字符串时,不赋值if(!"".equals(declaredField.get(deviceDTO))){fieldContent =declaredField.get(deviceDTO).toString();}// 防止相邻两列的内容相同合并if(fieldContent.equals(previouFieldContent)){fieldContent =" "+fieldContent;}}previouFieldContent =fieldContent;for(int i=0;i <fieldRowNum;i++){rown.add(fieldContent);}}list.add(rown);}returnlist;}// >>>>>>>>>>>>>> 生成模板 >>>>>>>>>>>>>>>>>>>>>>>>>>>>/**     * 工单确认单-生成模板     */publicstaticvoidgenerateWorkOrderTemplate(ExcelWriter excelWriter,WriteSheet writeSheet            ,WorkOrderApplyInfo orderApply)throws IOException {try{// table集成sheet配置(有/无头),此处多table需要各自的表头// 设备列表WriteTable writeTable0 =EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();// 服务列表WriteTable writeTable1 =EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();// 工单结论列WriteTable writeTable2 =EasyExcel.writerTable(2).needHead(Boolean.TRUE).build();// 费用合计WriteTable writeTableTotal =EasyExcel.writerTable(3).needHead(Boolean.TRUE).build();// 固定内容部分(备注)WriteTable writeTableFin =EasyExcel.writerTable(4).needHead(Boolean.FALSE).build();// 设备table写入头和内容writeTable0.setHead(this.deviceHead());excelWriter.write(contentRowData(orderApply.getDeviceList(),WorkOrderDevice.class,this.fieldRowNumMapByDevice()),writeSheet,writeTable0);// 服务 table写入头和内容writeTable1.setHead(this.serviceHead());excelWriter.write(contentRowData(this.getServiceList(),WorkOrderService.class,this.fieldRowNumMapByService()),writeSheet,writeTable1);// 工单结论 table仅写入头writeTable2.setHead(this.workOrderResultHead(orderApply.getWorkOrderResult()));excelWriter.write(newArrayList<>(),writeSheet,writeTable2);// 合计 table仅写入头writeTableTotal.setHead(this.workOrderResultHead(orderApply.getVisitNum(),orderApply.getWorkOrderResult(),orderApply.getSinglePay(),orderApply.getTotalPay()));excelWriter.write(newArrayList<>(),writeSheet,writeTableTotal);// 固定内容备注+签名行不创建头,在合计table之后写入数据excelWriter.write(this.getFinalData("备注 ....."),writeSheet,writeTableFin);//完成excelWriter.finish();}catch(Exception e){log.error("工单确认单模板生成,方法异常>>>>>>>>>>>>>>",e);}}}

    2)合并策略

    importcom.alibaba.excel.metadata.Head;importcom.alibaba.excel.write.merge.AbstractMergeStrategy;importorg.apache.poi.ss.usermodel.Cell;importorg.apache.poi.ss.usermodel.Sheet;importorg.apache.poi.ss.util.CellRangeAddress;importjava.util.*;/** * 功能描述:规则: 优先合并列,再合并行 * * @author SXT * @version 1.0 * @date 2024/3/9 15:12 */publicclassMergeCellStrategyHandlerextendsAbstractMergeStrategy{/**     * 相同列合并     */privateboolean alikeColumn;/**     * 相同行合并     */privateboolean alikeRow;/** 开始进行合并的行index */privateint rowIndex;/** 开始进行合并的行index */privateint rowIndexStart;/** 跨行合并的列index */privateSet<Integer>columns;privateint currentRowIndex =0;/**     * 构造方法,指定合并方式     * @param alikeColumn     * @param alikeRow     * @param rowIndex     * @param columns     */publicMergeCellStrategyHandler(boolean alikeColumn,boolean alikeRow,int rowIndex,Set<Integer>columns){this.alikeColumn =alikeColumn;this.alikeRow =alikeRow;this.rowIndex =rowIndex;this.columns =columns;}/**     * 构造方法,指定合并方式     * @param alikeColumn     * @param alikeRow     * @param rowIndex     * @param columns     */publicMergeCellStrategyHandler(boolean alikeColumn,boolean alikeRow,int rowIndex,Set<Integer>columns,int rowIndexStart){this.alikeColumn =alikeColumn;this.alikeRow =alikeRow;this.rowIndex =rowIndex;this.columns =columns;this.rowIndexStart =rowIndexStart;}/**     * 指定是否进行跨列合并单元格     * @param alikeColumn     * @param rowIndex     */publicMergeCellStrategyHandler(boolean alikeColumn,int rowIndex){this(alikeColumn,false,rowIndex,newHashSet<>());}/**     * 指定是否进行跨列合并单元格     * @param alikeColumn     * @param rowIndex     * @param rowIndexStart 开始行合并的行数     */publicMergeCellStrategyHandler(boolean alikeColumn,int rowIndex,int rowIndexStart){this(alikeColumn,false,rowIndex,newHashSet<>(),rowIndexStart);}/**     * 指定是否进行跨行合并单元格     * @param alikeRow     * @param rowIndex     * @param columns     */publicMergeCellStrategyHandler(boolean alikeRow,int rowIndex,Set<Integer>columns){this(false,alikeRow,rowIndex,columns);}@Override    protectedvoidmerge(Sheet sheet,Cell cell,Head head,Integer integer){int rowId =cell.getRowIndex();currentRowIndex =rowId ==currentRowIndex ?currentRowIndex :rowId;if(rowIndex >rowId){return;}int columnId =cell.getColumnIndex();// 列合并if(alikeColumn &&columnId >0){String currentCellVal =this.getCellVal(cell);Cell preCell =cell.getRow().getCell(columnId -1);String preCellVal =this.getCellVal(preCell);if(null!=currentCellVal &&null!=preCellVal &&!preCellVal.isEmpty()&&!currentCellVal.isEmpty()){// 当前单元格内容与上一个单元格内容相等,进行合并处理if(preCellVal.equals(currentCellVal)){CellRangeAddress rangeAddress =newCellRangeAddress(currentRowIndex,currentRowIndex,columnId -1,columnId);rangeAddress =this.findExistAddress(sheet,rangeAddress,currentCellVal);if(null!=rangeAddress){sheet.addMergedRegion(rangeAddress);}}}}// 限制开始行合并的行数if(rowId >rowIndexStart){// 行合并if(alikeRow &&rowIndex <rowId &&columns.contains(columnId)){String currentCellVal =this.getCellVal(cell);Cell preCell =sheet.getRow(rowId -1).getCell(columnId);String preCellVal =this.getCellVal(preCell);if(null!=currentCellVal &&null!=preCellVal &&!preCellVal.isEmpty()&&!currentCellVal.isEmpty()){// 当前单元格内容与上一行单元格内容相等,进行合并处理if(preCellVal.equals(currentCellVal)){//sheet.validateMergedRegions();CellRangeAddress rangeAddress =newCellRangeAddress(currentRowIndex -1,currentRowIndex,columnId,columnId);rangeAddress =this.findExistAddress(sheet,rangeAddress,currentCellVal);if(null!=rangeAddress){sheet.addMergedRegion(rangeAddress);}}}}}}/**     * 合并单元格地址范围,发现存在相同的地址则进行扩容合并     *     * @param sheet     * @param rangeAddress  单元格合并地址     * @param currentVal 当前单元格中的值     * @return     */privateCellRangeAddress findExistAddress(Sheet sheet,CellRangeAddress rangeAddress,String currentVal){List<CellRangeAddress>mergedRegions =sheet.getMergedRegions();int existIndex =0;Map<Integer,CellRangeAddress>existIdexMap =newLinkedHashMap<>();if(null!=mergedRegions &&!mergedRegions.isEmpty()){//验证当前合并的单元格是否存在重复for(CellRangeAddress mergedRegion :mergedRegions){if(mergedRegion.intersects(rangeAddress)){existIdexMap.put(existIndex,mergedRegion);}existIndex++;}}if(existIdexMap.isEmpty()){returnrangeAddress;}List<Integer>existIndexList =newArrayList<>(existIdexMap.size());for(Map.Entry<Integer,CellRangeAddress>addressEntry :existIdexMap.entrySet()){CellRangeAddress exist =addressEntry.getValue();// 自动进行单元格合并处理int firstRow =rangeAddress.getFirstRow();int lastRow =rangeAddress.getLastRow();int firstColumn =rangeAddress.getFirstColumn();int lastColumn =rangeAddress.getLastColumn();int firstRow1 =exist.getFirstRow();int lastRow1 =exist.getLastRow();int firstColumn1 =exist.getFirstColumn();int lastColumn1 =exist.getLastColumn();// 跨行合并 最后一列相等, 行不相等if(lastRow >lastRow1 &&lastColumn ==lastColumn1){// 检查进行跨行合并的单元格是否已经存在跨列合并if(lastColumn >0&&firstColumn1 !=lastColumn1){// 获取当前单元格的前一列单元格String cellVal =this.getCellVal(sheet.getRow(lastRow).getCell(lastColumn -1));if(null!=cellVal &&cellVal.equals(currentVal)){exist.setLastRow(lastRow);}}else{exist.setLastRow(lastRow);}rangeAddress =exist;existIndexList.add(addressEntry.getKey());}// 跨列合并 行相等,列不相等if(lastColumn >lastColumn1 &&firstRow ==firstRow1 ){exist.setLastColumn(lastColumn);rangeAddress =exist;existIndexList.add(addressEntry.getKey());}}// 移除已经存在且冲突的合并数据if(existIndexList.isEmpty()){rangeAddress =null;}else{sheet.removeMergedRegions(existIdexMap.keySet());}returnrangeAddress;}/**     * 获取单元格中的内容     * @param cell     * @return     */privateString getCellVal(Cell cell){String val =null;try{val =cell.getStringCellValue();}catch(Exception e){System.out.printf("读取单元格内容失败:行%d 列%d %n",(cell.getRowIndex()+1),(cell.getColumnIndex()+1));}returnval;}}

    3)单元格样式策略

    importcom.alibaba.excel.metadata.Head;importcom.alibaba.excel.metadata.data.WriteCellData;importcom.alibaba.excel.util.StyleUtil;importcom.alibaba.excel.write.handler.CellWriteHandler;importcom.alibaba.excel.write.metadata.holder.WriteSheetHolder;importcom.alibaba.excel.write.metadata.holder.WriteTableHolder;importcom.alibaba.excel.write.metadata.style.WriteCellStyle;importcom.alibaba.excel.write.metadata.style.WriteFont;importorg.apache.poi.ss.usermodel.*;importjava.util.HashMap;importjava.util.List;/** * @Desc 拦截处理单元格创建 */publicclassCellStyleWriteHandlerimplementsCellWriteHandler{/**     * map     * key:第i行     * value:第i行中单元格索引集合     */privateHashMap<Integer,List<Integer>>map;/**     * 颜色     */privateShort colorIndex;/**     * 有参构造     * map:用来记录需要为第key行中的第value.get(i)列设置样式     * colorIndex:表示单元格需要设置的颜色     */publicCellStyleWriteHandler(HashMap<Integer,List<Integer>>map,Short colorIndex){this.map =map;this.colorIndex =colorIndex;}/**     * 无参构造     */publicCellStyleWriteHandler(){}/**     * 在创建单元格之前调用     */@Override    publicvoidbeforeCellCreate(WriteSheetHolder writeSheetHolder,WriteTableHolder writeTableHolder,Row row,Head head,Integer columnIndex,Integer relativeRowIndex,Boolean isHead){}/**     * 在单元格创建后调用     */@Override    publicvoidafterCellCreate(WriteSheetHolder writeSheetHolder,WriteTableHolder writeTableHolder,Cell cell,Head head,Integer relativeRowIndex,Boolean isHead){}/**     * 在单元上的所有操作完成后调用     * 指定单元格特殊处理 todo 待修改为指定样式     */@Override    publicvoidafterCellDispose(WriteSheetHolder writeSheetHolder,WriteTableHolder writeTableHolder,List<WriteCellData<?>>cellDataList,Cell cell,Head head,Integer relativeRowIndex,Boolean isHead){/**         * 考虑到导出数据量过大的情况,不对每一行的每一个单元格进行样式设置,只设置必要行中的某个单元格的样式         *///当前行的第i列int i =cell.getColumnIndex();//不处理第一行if(0!=cell.getRowIndex()){List<Integer>integers =map.get(cell.getRowIndex());if(integers !=null&&integers.size()>0){if(integers.contains(i)){// 根据单元格获取workbookWorkbook workbook =cell.getSheet().getWorkbook();//设置行高writeSheetHolder.getSheet().getRow(cell.getRowIndex()).setHeight((short)(4.5*256));// 单元格策略WriteCellStyle contentWriteCellStyle =newWriteCellStyle();// 设置背景颜色白色contentWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());// 设置垂直居中为向上对齐contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.TOP);// 设置左右对齐为靠左对齐contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);// 设置单元格上下左右边框为细边框contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);contentWriteCellStyle.setBorderRight(BorderStyle.THIN);contentWriteCellStyle.setBorderTop(BorderStyle.THIN);// 创建字体实例WriteFont cellWriteFont =newWriteFont();// 设置字体大小cellWriteFont.setFontName("宋体");cellWriteFont.setFontHeightInPoints((short)11);//设置字体颜色:无效cellWriteFont.setColor(colorIndex);//单元格颜色:无效contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());contentWriteCellStyle.setWriteFont(cellWriteFont);CellStyle cellStyle =StyleUtil.buildCellStyle(workbook,null,contentWriteCellStyle);//设置当前行第i列的样式cell.getRow().getCell(i).setCellStyle(cellStyle);}}}}}

    3.模板导出

    /** * 导出工单表单模板 */publicvoidexportWorkOrderTemplate(Long orderId)throws IOException {/**:         * 根据表单id查询表单信息,回填模板表         */WorkOrderInfo workOrderInfo =workOrderInfoMapper.selectById(orderId);if(workOrderInfo ==null){thrownewBizException("工单信息不存在");}/**         * 查询工单设备、而余下的行是根据数据量动态写入的。EasyExcel文档

    有了以上思路,开始翻阅easyExcel文档,查找可以使用的方法。
    那么,我们在代码中需要对该模板进行填充写入的操作。
    在这里插入图片描述

    二、服务、最终确定使用以下两个方法去实现。
    在这里插入图片描述

    2.使用table去写入

    用于写入动态的标题和内容。

    1.最简单的填充Excel

    用于填充固定模板中的指定字段。

    目录

    • 一、工单服务申请明细表。代码示例
      • 1.实体类
        • 1)工单信息表
        • 2)工单申请信息表
        • 3)工单设备申请明细表
        • 4)工单服务申请表
      • 2.工具类
        • 1)内容导出工具类
        • 2)合并策略
        • 3)单元格样式策略
      • 3.模板导出
    • 参考文章

    一、对应字段也不同。工单申请信息、
    常规Excel写入一般是一个工作簿一个表头。所以此处将设备、复杂表单分析

    1.表单示例

    复杂表单示例1

    2.复杂表单拆解

    复杂表单示例拆解
    示例的模板,可以拆解为6个组成部分:
    (1)1-7行:表格固定的部分,需要在指定的单元格动态填充信息
    (2)8-10行:表格动态写入的部分,由【工单器材】的字段列标题和内容组成。工单设备申请明细、

    需求:在一个工作簿中,需要填充固定字段信息,并写入多个不同的标题列的表格及内容。服务申请情况 */WorkOrderApplyInfo orderApply =workOrderApplyService.getWorkOrderApplayInfo(olderId);// 开始合并的行数int addRowNum =3;// 设备列表List<WorkOrderDevice>deviceList =orderApply.getDeviceList();// 服务列表List<WorkOrderService>serviceList =orderApply.getServiceList();// 尾部固定行开始位置 = 前置固定行(7) + 设备列表行 + 服务列表行 + 合计行int endCol =MictConstants.EIGHT+deviceList.size()+serviceList.size()+addRowNum;// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 文件生成准备 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>RequestAttributes requestAttributes =RequestContextHolder.currentRequestAttributes();HttpServletResponse response =((ServletRequestAttributes)requestAttributes).getResponse();// 获取模版文件ClassPathResource classPathResource =newClassPathResource("xls_template/work_order_template.xlsx");InputStream fis =classPathResource.getInputStream();// 文件输出流String fileName =URLEncoder.encode(String.format("%s.xlsx","工单确认单"),"UTF-8");response.setContentType("application/x-download;charset=UTF-8");response.addHeader("Cache-Control","no-cache, no-store, must-revalidate");response.addHeader("Pragma","no-cache");response.addHeader("Access-Control-Expose-Headers","Content-Disposition");response.setHeader("Content-Disposition","attachment;filename="+fileName);OutputStream out =response.getOutputStream();try{// 构造固定填充模板对象WorkOrderInfo workOrderPrefix =WorkOrderInfo.builder().workOrderCode(workOrderInfo.getWorkOrderCode()).creatorName(workOrderInfo.getCreatorName()).handerName(workOrderInfo.getHanderName()).workOrderTitle(workOrderInfo.getWorkOrderTitle()).workOrderContent(businessInfo.getWorkOrderContent()).build();/** * 用来记录需要为第`key`行中的第`value.get(i)`列设置样式 */// 指定需要跨行合并的列项:7列HashSet<Integer>colSet =newHashSet<>(Arrays.asList(0,1,2,3,4,5,6));HashMap<Integer,List<Integer>>map =newHashMap<>(MictConstants.SIX);for(int i =endCol;i <endCol +MictConstants.SIX;i++){map.put(i,Arrays.asList(0,1,2,3,4,5,6));}// 设置excel输出策略ExcelWriter excelWriter =EasyExcel .write(out,WorkOrderInfo.class).withTemplate(fis)// 默认样式策略.registerWriteHandler(EasyExcelUtils.defaultStyles())// 指定单元格样式(备注说明内容栏).registerWriteHandler(newCellStyleWriteHandler(map,IndexedColors.RED.getIndex()))// 行和列合并策略.registerWriteHandler(newMergeCellStrategyHandler(true,true,7,colSet,endCol)).build();// sheet设置不需要头WriteSheet writeSheet =EasyExcel.writerSheet(0,"工单确认单").needHead(Boolean.FALSE).build();// 填充固定值excelWriter.fill(workOrderPrefix,writeSheet);// 使用table写入模板WorkOrderDataUtils.generateWorkOrderTemplate(excelWriter,writeSheet,orderApply);out.flush();out.close();fis.close();}catch(Exception e){log.error("工单确认单生成,方法异常>>>>>>>>>>>>>>",e);}}

    参考文章

    EasyExcel动态单元格合并(跨行或跨列) by 酸菜鱼没有鱼

    easyExcel实现单sheet多子表,并结合动态表头,复杂表头 by IM@taoyalong

    easyExcel官方文档


  • 在这里插入图片描述

    三、固定结尾的【标题】、
    (3)11-13行:表格动态写入的部分,由【工单服务】的字段列标题和内容组成。