| | |
| | | // 如果是index为1的sheet,设置顶端标题行 |
| | | |
| | | // 将第1行(索引为0)设置为每页重复的标题行 |
| | | sheet.setRepeatingRows(CellRangeAddress.valueOf("$1:$4")); |
| | | sheet.setRepeatingRows(CellRangeAddress.valueOf("$1:$5")); |
| | | row = sheet.createRow(0); |
| | | row.setHeight((short) (37*20)); |
| | | |
| | |
| | | public void fillExcelData(int index, Row row) { |
| | | // int startNo = index * sheetSize; |
| | | // int endNo = Math.min(startNo + sheetSize, list.size()); |
| | | |
| | | int startRow = (index == 1) ? 5 : 1; |
| | | for (int i = 0; i < list.size(); i++) { |
| | | if(index==1) |
| | | row = sheet.createRow(i + 5 ); |
| | | else |
| | | row = sheet.createRow(i + 1 ); |
| | | // 第一步:让POI自动计算适配内容的行高 |
| | | // sheet.autoSizeRow(0); |
| | | // 第二步:获取自动计算后的行高 |
| | | |
| | | // row.setHeight((short) -1); // 先设为自动,由addCell计算后覆盖 |
| | | |
| | | // 得到导出对象. |
| | | T vo = (T) list.get(i); |
| | |
| | | // 设置实体类私有属性可访问 |
| | | field.setAccessible(true); |
| | | this.addCell(excel, row, vo, field, column++); |
| | | |
| | | } |
| | | // 批量调整行高(数据填充完成后) |
| | | if (list.size() > 0) { |
| | | int lastRow = startRow + list.size() - 1; |
| | | batchAdjustRowHeights(sheet, startRow, lastRow); |
| | | } |
| | | } |
| | | } |
| | | /** |
| | | * 批量处理行高(在数据填充完成后统一计算) |
| | | */ |
| | | /** |
| | | * 批量处理行高(在数据填充完成后统一计算) |
| | | */ |
| | | private String getCellStringValue(Cell cell) { |
| | | if (cell == null) return null; |
| | | |
| | | switch (cell.getCellType()) { |
| | | case STRING: |
| | | return cell.getStringCellValue(); |
| | | case NUMERIC: |
| | | return String.valueOf(cell.getNumericCellValue()); |
| | | case BOOLEAN: |
| | | return String.valueOf(cell.getBooleanCellValue()); |
| | | case FORMULA: |
| | | try { |
| | | return cell.getStringCellValue(); |
| | | } catch (Exception e) { |
| | | return String.valueOf(cell.getNumericCellValue()); |
| | | } |
| | | default: |
| | | return null; |
| | | } |
| | | } |
| | | private void batchAdjustRowHeights(Sheet sheet, int startRow, int endRow) { |
| | | Workbook workbook = sheet.getWorkbook(); |
| | | |
| | | |
| | | for (int rowNum = startRow; rowNum <= endRow; rowNum++) { |
| | | Row row = sheet.getRow(rowNum); |
| | | if (row == null) continue; |
| | | |
| | | int maxLinesInRow = 1; // 记录该行所有单元格中的最大行数 |
| | | |
| | | // 第一步:遍历该行的所有单元格,找到最大行数需求 |
| | | for (int colNum = 0; colNum < row.getLastCellNum(); colNum++) { |
| | | Cell cell = row.getCell(colNum); |
| | | if (cell == null) continue; |
| | | |
| | | // 获取单元格内容 |
| | | String content = getCellStringValue(cell); |
| | | if (content == null || content.isEmpty()) continue; |
| | | |
| | | // 获取列宽 |
| | | int columnWidth = sheet.getColumnWidth(colNum) / 256; |
| | | if (columnWidth <= 0) columnWidth = 10; // 默认列宽 |
| | | |
| | | // 计算该单元格内容需要的行数 |
| | | int contentLines = calculateContentLines(content, columnWidth); |
| | | |
| | | // 更新最大行数 |
| | | maxLinesInRow = Math.max(maxLinesInRow, contentLines); |
| | | } |
| | | |
| | | // 第二步:根据最大行数设置行高(关键修复) |
| | | if (maxLinesInRow > 1) { |
| | | // 基础行高:一行文本的高度 |
| | | int baseHeightPerLine = 400; // 20点 = 400单位(建议值) |
| | | |
| | | // 计算总高度 = 行数 × 每行高度 |
| | | int newHeight = maxLinesInRow * baseHeightPerLine + 600; |
| | | |
| | | // 限制最小和最大高度 |
| | | int minHeight = 300; // 15点 |
| | | int maxHeight = 10000; // 500点 |
| | | newHeight = Math.max(minHeight, Math.min(newHeight, maxHeight)); |
| | | |
| | | // 设置行高 |
| | | row.setHeight((short) newHeight); |
| | | |
| | | // 可选:记录调整信息用于调试 |
| | | log.debug("Row {} adjusted: maxLines={}, height={}", |
| | | rowNum, maxLinesInRow, newHeight); |
| | | } |
| | | } |
| | | } |
| | | /** |
| | | * 计算文本的有效字符宽度 |
| | | */ |
| | | private double calculateEffectiveWidth(String text) { |
| | | if (text == null || text.isEmpty()) { |
| | | return 0; |
| | | } |
| | | |
| | | double totalWidth = 0; |
| | | |
| | | for (char c : text.toCharArray()) { |
| | | if (isChineseChar(c)) { |
| | | // 中文字符:1.0宽度 |
| | | totalWidth += 2.0; |
| | | } else if (Character.isDigit(c)) { |
| | | // 数字:0.6宽度 |
| | | totalWidth += 0.6; |
| | | } else if (Character.isUpperCase(c)) { |
| | | // 大写字母:0.7宽度 |
| | | totalWidth += 0.7; |
| | | } else if (Character.isLowerCase(c)) { |
| | | // 小写字母:0.5宽度 |
| | | totalWidth += 0.5; |
| | | } else if (c == '.' || c == ',') { |
| | | // 点号、逗号:0.3宽度 |
| | | totalWidth += 0.3; |
| | | } else if (c == '-' || c == '_') { |
| | | // 连字符、下划线:0.35宽度 |
| | | totalWidth += 0.35; |
| | | } else if (c == ' ') { |
| | | // 空格:0.3宽度 |
| | | totalWidth += 0.3; |
| | | } else if (c == '\t') { |
| | | // 制表符:4.0宽度 |
| | | totalWidth += 4.0; |
| | | } else { |
| | | // 其他字符:默认0.6宽度 |
| | | totalWidth += 0.6; |
| | | } |
| | | } |
| | | |
| | | return totalWidth; |
| | | } |
| | | /** |
| | | * 计算单元格内容所需行数(更精确的版本) |
| | | */ |
| | | private int calculateContentLines(String content, int columnWidthChars) { |
| | | if (content == null || content.isEmpty() || columnWidthChars <= 0) { |
| | | return 1; |
| | | } |
| | | |
| | | int totalLines = 0; |
| | | String[] lines = content.split("\n"); |
| | | |
| | | for (String line : lines) { |
| | | if (line.trim().isEmpty()) { |
| | | totalLines++; // 空行也算一行 |
| | | continue; |
| | | } |
| | | |
| | | // 计算该行需要的字符宽度 |
| | | // 考虑中英文字符宽度差异 |
| | | double effectiveLength = 0; |
| | | |
| | | |
| | | effectiveLength += calculateEffectiveWidth(line); // 中文字符占1个宽度 |
| | | |
| | | |
| | | |
| | | // 计算需要的行数 |
| | | int linesForText = (int) Math.ceil(effectiveLength / columnWidthChars); |
| | | totalLines += Math.max(1, linesForText); |
| | | } |
| | | |
| | | return Math.max(totalLines, 1); |
| | | } |
| | | |
| | | /** |
| | | * 判断是否为中文字符 |
| | | */ |
| | | private boolean isChineseChar(char c) { |
| | | Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); |
| | | return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS |
| | | || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS |
| | | || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A |
| | | || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION |
| | | || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION |
| | | || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS; |
| | | } |
| | | /** |
| | | * 创建表格样式 |
| | | * |
| | |
| | | } else if (align == HorizontalAlignment.RIGHT) { |
| | | styleKey = "data3"; |
| | | } |
| | | cell.setCellStyle(styles.get(styleKey)); |
| | | System.out.println(styleKey); |
| | | // 获取并修改样式 |
| | | CellStyle style = styles.get(styleKey); |
| | | Workbook workbook = row.getSheet().getWorkbook(); |
| | | CellStyle newStyle = workbook.createCellStyle(); |
| | | newStyle.cloneStyleFrom(style); |
| | | newStyle.setWrapText(true); // 关键:启用自动换行 |
| | | cell.setCellStyle(newStyle); |
| | | |
| | | // 用于读取对象中的属性 |
| | | Object value = getTargetValue(vo, field, attr); |
| | |
| | | // 设置列类型 |
| | | setCellVo(value, attr, cell); |
| | | } |
| | | |
| | | |
| | | System.out.println(row.getHeight()); |
| | | // // adjustRowHeightAfterSetValue(row, cell, value); |
| | | // int defaultRowHeight = row.getHeight() / 20; // 原始行高(转成点数) |
| | | // if(value==null) |
| | | // value=""; |
| | | // System.out.println(sheet.getColumnWidth(column)); |
| | | // int contentLines = getContentLines(value.toString(), sheet.getColumnWidth(column), style); // 计算换行后的行数 |
| | | // int newHeight = defaultRowHeight * contentLines; // 按行数调整行高(可加少量冗余) |
| | | // row.setHeightInPoints((short) (newHeight)); // 提高行高(+2 是冗余,避免内容被截断) |
| | | addStatisticsData(column, Convert.toStr(value), attr); |
| | | } |
| | | } catch (Exception e) { |
| | |
| | | // header.setFontName("宋体");4 |
| | | // header.setFontSize((short) 10); |
| | | // 设置页眉内容 |
| | | hssfSheet.setMargin(Sheet.HeaderMargin, 1.34); |
| | | hssfSheet.setMargin(Sheet.HeaderMargin, 1.43); |
| | | header.setRight("&\"宋体,Bold\"共 &N 页 第 &P 页"); |
| | | } |
| | | // 如果是index为1的sheet,设置顶端标题行 |