POI创建Excel文件

文章目录

  • 1、背景
  • 2、创建表格
    • 2.1 定义表头对象
    • 2.2 Excel生成器
    • 2.3 创建模板
    • 2.4 处理Excel表头
    • 2.5 处理Excel内容单元格样式
    • 2.6 处理单个表头
  • 3、追加sheet
  • 4、静态工具
  • 5、单元测试
  • 6、完整代码示例

1、背景

需求中有需要用户自定义Excel表格表头,然后生成Excel文件,使用EasyExcel更适合生成固定表头的Excel文档,所以此处采用POI原生方式进行开发。文档如下:
在这里插入图片描述

2、创建表格

主要的代码逻辑如下,非主要方法可以在完整代码中找到。

2.1 定义表头对象

根据需求,表头需要制定2级表头,我们先定义一个Excel表头对象。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExcelModelDto {/*** 名称 */private String fieldName;/*** 提示语 */private String comment;/*** 类型 */private Integer type;/*** 背景色 */private short backgroundColor;/*** 子标题 */private List<Child> children;@Data@NoArgsConstructor@AllArgsConstructorpublic static class Child {/*** 字段编码 */private String fieldCode;/*** 字段名称 */private String fieldName;/*** 提示语 */private String comment;/*** 类型 */private Integer type;/*** 下拉框选项 */private String[] items;}}

2.2 Excel生成器

创建一个Excel文件生成对象,包含多个属性,其中包括:文件路径、文件名称、是否需要下拉框、文件后缀名、最大文本行数等。

@Slf4j
public class ExcelGenerator {private final String localPath;private final String sheetName;private final String fileName;private final String file;private final Boolean needItems;private final List<ExcelModelDto> data;/*** 字段编码集合,从data中解析 */private final List<String> fieldCodeList;public static final Integer FIRST_ROW = 2;public static final Integer LAST_ROW = 65535;public static final String FILE_SUFFIX = ".xlsx";public static final String PATH_SUFFIX = "/";public static final String ITEM_SHEET_NAME = "itemSheet";public static final String END_FLAG = "*";public static final Integer MAX_CONTENT_ROW_NUMBER = 1002;/*** 扩展字段sheet页行数记录key值*/public static final String EXTEND_PAGE_ROW_NUMBER_KEY = "extend";public ExcelGenerator(String localPath, String fileName, String sheetName, List<ExcelModelDto> data) {this(localPath, fileName, sheetName, true, data);}public ExcelGenerator(String localPath, String fileName, String sheetName, Boolean needItems, List<ExcelModelDto> data) {this.localPath = localPath;this.fileName = fileName;this.sheetName = sheetName;this.file = localPath + fileName;this.needItems = needItems;this.data = data;fieldCodeList = this.parseField(data);}
}

2.3 创建模板

/*** 生成模板** @throws IOException 异常*/public void createTemplate() throws IOException {this.doCreateSheet(Paths.get(file), sheetName, data);}/*** 向Excel文件新增一个新的工作表,并处理表头。** @param pathForFile 新工作表将要保存的文件路径。* @throws IOException 如果读写文件时发生异常。*/private void doCreateSheet(Path pathForFile, String sheetName, List<ExcelModelDto> data)throws IOException {long startTime = System.currentTimeMillis();Workbook workbook = new XSSFWorkbook();Sheet sheet = this.getSheetByName(workbook, sheetName, false);// 处理Excel表头this.dealExcelHeadingCell(workbook, sheet, data);// 处理Excel内容单元格,默认都是有二级标题this.dealExcelContentCell(workbook, sheet, data);// 将inputStream转换为outputStream,并重新写入文件try (OutputStream outputStream = Files.newOutputStream(pathForFile)) {workbook.write(outputStream);} finally {long endTime = System.currentTimeMillis();log.info("创建Excel模板文件共耗时:{}秒。", (endTime - startTime) / 1000);}}

2.4 处理Excel表头

/*** 处理 Excel 表头数据,包括第一行和第二行的标题单元格样式设置、数据填充和合并单元格。** @param workbook 工作簿对象* @param sheet    主表的工作表对象* @param data     表头数据*/private void dealExcelHeadingCell(Workbook workbook, Sheet sheet, List<ExcelModelDto> data) {// 创建第一行和第二行表头数据,并设置行高Row row1 = this.getRow(sheet, 0);Row row2 = this.getRow(sheet, 1);row1.setHeightInPoints(20);row2.setHeightInPoints(20);// 已经存在的列号int lastCellNum = this.getLastCellNum(sheet, 1);int currentCellNum = lastCellNum;int startCellNum = lastCellNum;int endCellNum;for (ExcelModelDto excelModelDto : data) {// 一级标题名称String firstTitleName = excelModelDto.getFieldName();// 一级标题单元格样式CellStyle firstTitleCellStyle = this.buildFirstTitleCellStyle(workbook, excelModelDto);// 二级标题的单元格样式CellStyle secondTitleCellStyle = this.getCellStyle(workbook, IndexedColors.WHITE.getIndex());List<ExcelModelDto.Child> children = excelModelDto.getChildren();if (children == null || children.size() == 0) {continue;}for (ExcelModelDto.Child child : children) {// 处理表头单元格this.dealTitleCell(workbook, sheet, child, firstTitleName, firstTitleCellStyle, secondTitleCellStyle, currentCellNum);// 处理完后列号加一currentCellNum++;}endCellNum = currentCellNum - 1;// POI 版本升级后,合并单元格需要大于一个单元格if (startCellNum != endCellNum) {CellRangeAddress region = new CellRangeAddress(0, 0, startCellNum, endCellNum);sheet.addMergedRegion(region);}startCellNum = endCellNum + 1;}}

2.5 处理Excel内容单元格样式

/*** 格式化内容单元格。** @param sheet    工作表对象。* @param workbook 工作簿对象。*/private void dealExcelContentCell(Workbook workbook, Sheet sheet, List<ExcelModelDto> data) {// 获取统一的单元格样式,不用每个单元格获取一个对象,防止对象过多CellStyle childCellStyle = this.getContentCellStyle(workbook);// 只格式化内容单元格,且有上限int maxContentRowNumber = MAX_CONTENT_ROW_NUMBER;// 跳过表头,从文本行开始for (int rowNumber = 2; rowNumber < maxContentRowNumber; rowNumber++) {Row row = sheet.createRow(rowNumber);// 列号从0开始int cellNumber = 0;for (ExcelModelDto excelModelDto : data) {List<ExcelModelDto.Child> children = excelModelDto.getChildren();for (ExcelModelDto.Child child : children) {String[] items = child.getItems();if (Objects.isNull(items) || items.length == 0) {Cell cell = row.createCell(cellNumber);cell.setCellStyle(childCellStyle);}// 每处理完一个单元格,列号加1cellNumber++;}}}}

2.6 处理单个表头

在处理表头过程中,如果items 不为空,则说明此列需要下拉框,数组为供用户选择的下拉内容,防止下拉框内容过大,所以将下拉内容单独生成到一个隐藏的sheet页中,并且使用表达式来表达下拉框内容,设定到单元格中。

/*** 处理Excel表格的标题单元格。** @param workbook             工作簿对象* @param sheet                工作表对象* @param child                ExcelModelDto.Child 对象,包含字段名、注释和下拉框选项等信息* @param firstTitleName       一级标题名称* @param firstTitleCellStyle  一级标题单元格样式* @param secondTitleCellStyle 二级标题单元格样式* @param index                当前处理的列索引*/private void dealTitleCell(Workbook workbook, Sheet sheet,ExcelModelDto.Child child, String firstTitleName,CellStyle firstTitleCellStyle, CellStyle secondTitleCellStyle,int index) {Row row1 = this.getRow(sheet, 0);Row row2 = this.getRow(sheet, 1);String secondFieldName = child.getFieldName();String comment = child.getComment();String[] items = child.getItems();// 一级表头Cell cell1 = row1.createCell(index);cell1.setCellValue(firstTitleName);cell1.setCellStyle(firstTitleCellStyle);// 二级表头,标题如果以* 号结尾,则* 置为红色Cell cell2 = row2.createCell(index);RichTextString textString = this.parseCellValue(workbook, Font.COLOR_NORMAL, true, secondFieldName);cell2.setCellValue(textString);cell2.setCellStyle(secondTitleCellStyle);// 设置下拉框if (items != null && items.length > 0 && needItems) {this.appendItems(workbook, sheet, secondTitleCellStyle, secondFieldName, items, index);}// 设置表头备注if (!org.apache.commons.lang.StringUtils.isEmpty(comment)) {this.setComment(sheet, cell2, comment);}// 根据字段长度自动调整列的宽度sheet.setColumnWidth(index, 100 * 50);}/*** 在指定的工作簿和工作表中追加枚举类型的项,并设置公式引用。** @param workbook        工作簿对象* @param sheet           工作表对象* @param childCellStyle  子单元格样式* @param secondTitleName 第二级标题名称* @param items           枚举类型的项数组* @param index           当前项在总体中的索引位置*/private void appendItems(Workbook workbook, Sheet sheet, CellStyle childCellStyle, String secondTitleName, String[] items, int index) {// 如果有序列单元格,则创建一个sheet页,来保存所有的枚举类型,同时隐藏该sheet页Sheet itemsSheet = this.getSheetByName(workbook, ITEM_SHEET_NAME, true);// 追加sheet的时候,需要看隐藏sheet的列已经到哪一列了,避免追加时将原有隐藏列覆盖掉int existItemCell = this.getLastCellNum(itemsSheet, 0);// 将枚举数组写入到独立的sheet页中,同时设置表头格式String formula = this.writeItems(itemsSheet, childCellStyle, secondTitleName, existItemCell, items);// 设置公式到模板的sheet页中,格式化后的最终公式为// =itemSheet!$B$1:$B$88// 表明该单元格引用的是 itemSheet sheet页中 B1~B88的数据formula = String.format("=%s!%s", ITEM_SHEET_NAME, formula);this.setItems(sheet, formula, FIRST_ROW, LAST_ROW, index, index);}

3、追加sheet

有些需要在已有的Excel文档中追加新的sheet表格内容,效果如下:

在这里插入图片描述

/*** 在指定的 Excel 文件中添加一个新的工作表,并填充数据。** @param sheetName 新工作表的名称* @param data      要填充的数据列表* @throws IOException 如果在操作文件时发生了 I/O 错误*/public void appendSheet(String sheetName, List<ExcelModelDto> data) throws IOException {long startTime = System.currentTimeMillis();// 路径不存在则创建,保证路径是存在的Path pathForLocalPath = Paths.get(localPath);boolean existPath = Files.exists(pathForLocalPath);if (!existPath) {Files.createDirectories(pathForLocalPath);}// 如果文件不存在,则走创建sheet逻辑Path pathForFile = Paths.get(file);if (!Files.exists(pathForFile)) {this.doCreateSheet(pathForFile, sheetName, data);return;}// 如果文件存在则走追加sheet逻辑try (InputStream inputStream = Files.newInputStream(pathForFile)) {this.doAppendSheet(inputStream, pathForFile, sheetName, data);long endTime = System.currentTimeMillis();log.info("追加Excel模板文件共耗时:{}秒。", (endTime - startTime) / 1000);} catch (Exception e) {log.error("追加Excel模板文件失败!", e);throw new BizException(e);}}/*** 向Excel文件追加一个新的工作表,并处理表头。** @param inputStream Excel文件的输入流。* @param pathForFile 新工作表将要保存的文件路径。* @throws IOException 如果读写文件时发生异常。*/private void doAppendSheet(InputStream inputStream, Path pathForFile, String sheetName, List<ExcelModelDto> data)throws IOException {Workbook workbook = new XSSFWorkbook(inputStream);Sheet sheet = this.getSheetByName(workbook, sheetName, false);// 处理Excel表头this.dealExcelHeadingCell(workbook, sheet, data);// 处理Excel内容单元格,默认都是有二级标题this.dealExcelContentCell(workbook, sheet, data);// 将inputStream转换为outputStream,并重新写入文件try (OutputStream outputStream = Files.newOutputStream(pathForFile)) {IOUtils.copy(inputStream, outputStream);workbook.write(outputStream);}}

4、静态工具

每次使用都需要new一个对象来创建Excel文件,所以创建一个静态工具类,来通过静态方法实现文档的创建与追加。

public class ExcelGeneratorExecutors {/*** 创建 Excel 模板文件。** @param localPath 本地路径* @param fileName  文件名* @param sheetName 工作表名称* @param data      数据列表* @throws IOException 如果创建模板文件失败*/public static void createTemplate(String localPath, String fileName, String sheetName, List<ExcelModelDto> data) throws IOException {ExcelGenerator excelGenerator = new ExcelGenerator(localPath, fileName, sheetName, data);excelGenerator.createTemplate();}/*** 在指定路径的Excel文件中追加一个新的工作表,并填充数据。** @param localPath Excel文件的本地路径。* @param fileName  Excel文件的名称。* @param sheetName 新增工作表的名称。* @param data      填充到新增工作表的数据。* @throws IOException 如果在追加工作表或填充数据时发生I/O错误。*/public static void appendSheet(String localPath, String fileName, String sheetName, List<ExcelModelDto> data) throws IOException {ExcelGenerator excelGenerator = new ExcelGenerator(localPath, fileName, sheetName, data);excelGenerator.appendSheet(sheetName, data);}}

5、单元测试

@Testpublic void testGenerate() {String localPath = "D:\\mytmp\\template\\";String dateTime = DateUtils.format(new Date(), DateUtils.DATE_FORMAT_COMMENT_2);String fileName = String.format("生成模板-%s.xlsx", dateTime);String sheetName = "测试";List<ExcelModelDto> data = this.buildExcelModelDtoList();ExcelGenerator excelGenerator = new ExcelGenerator(localPath, fileName, sheetName, data);try {excelGenerator.createTemplate();List<ExcelModelDto> data2 = this.buildExcelModelDtoList2();excelGenerator.appendSheet("自定义sheet", data);excelGenerator.appendSheet("自定义sheet2", data2);excelGenerator.appendSheet("自定义sheet3", data2);log.info("模板文件生成,名称为:{}", fileName);} catch (IOException e) {e.printStackTrace();}}@Testpublic void testGenerate2() {String localPath = "D:\\mytmp\\template\\";String dateTime = DateUtils.format(new Date(), DateUtils.DATE_FORMAT_COMMENT_2);String fileName = String.format("生成模板-%s.xlsx", dateTime);String sheetName = "测试";List<ExcelModelDto> data = this.buildExcelModelDtoList();try {ExcelGeneratorExecutors.createTemplate(localPath, fileName, sheetName, data);ExcelGeneratorExecutors.appendSheet(localPath, fileName, sheetName, data);ExcelGeneratorExecutors.appendSheet(localPath, fileName, "自定义sheet3", data);log.info("模板文件生成,名称为:{}", fileName);} catch (IOException e) {e.printStackTrace();}}public List<ExcelModelDto> buildExcelModelDtoList() {List<ExcelModelDto> data = new ArrayList<>();ExcelModelDto excelModelDto = new ExcelModelDto();excelModelDto.setFieldName("电器");excelModelDto.setComment("song");excelModelDto.setType(2);excelModelDto.setBackgroundColor((short) 2);List<ExcelModelDto.Child> children = new ArrayList<>();ExcelModelDto.Child child1 = new ExcelModelDto.Child();child1.setComment("类目1");child1.setFieldCode("category");child1.setFieldName("类目1");List<String> list1 = Lists.newArrayList("冰箱", "洗衣机", "空调");child1.setItems(list1.toArray(new String[0]));ExcelModelDto.Child child2 = new ExcelModelDto.Child();child2.setComment("数量1");child2.setFieldCode("qty");child2.setFieldName("数量1");List<String> list2 = Lists.newArrayList("1", "2", "3");child2.setItems(list2.toArray(new String[0]));ExcelModelDto.Child child3 = new ExcelModelDto.Child();child3.setComment("文本内容");child3.setFieldCode("textValue");child3.setFieldName("文本内容");children.add(child1);children.add(child2);children.add(child3);excelModelDto.setChildren(children);data.add(excelModelDto);return data;}public List<ExcelModelDto> buildExcelModelDtoList2() {List<ExcelModelDto> data = new ArrayList<>();ExcelModelDto excelModelDto0 = new ExcelModelDto();excelModelDto0.setFieldName("商家运单号");excelModelDto0.setComment("商家运单号");excelModelDto0.setType((int) IndexedColors.TURQUOISE1.getIndex());excelModelDto0.setBackgroundColor(IndexedColors.TURQUOISE1.getIndex());ExcelModelDto.Child child0 = new ExcelModelDto.Child();child0.setComment("关联第一个sheet页的商家运单号");child0.setFieldCode("orderNo");child0.setFieldName("商家运单号*");List<ExcelModelDto.Child> children0 = new ArrayList<>();children0.add(child0);excelModelDto0.setChildren(children0);ExcelModelDto excelModelDto = new ExcelModelDto();excelModelDto.setFieldName("购买电器");excelModelDto.setComment("song");excelModelDto.setType((int) IndexedColors.TURQUOISE1.getIndex());excelModelDto.setBackgroundColor(IndexedColors.TURQUOISE1.getIndex());ExcelModelDto.Child child1 = new ExcelModelDto.Child();child1.setComment("类目");child1.setFieldCode("category");child1.setFieldName("类目");List<String> list1 = Lists.newArrayList("冰箱", "洗衣机", "空调");child1.setItems(list1.toArray(new String[0]));ExcelModelDto.Child child2 = new ExcelModelDto.Child();child2.setComment("数量");child2.setFieldCode("qty");child2.setFieldName("数量");//List<String> list2 = Lists.newArrayList("1", "2", "3");//child2.setItems(list2.toArray(new String[0]));List<ExcelModelDto.Child> children = new ArrayList<>();children.add(child1);children.add(child2);excelModelDto.setChildren(children);data.add(excelModelDto0);data.add(excelModelDto);return data;}

6、完整代码示例

@Slf4j
public class ExcelGenerator {private final String localPath;private final String sheetName;private final String fileName;private final String file;private final Boolean needItems;private final List<ExcelModelDto> data;/*** 字段编码集合,从data中解析 */private final List<String> fieldCodeList;public static final Integer FIRST_ROW = 2;public static final Integer LAST_ROW = 65535;public static final String FILE_SUFFIX = ".xlsx";public static final String PATH_SUFFIX = "/";public static final String ITEM_SHEET_NAME = "itemSheet";public static final String END_FLAG = "*";public static final Integer MAX_CONTENT_ROW_NUMBER = 1002;/*** 扩展字段sheet页行数记录key值*/public static final String EXTEND_PAGE_ROW_NUMBER_KEY = "extend";public ExcelGenerator(String localPath, String fileName, String sheetName, List<ExcelModelDto> data) {this(localPath, fileName, sheetName, true, data);}public ExcelGenerator(String localPath, String fileName, String sheetName, Boolean needItems, List<ExcelModelDto> data) {this.localPath = localPath;this.fileName = fileName;this.sheetName = sheetName;this.file = localPath + fileName;this.needItems = needItems;this.data = data;fieldCodeList = this.parseField(data);}/*** 创建对象时,将ExcelModel中的字段按顺序排好,保存到List中** @param data 入参* @return 返回值*/public List<String> parseField(List<ExcelModelDto> data) {List<String> fieldCodeList = new ArrayList<>();for (ExcelModelDto modelDto : data) {List<ExcelModelDto.Child> children = modelDto.getChildren();for (ExcelModelDto.Child child : children) {String fieldCode = child.getFieldCode();fieldCodeList.add(fieldCode);}}return fieldCodeList;}/*** 生成模板** @throws IOException 异常*/public void createTemplate() throws IOException {this.doCreateSheet(Paths.get(file), sheetName, data);}/*** 在指定的 Excel 文件中添加一个新的工作表,并填充数据。** @param sheetName 新工作表的名称* @param data      要填充的数据列表* @throws IOException 如果在操作文件时发生了 I/O 错误*/public void appendSheet(String sheetName, List<ExcelModelDto> data) throws IOException {long startTime = System.currentTimeMillis();// 路径不存在则创建,保证路径是存在的Path pathForLocalPath = Paths.get(localPath);boolean existPath = Files.exists(pathForLocalPath);if (!existPath) {Files.createDirectories(pathForLocalPath);}// 如果文件不存在,则走创建sheet逻辑Path pathForFile = Paths.get(file);if (!Files.exists(pathForFile)) {this.doCreateSheet(pathForFile, sheetName, data);return;}// 如果文件存在则走追加sheet逻辑try (InputStream inputStream = Files.newInputStream(pathForFile)) {this.doAppendSheet(inputStream, pathForFile, sheetName, data);long endTime = System.currentTimeMillis();log.info("追加Excel模板文件共耗时:{}秒。", (endTime - startTime) / 1000);} catch (Exception e) {log.error("追加Excel模板文件失败!", e);throw new BizException(e);}}/*** 向Excel文件新增一个新的工作表,并处理表头。** @param pathForFile 新工作表将要保存的文件路径。* @throws IOException 如果读写文件时发生异常。*/private void doCreateSheet(Path pathForFile, String sheetName, List<ExcelModelDto> data)throws IOException {long startTime = System.currentTimeMillis();Workbook workbook = new XSSFWorkbook();Sheet sheet = this.getSheetByName(workbook, sheetName, false);// 处理Excel表头this.dealExcelHeadingCell(workbook, sheet, data);// 处理Excel内容单元格,默认都是有二级标题this.dealExcelContentCell(workbook, sheet, data);// 将inputStream转换为outputStream,并重新写入文件try (OutputStream outputStream = Files.newOutputStream(pathForFile)) {workbook.write(outputStream);} finally {long endTime = System.currentTimeMillis();log.info("创建Excel模板文件共耗时:{}秒。", (endTime - startTime) / 1000);}}/*** 向Excel文件追加一个新的工作表,并处理表头。** @param inputStream Excel文件的输入流。* @param pathForFile 新工作表将要保存的文件路径。* @throws IOException 如果读写文件时发生异常。*/private void doAppendSheet(InputStream inputStream, Path pathForFile, String sheetName, List<ExcelModelDto> data)throws IOException {Workbook workbook = new XSSFWorkbook(inputStream);Sheet sheet = this.getSheetByName(workbook, sheetName, false);// 处理Excel表头this.dealExcelHeadingCell(workbook, sheet, data);// 处理Excel内容单元格,默认都是有二级标题this.dealExcelContentCell(workbook, sheet, data);// 将inputStream转换为outputStream,并重新写入文件try (OutputStream outputStream = Files.newOutputStream(pathForFile)) {IOUtils.copy(inputStream, outputStream);workbook.write(outputStream);}}/*** 处理 Excel 表头数据,包括第一行和第二行的标题单元格样式设置、数据填充和合并单元格。** @param workbook 工作簿对象* @param sheet    主表的工作表对象* @param data     表头数据*/private void dealExcelHeadingCell(Workbook workbook, Sheet sheet, List<ExcelModelDto> data) {// 创建第一行和第二行表头数据,并设置行高Row row1 = this.getRow(sheet, 0);Row row2 = this.getRow(sheet, 1);row1.setHeightInPoints(20);row2.setHeightInPoints(20);// 已经存在的列号int lastCellNum = this.getLastCellNum(sheet, 1);int currentCellNum = lastCellNum;int startCellNum = lastCellNum;int endCellNum;for (ExcelModelDto excelModelDto : data) {// 一级标题名称String firstTitleName = excelModelDto.getFieldName();// 一级标题单元格样式CellStyle firstTitleCellStyle = this.buildFirstTitleCellStyle(workbook, excelModelDto);// 二级标题的单元格样式CellStyle secondTitleCellStyle = this.getCellStyle(workbook, IndexedColors.WHITE.getIndex());List<ExcelModelDto.Child> children = excelModelDto.getChildren();if (children == null || children.size() == 0) {continue;}for (ExcelModelDto.Child child : children) {// 处理表头单元格this.dealTitleCell(workbook, sheet, child, firstTitleName, firstTitleCellStyle, secondTitleCellStyle, currentCellNum);// 处理完后列号加一currentCellNum++;}endCellNum = currentCellNum - 1;// POI 版本升级后,合并单元格需要大于一个单元格if (startCellNum != endCellNum) {CellRangeAddress region = new CellRangeAddress(0, 0, startCellNum, endCellNum);sheet.addMergedRegion(region);}startCellNum = endCellNum + 1;}}/*** 格式化内容单元格。** @param sheet    工作表对象。* @param workbook 工作簿对象。*/private void dealExcelContentCell(Workbook workbook, Sheet sheet, List<ExcelModelDto> data) {// 获取统一的单元格样式,不用每个单元格获取一个对象,防止对象过多CellStyle childCellStyle = this.getContentCellStyle(workbook);// 只格式化内容单元格,且有上限int maxContentRowNumber = MAX_CONTENT_ROW_NUMBER;// 跳过表头,从文本行开始for (int rowNumber = 2; rowNumber < maxContentRowNumber; rowNumber++) {Row row = sheet.createRow(rowNumber);// 列号从0开始int cellNumber = 0;for (ExcelModelDto excelModelDto : data) {List<ExcelModelDto.Child> children = excelModelDto.getChildren();for (ExcelModelDto.Child child : children) {String[] items = child.getItems();if (Objects.isNull(items) || items.length == 0) {Cell cell = row.createCell(cellNumber);cell.setCellStyle(childCellStyle);}// 每处理完一个单元格,列号加1cellNumber++;}}}}/*** 处理Excel表格的标题单元格。** @param workbook             工作簿对象* @param sheet                工作表对象* @param child                ExcelModelDto.Child 对象,包含字段名、注释和下拉框选项等信息* @param firstTitleName       一级标题名称* @param firstTitleCellStyle  一级标题单元格样式* @param secondTitleCellStyle 二级标题单元格样式* @param index                当前处理的列索引*/private void dealTitleCell(Workbook workbook, Sheet sheet,ExcelModelDto.Child child, String firstTitleName,CellStyle firstTitleCellStyle, CellStyle secondTitleCellStyle,int index) {Row row1 = this.getRow(sheet, 0);Row row2 = this.getRow(sheet, 1);String secondFieldName = child.getFieldName();String comment = child.getComment();String[] items = child.getItems();// 一级表头Cell cell1 = row1.createCell(index);cell1.setCellValue(firstTitleName);cell1.setCellStyle(firstTitleCellStyle);// 二级表头,标题如果以* 号结尾,则* 置为红色Cell cell2 = row2.createCell(index);RichTextString textString = this.parseCellValue(workbook, Font.COLOR_NORMAL, true, secondFieldName);cell2.setCellValue(textString);cell2.setCellStyle(secondTitleCellStyle);// 设置下拉框if (items != null && items.length > 0 && needItems) {this.appendItems(workbook, sheet, secondTitleCellStyle, secondFieldName, items, index);}// 设置表头备注if (!org.apache.commons.lang.StringUtils.isEmpty(comment)) {this.setComment(sheet, cell2, comment);}// 根据字段长度自动调整列的宽度sheet.setColumnWidth(index, 100 * 50);}/*** 设置单元格下拉框* 下拉框引用单独一个sheet页中的数据** @param sheet    sheet* @param formula  公式* @param firstRow 起始行* @param lastRow  结束行* @param firstCol 起始列* @param lastCol  结束列*/public void setItems(Sheet sheet, String formula, int firstRow, int lastRow, int firstCol, int lastCol) {CellRangeAddressList addressList = new CellRangeAddressList(firstRow, lastRow, firstCol, lastCol);DataValidationHelper helper = sheet.getDataValidationHelper();DataValidationConstraint constraint = helper.createFormulaListConstraint(formula);DataValidation validation = helper.createValidation(constraint, addressList);validation.setShowErrorBox(true);sheet.addValidationData(validation);}/*** 设置单元格备注信息** @param sheet      sheet* @param cell       单元格* @param textString 提示信息*/public void setComment(Sheet sheet, Cell cell, String textString) {Drawing<?> drawing = sheet.createDrawingPatriarch();CreationHelper factory = sheet.getWorkbook().getCreationHelper();// 设置提示框大小,默认根据 提示信息的大小来确认提示框高度/// ClientAnchor anchor = factory.createClientAnchor();textString = StringUtils.defaultIfBlank(textString, "");int length = textString.length();int row2 = length / 25 + 6;// (int dx1, int dy1, int dx2, int dy2, short col1, int row1, short col2, int row2)// 前四个参数是坐标点,后四个参数是编辑和显示批注时的大小.ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) 4, 2, (short) 6, row2);Comment comment = drawing.createCellComment(anchor);RichTextString str = factory.createRichTextString(textString);comment.setString(str);comment.setAuthor("Auto+");// 以上参数不设置时会有默认值,当一个被重复设置批注时会报错// Multiple cell comments in one cell are not allowed// 故在设置批注前检查锚点位置有无批注,有的话移除if (cell.getCellComment() != null) {cell.removeCellComment();}cell.setCellComment(comment);}/*** 获取单元格样式对象** @param workbook        工作簿* @param backGroundColor 背景色* @return 返回样式对象*/public CellStyle getCellStyle(Workbook workbook, short backGroundColor) {CellStyle cellStyle = workbook.createCellStyle();CreationHelper createHelper = workbook.getCreationHelper();cellStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd HH:mm:ss"));// IndexedColors.YELLOW.getIndex()cellStyle.setFillForegroundColor(backGroundColor);cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);// 水平居中cellStyle.setAlignment(HorizontalAlignment.CENTER);// 垂直居中cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 设置边框及颜色cellStyle.setBorderTop(BorderStyle.DOUBLE);cellStyle.setBorderBottom(BorderStyle.DOUBLE);cellStyle.setBorderLeft(BorderStyle.DOUBLE);cellStyle.setBorderRight(BorderStyle.DOUBLE);cellStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());cellStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());cellStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());return cellStyle;}/*** 向sheet中写入 序列内容** @param sheet     sheet* @param cellStyle 单元格格式,表头格式* @param itemsType 序列类型* @param col       列号* @param items     序列数组* @return 返回坐标*/protected String writeItems(Sheet sheet, CellStyle cellStyle, String itemsType, int col, String[] items) {// 第一行为表头数据Row row = sheet.getRow(0);if (row == null) {row = sheet.createRow(0);}Cell cell = row.createCell(col);// 获取单元格列所对应的字母,即 0=A,1=B ...String columnLetter = CellReference.convertNumToColString(col);cell.setCellValue(itemsType);cell.setCellStyle(cellStyle);int length = items.length;for (int i = 0; i < length; i++) {Row itemRow = sheet.getRow(i + 1);if (itemRow == null) {itemRow = sheet.createRow(i + 1);}Cell itemRowCell = itemRow.createCell(col);itemRowCell.setCellValue(items[i]);}// 格式化后的公式坐标为  $B$1:$B$88return String.format("$%s$%s:$%s$%s", columnLetter, 2, columnLetter, items.length + 1);}/*** 格式化单元格字体样式** @param workbook  工作簿* @param fontColor 字体颜色* @param isBold    是否加粗* @param value     单元格值*/public RichTextString parseCellValue(Workbook workbook, short fontColor, boolean isBold, String value) {value = StringUtils.defaultIfBlank(value, "");XSSFRichTextString textString = new XSSFRichTextString(value);Font font1 = getFontStyle(workbook, fontColor, isBold);if (StringUtils.isNotBlank(value)) {int length = value.length();// 如果内容是以 * 号结尾的,则将 * 号置为红色,默认黑色if (value.endsWith(END_FLAG)) {int point = length - 1;textString.applyFont(0, point, font1);Font font2 = getFontStyle(workbook, Font.COLOR_RED, isBold);textString.applyFont(point, length, font2);} else {textString.applyFont(0, length, font1);}}return textString;}/*** 获取字体样式** @param workbook  工作簿* @param fontColor 字体颜色* @param isBold    是否加粗* @return 返回值*/public Font getFontStyle(Workbook workbook, short fontColor, boolean isBold) {Font font = workbook.createFont();font.setColor(fontColor);if (isBold) {font.setBold(true);}font.setFontName("宋体");// 字体大小font.setFontHeightInPoints((short) 10);return font;}/*** 获取指定行在给定工作表中的最后一个单元格的索引。** @param sheet  工作表对象* @param rowNum 行号(从0开始计数)* @return 最后一个单元格的索引,若行不存在则返回0*/private int getLastCellNum(Sheet sheet, int rowNum) {int existCell = 0;// 指定sheet页不为空,则获取已经有多少列Row row = sheet.getRow(rowNum);if (Objects.nonNull(row)) {existCell = row.getLastCellNum();// 如果不存在返回的是-1,业务上从0开始计算if (existCell < 0) {existCell = 0;}}return existCell;}/*** 获取或创建指定名称的工作表并将其隐藏。** @param workbook 工作簿对象* @return 指定名称的工作表对象*/private Sheet getSheetByName(Workbook workbook, String sheetName, boolean hide) {Sheet itemsSheet = workbook.getSheet(sheetName);// 指定sheet页为空则创建if (Objects.isNull(itemsSheet)) {itemsSheet = workbook.createSheet(sheetName);int sheetIndex = workbook.getSheetIndex(sheetName);workbook.setSheetHidden(sheetIndex, hide);}return itemsSheet;}/*** 根据行号获取或创建指定Sheet中的Row对象。** @param sheet  要操作的Sheet对象。* @param rowNum 需要获取或创建的行号。* @return 指定行号的Row对象。*/private Row getRow(Sheet sheet, int rowNum) {Row row = sheet.getRow(rowNum);if (Objects.isNull(row)) {row = sheet.createRow(rowNum);}return row;}/*** 构建第一行标题单元格样式。** @param workbook      工作簿对象。* @param excelModelDto Excel模型数据传输对象。* @return 第一行标题单元格样式。*/private CellStyle buildFirstTitleCellStyle(Workbook workbook, ExcelModelDto excelModelDto) {// 根据字段类型来获取背景色short backGroundColor = excelModelDto.getBackgroundColor();CellStyle cellStyle = this.getCellStyle(workbook, backGroundColor);Font font = this.getFontStyle(workbook, Font.COLOR_NORMAL, true);cellStyle.setFont(font);return cellStyle;}/*** 在指定的工作簿和工作表中追加枚举类型的项,并设置公式引用。** @param workbook        工作簿对象* @param sheet           工作表对象* @param childCellStyle  子单元格样式* @param secondTitleName 第二级标题名称* @param items           枚举类型的项数组* @param index           当前项在总体中的索引位置*/private void appendItems(Workbook workbook, Sheet sheet, CellStyle childCellStyle, String secondTitleName, String[] items, int index) {// 如果有序列单元格,则创建一个sheet页,来保存所有的枚举类型,同时隐藏该sheet页Sheet itemsSheet = this.getSheetByName(workbook, ITEM_SHEET_NAME, true);// 追加sheet的时候,需要看隐藏sheet的列已经到哪一列了,避免追加时将原有隐藏列覆盖掉int existItemCell = this.getLastCellNum(itemsSheet, 0);// 将枚举数组写入到独立的sheet页中,同时设置表头格式String formula = this.writeItems(itemsSheet, childCellStyle, secondTitleName, existItemCell, items);// 设置公式到模板的sheet页中,格式化后的最终公式为// =itemSheet!$B$1:$B$88// 表明该单元格引用的是 itemSheet sheet页中 B1~B88的数据formula = String.format("=%s!%s", ITEM_SHEET_NAME, formula);this.setItems(sheet, formula, FIRST_ROW, LAST_ROW, index, index);}/*** 获取单元格样式对象** @param workbook 工作簿* @return 返回样式对象*/public CellStyle getContentCellStyle(Workbook workbook) {CellStyle cellStyle = workbook.createCellStyle();CreationHelper createHelper = workbook.getCreationHelper();cellStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd HH:mm:ss"));// 背景色为纯色cellStyle.setFillPattern(FillPatternType.NO_FILL);// 设置单元格格式为文本格式DataFormat format = workbook.createDataFormat();cellStyle.setDataFormat(format.getFormat("@"));return cellStyle;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/79946.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【分布式系统中的“瑞士军刀”_ Zookeeper】三、Zookeeper 在实际项目中的应用场景与案例分析

在分布式系统日益复杂的当下&#xff0c;Zookeeper 凭借强大的协调能力成为众多项目的关键组件。本篇文章将结合实际项目场景&#xff0c;详细介绍 Zookeeper 在电商秒杀、微服务架构、分布式配置管理以及大数据处理集群等领域的应用&#xff0c;以及在不同的案例场景下的具体分…

【翻译、转载】MCP 提示 (Prompts)

原文地址&#xff1a;https://modelcontextprotocol.io/docs/concepts/prompts#python 提示 (Prompts) 创建可重用的提示模板和工作流 提示 (Prompts) 使服务器能够定义可重用的提示模板和工作流&#xff0c;客户端可以轻松地将其呈现给用户和 LLM。它们提供了一种强大的方式来…

accept() reject() hide()

1. accept() 用途 确认操作&#xff1a;表示用户完成了对话框的交互并确认了操作&#xff08;如点击“确定”按钮&#xff09;。 关闭模态对话框&#xff1a;结束 exec() 的事件循环&#xff0c;返回 QDialog::Accepted 结果码。适用场景 模态对话框&#xff08;通过 exec()…

如何查看电脑IP地址和归属地:全面指南

在数字化时代&#xff0c;了解自己电脑的IP地址和归属地信息变得越来越重要。无论是进行网络故障排查、远程办公设置&#xff0c;还是出于网络安全考虑&#xff0c;掌握这些基本信息都很有必要。本文将详细介绍如何查看电脑的公网IP、内网IP以及归属地信息&#xff0c;并提供常…

基于python生成taskc语言文件--时间片轮询

目录 前言 utf-8 chinese GB2312 utf-8 排除task.c chinese GB2312 排除task.c 运行结果 前言 建议是把能正常工作的单个功能函数放到一起&#xff08;就和放while函数里的程序一样&#xff09;&#xff0c;程序会按顺序自动配置。 不同的格式已经对应给出。 utf-8 impo…

Docker手动重构Nginx镜像,融入Lua、Redis功能

核心内容&#xff1a;Docker重构Nginx镜像&#xff0c;融入Lua、Redis功能 文章目录 前言一、准备工作1、说明2、下载模块3、Nginx配置文件3、Dockerfile配置文件3、准备工作全部结束 二、构建镜像三、基于镜像创建容器三、lua脚本的redis功能使用总结 前言 ⁣⁣⁣⁣ ⁣⁣⁣⁣…

DeepSeek+Excel:解锁办公效率新高度

目录 一、引言&#xff1a;Excel 遇上 DeepSeek二、认识 DeepSeek&#xff1a;大模型中的得力助手2.1 DeepSeek 的技术架构与原理2.2 DeepSeek 在办公场景中的独特优势 三、DeepSeek 与 Excel 结合的准备工作3.1 获取 DeepSeek API Key3.2 配置 Excel 环境 四、DeepSeekExcel 实…

解决Vue2------You may use special comments to disable some warnings.问题

问题截图 解决办法 打开项目中.eslintrc.js在rules中&#xff0c;添加以下代码&#xff0c;并extends的 vue/standard注释掉 space-before-function-paren: 0, semi: off, quotes : off, comma-dangle : off, vue/comment-directive: off

数据集-目标检测系列- 牙刷 检测数据集 toothbrush >> DataBall

数据集-目标检测系列- 牙刷 检测数据集 toothbrush >> DataBall DataBall 助力快速掌握数据集的信息和使用方式。 贵在坚持&#xff01; * 相关项目 1&#xff09;数据集可视化项目&#xff1a;gitcode: https://gitcode.com/DataBall/DataBall-detections-100s/over…

解决:前后端跨域请求

目录 关于跨域请求出现的原因 同源策略 示例&#xff08;跨域问题&#xff09; 如何解决跨域请求 方法一&#xff1a;配置后端服务器以允许跨域请求&#xff08;后端&#xff09; 方法二&#xff1a;使用代理服务器&#xff08;前端&#xff09; 一 &#xff0c;使用aja…

AI内容检测的技术优势与应用场景

随着互联网的普及和数字内容的爆发式增长&#xff0c;文本、图片、音频、视频等多样化内容已成为信息传播的主要载体。然而&#xff0c;伴随内容增长的是违法违规信息的泛滥&#xff0c;如涉黄、涉政、虚假广告、恶意引流等&#xff0c;不仅威胁用户体验&#xff0c;还对平台合…

DockerDesktop替换方案

背景 由于DockerDesktop并非开源软件&#xff0c;如果在公司使用&#xff0c;可能就有一些限制&#xff0c;那是不是除了使用DockerDesktop外&#xff0c;就没其它办法了呢&#xff0c;现在咱们来说说替换方案。 WSL WSL是什么&#xff0c;可自行百度&#xff0c;这里引用WS…

『Linux_网络』 基于状态机的Connect断线重连

客户端会面临服务器崩溃的情况&#xff0c; 我们可以试着写一个客户端重连的代码&#xff0c; 模拟并理 解一些客户端行为&#xff0c; 比如游戏客户端等。 客户端部分&#xff0c;我们本次采用状态机的设计模式实现 下面是关于状态机模式的介绍 状态机模式 状态机模式&…

5月6日日记

一点心得是 看通知要仔细认真&#xff0c;自己想问的问题要先看看通知或者文件中说了没有&#xff0c;如果没说再去问相关负责人。 上课的教室一定要看好&#xff0c;看准了再去。别像今天一样先去了科技楼又去了工学馆。 线代开课了。感觉总体还行&#xff0c;并不是很难。…

【算法专题十】哈希表

文章目录 0.哈希表简介1. 两数之和1.1 题目1.2 思路1.3 代码 2.判断是否为字符重排2.1 题目2.2 思路2.3 代码 3. leetcode.217.存在重复元素3.1 题目3.2 思路3.3 代码 4. leetcode.219.存在重复的元素Ⅱ4.1 题目4.2 思路4.3 代码 5. leetcode.49.字母异位词分组5.1 题目5.2 思路…

【前缀和】矩阵区域和

文章目录 1314. 矩阵区域和解题思路1314. 矩阵区域和 1314. 矩阵区域和 ​ 给你一个 m x n 的矩阵 mat 和一个整数 k ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和: i - k <= r <= i + k, j - k <= c <= j + k …

MyBatis的SQL映射文件中,`#`和`$`符号的区别

在MyBatis的SQL映射文件中,#和$符号用于处理SQL语句中的参数替换,但它们的工作方式和使用场景有所不同。 #{} 符号 预编译参数:#{} 被用来作为预编译SQL语句的占位符。这意味着MyBatis会将你传入的参数设置为PreparedStatement的参数,从而防止SQL注入攻击,并允许MyBatis对…

Linux中为某个进程临时指定tmp目录

起因&#xff1a; 在linux下编译k8s&#xff0c;由于编译的中间文件太多而系统的/tmp分区设置太小&#xff0c;导致编译失败&#xff0c;但自己不想或不能更改/tmp分区大小&#xff0c;所以只能通过其他方式解决。 现象&#xff1a; tmp分区大小&#xff1a; 解决方法&#x…

Tomcat中Web应用程序停止时为了防止内存泄漏,JDBC驱动程序被强制取消注册出现原因

1.问题描述 本地Windows环境开发的Springboot项目同样的mysql版本&#xff0c;jdk版本&#xff0c;tomcat版本&#xff0c;本地运行没有任何问题&#xff0c;发布到阿里云服务器上时报以下问题&#xff1a; 06-May-2025 20:06:12.842 警告 [main] org.apache.catalina.loader…

主流国产大模型(以华为盘古大模型和腾讯混元大模型为例)API调用接口的具体参数和使用方法,包括Python和C++的示例代码

以下是主流国产大模型&#xff08;以华为盘古大模型和腾讯混元大模型为例&#xff09;API调用接口的具体参数和使用方法&#xff0c;包括Python和C的示例代码。 华为盘古大模型 API参数&#xff1a; - model&#xff1a;模型名称&#xff0c;如pangu-nlp-large。 - messages&…