需求:在一个工作簿中,需要填充固定字段信息,并写入多个不同的标题列的表格及内容。
常规Excel写入一般是一个工作簿一个表头。
目录
- 一、复杂表单分析
- 1.表单示例
- 2.复杂表单拆解
- 3.准备模板
- 二、EasyExcel文档
- 1.最简单的填充Excel
- 2.使用table去写入
- 三、代码示例
- 1.实体类
- 1)工单信息表
- 2)工单申请信息表
- 3)工单设备申请明细表
- 4)工单服务申请表
- 2.工具类
- 1)内容导出工具类
- 2)合并策略
- 3)单元格样式策略
- 3.模板导出
- 参考文章
一、复杂表单分析
1.表单示例
2.复杂表单拆解
示例的模板,可以拆解为6个组成部分:
(1)1-7行:表格固定的部分,需要在指定的单元格动态填充信息
(2)8-10行:表格动态写入的部分,由【工单器材】的字段列标题和内容组成。
(3)11-13行:表格动态写入的部分,由【工单服务】的字段列标题和内容组成。
(4)14行:表格动态写入的部分,由【工单结论】的字段列标题组成
(5)15-16行:表格动态写入的部分,由【费用计算】的字段列标题组成
(6)17-19行:表格固定的部分,但是因为无法往表格中间插入table,所以此处只能将固定的格式数据以table方法写入,由【表格固定结尾】的字段内容组成。
3.准备模板
因为表格的前7行是固定格式的,为了简化操作,我们直接将格式定好作为模板。而余下的行是根据数据量动态写入的。
那么,我们在代码中需要对该模板进行填充和写入的操作。
二、EasyExcel文档
有了以上思路,开始翻阅easyExcel文档,查找可以使用的方法。最终确定使用以下两个方法去实现。
1.最简单的填充Excel
用于填充固定模板中的指定字段。
2.使用table去写入
用于写入动态的标题和内容。
三、代码示例
1.实体类
工单模板的信息来源于工单信息、工单申请信息、工单设备申请明细、工单服务申请明细表。
1)工单信息表
/**
* 工单信息表
*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Data
public class WorkOrderInfo implements Serializable {
// 工单ID
private Long workOrderId;
// 工单号码
private String workOrderCode;
// 发起人名称
private String creatorName;
// 处理人名称
private String handerName;
// 工单标题
private String workOrderTitle;
// 工单内容
private String workOrderContent;
// 工单结论
private String workOrderResult;
}
2)工单申请信息表
/**
* 工单申请信息表
*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Data
public class WorkOrderApplyInfo implements Serializable {
// 工单申请id
private Long workOrderApplyId;
// 工单id
private Long workOrderId;
// 工单上门次数
private String visitNum;
// 工单单次支付(元)
private String singlePay;
// 工单总计费用(元)
private String totalPay;
// 设备申请明细
private List<WorkOrderDevice> deviceList;
// 服务申请明细
private List<WorkOrderService> serviceList;
}
3)工单设备申请明细表
/**
* 工单设备申请明细表
*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Data
public class WorkOrderDevice implements Serializable {
// 工单设备申请明细id
private Long workOrderDeviceId;
// 工单id
private Long workOrderId;
// 工单申请id
private Long workOrderApplyId;
// 设备名称
private String deviceName;
// 设备明数
private String deviceContent;
// 申请数量
private String applyNum;
}
4)工单服务申请表
/**
* 工单服务申请明细表
*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Data
public class WorkOrderService implements Serializable {
// 工单设备申请明细id
private Long workOrderServiceId;
// 工单id
private Long workOrderId;
// 工单申请id
private Long workOrderApplyId;
// 服务名称
private String serviceName;
// 服务描述
private String serviceContent;
// 申请工时(时)
private String hourNum;
}
2.工具类
1)内容导出工具类
因为该模板中不同的table,标题不同,占用合并的列数、对应字段也不同。所以此处将设备、服务、工单结论、费用合计、固定结尾的【标题】、【字段映射列及列数】分别单独设置。最后通过【字段映射列及列数】和实际数据,构造【内容】列。
tips:
excel写入实际上是一行一行的写入数据
一行的数据结构是List<String>
多行的数据结构是List<List<String>>
/**
* 工单确认单内容导出工具类
*/
@Component
@Slf4j
public class WorkOrderDataUtils {
// >>>>>>>>>>>>>>> 标题构造 >>>>>>>>>>>>>>>>>>>>>>>>>>>
/**
* 设备列表标题
* @return
*/
public static List<List<String>> deviceHead() {
List<List<String>> list = new ArrayList<List<String>>();
List<String> head0 = new ArrayList<String>();
head0.add("工单器材");
// 占用3列
list.add(head0);
list.add(head0);
list.add(head0);
List<String> head1 = new ArrayList<String>();
head1.add("器材描述");
// 占用2列
list.add(head1);
list.add(head1);
List<String> head2 = new ArrayList<String>();
head2.add("数量");
// 占用两列
list.add(head2);
list.add(head2);
return list;
}
/**
* 服务列表标题
* @return
*/
public static List<List<String>> serviceHead() {
List<List<String>> list = new ArrayList<List<String>>();
List<String> head0 = new ArrayList<String>();
head0.add("工单服务");
// 占用3列
list.add(head0);
list.add(head0);
list.add(head0);
List<String> head1 = new ArrayList<String>();
head1.add("服务描述");
// 占用2列
list.add(head1);
list.add(head1);
List<String> head2 = new ArrayList<String>();
head2.add("工时");
// 占用两列
list.add(head2);
list.add(head2);
return list;
}
/**
* 工单结论标题:
* 将结论传入作为标题
* @return
*/
public static List<List<String>> workOrderResultHead(String workOrderResult) {
List<List<String>> list = new ArrayList<List<String>>();
List<String> head0 = new ArrayList<String>();
head0.add("工单结论");
// 占用3列
list.add(head0);
list.add(head0);
list.add(head0);
List<String> head1 = new ArrayList<String>();
head1.add(workOrderResult);
// 占用4列
list.add(head1);
list.add(head1);
list.add(head1);
list.add(head1);
return list;
}
/**
* 费用合计标题
* 将上门次数,每次支付(元),总费用传入作为标题
* @return
*/
public static List<List<String>> totalHead(String visitNum, String singlePay, String totalPay) {
List<List<String>> list = new ArrayList<List<String>>();
List<String> head0 = new ArrayList<String>();
head0.add("上门次数");
head0.add(visitNum);
// 占用3列
list.add(head0);
list.add(head0);
list.add(head0);
List<String> head1 = new ArrayList<String>();
head1.add("每次支付(元)");
head1.add(singlePay);
// 占用2列
list.add(head1);
list.add(head1);
List<String> head2 = new ArrayList<String>();
head2.add("总费用");
head2.add(totalPay);
// 占用两列
list.add(head2);
list.add(head2);
return list;
}
// >>>>>>>>>>>>>>> 结尾固定内容 >>>>>>>>>>>>>>>>>>>>>>>>>>>
/**
* 结尾固定内容
* @return
*/
public static List<List<Object>> getFinalData(String content) {
List<List<Object>> list = new ArrayList<List<Object>>();
List<Object> row0 = new ArrayList<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 = new ArrayList<Object>();
row5.add("发起人签字");
row5.add("");
row5.add("");
row5.add("");
row5.add("处理人签字");
row5.add("");
row5.add("");
list.add(row0);
list.add(row0);
list.add(row5);
return list;
}
// >>>>>>>>>>>>>> 内容构造 >>>>>>>>>>>>>>>>>>>>>>>>>>>>
/**
* 设备字段映射及占用列数
* @return
*/
public static LinkedHashMap<String, Integer> fieldRowNumMapByDevice() {
LinkedHashMap<String, Integer> fieldRowNumMap = new LinkedHashMap<>();
// 器材名称
fieldRowNumMap.put("deviceName", 3);
// 器材描述
fieldRowNumMap.put("deviceContent", 2);
// 申请数量
fieldRowNumMap.put("applyNum", 2);
return fieldRowNumMap;
}
/**
* 服务字段映射及占用列数
* @return
*/
public static LinkedHashMap<String, Integer> fieldRowNumMapByDevice() {
LinkedHashMap<String, Integer> fieldRowNumMap = new LinkedHashMap<>();
// 服务名称
fieldRowNumMap.put("serviceName", 3);
// 服务描述
fieldRowNumMap.put("serviceContent", 2);
// 服务工时
fieldRowNumMap.put("hourName", 2);
return fieldRowNumMap;
}
// >>>>>>>>>>>>>> 内容构造 >>>>>>>>>>>>>>>>>>>>>>>>>>>>
/**
* 设备/服务列表内容构造
* 通过反射获取对应字段的内容值
* @param detailsList 设备/服务列表
* @param fieldRowNumMap 对应的字段及列数对象
* @return
*/
public static List<List<Object>> contentRowData(List<?> applyList, Class<?> clazz
, LinkedHashMap<String, Integer> fieldRowNumMap) throws NoSuchFieldException, IllegalAccessException {
List<List<Object>> list = new ArrayList<List<Object>>();
for (BusinessInternalDetailsExcelVO deviceDTO : detailsList) {
List<Object> rown = new ArrayList<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);
}
return list;
}
// >>>>>>>>>>>>>> 生成模板 >>>>>>>>>>>>>>>>>>>>>>>>>>>>
/**
* 工单确认单-生成模板
*/
public static void generateWorkOrderTemplate(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(new ArrayList<>(), writeSheet, writeTable2);
// 合计 table仅写入头
writeTableTotal.setHead(this.workOrderResultHead(orderApply.getVisitNum(), orderApply.getWorkOrderResult(), orderApply.getSinglePay(), orderApply.getTotalPay()));
excelWriter.write(new ArrayList<>(), writeSheet, writeTableTotal);
// 固定内容备注+签名行不创建头,在合计table之后写入数据
excelWriter.write(this.getFinalData("备注 ....."), writeSheet, writeTableFin);
//完成
excelWriter.finish();
} catch (Exception e) {
log.error("工单确认单模板生成,方法异常>>>>>>>>>>>>>>", e);
}
}
}
2)合并策略
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.*;
/**
* 功能描述:规则: 优先合并列,再合并行
*
* @author SXT
* @version 1.0
* @date 2024/3/9 15:12
*/
public class MergeCellStrategyHandler extends AbstractMergeStrategy {
/**
* 相同列合并
*/
private boolean alikeColumn;
/**
* 相同行合并
*/
private boolean alikeRow;
/** 开始进行合并的行index */
private int rowIndex;
/** 开始进行合并的行index */
private int rowIndexStart;
/** 跨行合并的列index */
private Set<Integer> columns;
private int currentRowIndex = 0;
/**
* 构造方法,指定合并方式
* @param alikeColumn
* @param alikeRow
* @param rowIndex
* @param columns
*/
public MergeCellStrategyHandler(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
*/
public MergeCellStrategyHandler(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
*/
public MergeCellStrategyHandler(boolean alikeColumn, int rowIndex){
this(alikeColumn, false, rowIndex, new HashSet<>());
}
/**
* 指定是否进行跨列合并单元格
* @param alikeColumn
* @param rowIndex
* @param rowIndexStart 开始行合并的行数
*/
public MergeCellStrategyHandler(boolean alikeColumn, int rowIndex, int rowIndexStart){
this(alikeColumn, false, rowIndex, new HashSet<>(), rowIndexStart);
}
/**
* 指定是否进行跨行合并单元格
* @param alikeRow
* @param rowIndex
* @param columns
*/
public MergeCellStrategyHandler(boolean alikeRow, int rowIndex, Set<Integer> columns){
this(false, alikeRow, rowIndex, columns);
}
@Override
protected void merge(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 = new CellRangeAddress(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 = new CellRangeAddress(currentRowIndex - 1, currentRowIndex, columnId, columnId);
rangeAddress = this.findExistAddress(sheet, rangeAddress, currentCellVal);
if (null != rangeAddress) {
sheet.addMergedRegion(rangeAddress);
}
}
}
}
}
}
/**
* 合并单元格地址范围,发现存在相同的地址则进行扩容合并
*
* @param sheet
* @param rangeAddress 单元格合并地址
* @param currentVal 当前单元格中的值
* @return
*/
private CellRangeAddress findExistAddress(Sheet sheet, CellRangeAddress rangeAddress, String currentVal) {
List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
int existIndex = 0;
Map<Integer, CellRangeAddress> existIdexMap = new LinkedHashMap<>();
if (null != mergedRegions && !mergedRegions.isEmpty()) {
//验证当前合并的单元格是否存在重复
for (CellRangeAddress mergedRegion : mergedRegions) {
if (mergedRegion.intersects(rangeAddress)) {
existIdexMap.put(existIndex, mergedRegion);
}
existIndex++;
}
}
if (existIdexMap.isEmpty()) {
return rangeAddress;
}
List<Integer> existIndexList = new ArrayList<>(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());
}
return rangeAddress;
}
/**
* 获取单元格中的内容
* @param cell
* @return
*/
private String 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));
}
return val;
}
}
3)单元格样式策略
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.util.StyleUtil;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.*;
import java.util.HashMap;
import java.util.List;
/**
* @Desc 拦截处理单元格创建
*/
public class CellStyleWriteHandler implements CellWriteHandler {
/**
* map
* key:第i行
* value:第i行中单元格索引集合
*/
private HashMap<Integer,List<Integer>> map;
/**
* 颜色
*/
private Short colorIndex;
/**
* 有参构造
* map:用来记录需要为第key行中的第value.get(i)列设置样式
* colorIndex:表示单元格需要设置的颜色
*/
public CellStyleWriteHandler(HashMap<Integer, List<Integer>> map, Short colorIndex) {
this.map = map;
this.colorIndex = colorIndex;
}
/**
* 无参构造
*/
public CellStyleWriteHandler() {
}
/**
* 在创建单元格之前调用
*/
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 在单元格创建后调用
*/
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 在单元上的所有操作完成后调用
* 指定单元格特殊处理 todo 待修改为指定样式
*/
@Override
public void afterCellDispose(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)) {
// 根据单元格获取workbook
Workbook workbook = cell.getSheet().getWorkbook();
//设置行高
writeSheetHolder.getSheet().getRow(cell.getRowIndex()).setHeight((short) (4.5 * 256));
// 单元格策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 设置背景颜色白色
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 = new WriteFont();
// 设置字体大小
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.模板导出
/**
* 导出工单表单模板
*/
public void exportWorkOrderTemplate(Long orderId) throws IOException {
/**:
* 根据表单id查询表单信息,回填模板表
*/
WorkOrderInfo workOrderInfo = workOrderInfoMapper.selectById(orderId);
if (workOrderInfo == null) {
throw new BizException("工单信息不存在");
}
/**
* 查询工单设备、服务申请情况
*/
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 = new ClassPathResource("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 = new HashSet<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));
HashMap<Integer, List<Integer>> map = new HashMap<>(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(new CellStyleWriteHandler(map, IndexedColors.RED.getIndex()))
// 行和列合并策略
.registerWriteHandler(new MergeCellStrategyHandler(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官方文档