From 34d36a15f339d331d668d4063cfdff50cffa5800 Mon Sep 17 00:00:00 2001
From: zhou zhou <zozhouo3o@gmail.com>
Date: 星期五, 17 四月 2026 15:11:32 +0800
Subject: [PATCH] #导出服务

---
 rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java |  179 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 167 insertions(+), 12 deletions(-)

diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java
index 4b3649a..5c111e4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java
@@ -33,6 +33,12 @@
 @Slf4j
 public class ExcelUtil {
     private static final Pattern EXTEND_FIELD_SOURCE_PATTERN = Pattern.compile("^extendFields\\.\\[(.+)]$");
+    private static final String SEQUENCE_SOURCE = "sequence";
+    private static final String SEQUENCE_LABEL = "搴忓彿";
+    private static final int EXCEL_WIDTH_UNIT = 256;
+    private static final int MIN_COLUMN_WIDTH_CHARS = 8;
+    private static final int MAX_COLUMN_WIDTH_CHARS = 60;
+    private static final int COLUMN_WIDTH_PADDING_CHARS = 2;
 
     public static void build(Workbook workbook, HttpServletResponse response) {
         response.reset();
@@ -122,9 +128,7 @@
                 }
             }
         }
-        for (int i = 0; i <= fields.length; i++) {
-            sheet.autoSizeColumn(i);
-        }
+        autoFitColumns(sheet, headerIdx, 0);
 
         return workbook;
     }
@@ -136,13 +140,19 @@
     public static Workbook create(List<Map<String, Object>> rows, List<ExportColumn> columns, ExportMeta exportMeta) {
         XSSFWorkbook workbook = new XSSFWorkbook();
         Sheet sheet = workbook.createSheet("export");
-        int titleColumnCount = Math.max(columns.size(), 4);
+        List<ExportColumn> effectiveColumns = buildEffectiveColumns(columns, exportMeta);
+        boolean generatedSequenceColumn = hasGeneratedSequenceColumn(columns, effectiveColumns);
+        int titleColumnCount = Math.max(effectiveColumns.size(), 4);
         int currentRowIndex = 0;
 
         CellStyle titleStyle = createTitleStyle(workbook);
         CellStyle subHeaderStyle = createSubHeaderStyle(workbook);
         CellStyle headerStyle = createHeaderStyle(workbook);
         CellStyle bodyStyle = createBodyStyle(workbook);
+
+        if (exportMeta != null && exportMeta.isLandscape()) {
+            sheet.getPrintSetup().setLandscape(true);
+        }
 
         if (exportMeta != null && StringUtils.isNotBlank(exportMeta.getReportTitle())) {
             Row titleRow = sheet.createRow(currentRowIndex++);
@@ -162,26 +172,123 @@
         }
 
         Row header = sheet.createRow(currentRowIndex++);
-        for (int index = 0; index < columns.size(); index++) {
+        for (int index = 0; index < effectiveColumns.size(); index++) {
             Cell headerCell = header.createCell(index);
-            headerCell.setCellValue(columns.get(index).getLabel());
+            headerCell.setCellValue(effectiveColumns.get(index).getLabel());
             headerCell.setCellStyle(headerStyle);
         }
 
         int rowIndex = currentRowIndex;
-        for (Map<String, Object> rowData : rows) {
+        for (int dataIndex = 0; dataIndex < rows.size(); dataIndex++) {
+            Map<String, Object> rowData = rows.get(dataIndex);
             Row row = sheet.createRow(rowIndex++);
-            for (int columnIndex = 0; columnIndex < columns.size(); columnIndex++) {
-                Object value = getRowValue(rowData, columns.get(columnIndex).getSource());
+            for (int columnIndex = 0; columnIndex < effectiveColumns.size(); columnIndex++) {
+                Object value = getExportCellValue(rowData, effectiveColumns.get(columnIndex).getSource(), generatedSequenceColumn, dataIndex);
                 writeCellValue(row, columnIndex, value, bodyStyle);
             }
         }
 
-        for (int columnIndex = 0; columnIndex < columns.size(); columnIndex++) {
-            sheet.autoSizeColumn(columnIndex);
-        }
+        autoFitColumns(sheet, effectiveColumns.size(), header.getRowNum());
 
         return workbook;
+    }
+
+    private static void autoFitColumns(Sheet sheet, int columnCount, int startRowIndex) {
+        if (sheet == null || columnCount <= 0) {
+            return;
+        }
+
+        DataFormatter formatter = new DataFormatter(Locale.CHINA);
+        for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
+            int maxDisplayChars = MIN_COLUMN_WIDTH_CHARS;
+            for (int rowIndex = Math.max(startRowIndex, 0); rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                Row row = sheet.getRow(rowIndex);
+                if (row == null) {
+                    continue;
+                }
+                Cell cell = row.getCell(columnIndex);
+                if (cell == null || isMergedCell(sheet, rowIndex, columnIndex)) {
+                    continue;
+                }
+                maxDisplayChars = Math.max(maxDisplayChars, getDisplayChars(formatter.formatCellValue(cell)));
+            }
+            int widthChars = Math.min(MAX_COLUMN_WIDTH_CHARS, maxDisplayChars + COLUMN_WIDTH_PADDING_CHARS);
+            sheet.setColumnWidth(columnIndex, widthChars * EXCEL_WIDTH_UNIT);
+        }
+    }
+
+    private static boolean isMergedCell(Sheet sheet, int rowIndex, int columnIndex) {
+        for (int index = 0; index < sheet.getNumMergedRegions(); index++) {
+            CellRangeAddress region = sheet.getMergedRegion(index);
+            if (region.isInRange(rowIndex, columnIndex)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static int getDisplayChars(String value) {
+        if (StringUtils.isBlank(value)) {
+            return 0;
+        }
+        int maxLineChars = 0;
+        for (String line : value.split("\\R", -1)) {
+            maxLineChars = Math.max(maxLineChars, getLineDisplayChars(line));
+        }
+        return maxLineChars;
+    }
+
+    private static int getLineDisplayChars(String value) {
+        int width = 0;
+        for (int index = 0; index < value.length(); ) {
+            int codePoint = value.codePointAt(index);
+            width += isWideCodePoint(codePoint) ? 2 : 1;
+            index += Character.charCount(codePoint);
+        }
+        return width;
+    }
+
+    private static boolean isWideCodePoint(int codePoint) {
+        Character.UnicodeScript script = Character.UnicodeScript.of(codePoint);
+        return script == Character.UnicodeScript.HAN
+                || script == Character.UnicodeScript.HIRAGANA
+                || script == Character.UnicodeScript.KATAKANA
+                || script == Character.UnicodeScript.HANGUL;
+    }
+
+    private static List<ExportColumn> buildEffectiveColumns(List<ExportColumn> columns, ExportMeta exportMeta) {
+        List<ExportColumn> effectiveColumns = new ArrayList<>(columns);
+        if (exportMeta != null && exportMeta.isShowSequence() && !containsSequenceColumn(columns)) {
+            effectiveColumns.add(0, new ExportColumn(SEQUENCE_SOURCE, SEQUENCE_LABEL));
+        }
+        return effectiveColumns;
+    }
+
+    private static boolean containsSequenceColumn(List<ExportColumn> columns) {
+        for (ExportColumn column : columns) {
+            if (SEQUENCE_SOURCE.equals(column.getSource())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasGeneratedSequenceColumn(List<ExportColumn> originalColumns, List<ExportColumn> effectiveColumns) {
+        return effectiveColumns.size() > originalColumns.size()
+                && !effectiveColumns.isEmpty()
+                && SEQUENCE_SOURCE.equals(effectiveColumns.get(0).getSource());
+    }
+
+    private static Object getExportCellValue(
+            Map<String, Object> rowData,
+            String source,
+            boolean generatedSequenceColumn,
+            int dataIndex
+    ) {
+        if (generatedSequenceColumn && SEQUENCE_SOURCE.equals(source)) {
+            return dataIndex + 1;
+        }
+        return getRowValue(rowData, source);
     }
 
     private static Object getRowValue(Map<String, Object> rowData, String source) {
@@ -301,13 +408,26 @@
         private final String printedAt;
         private final String operator;
         private final int count;
+        private final Map<String, Object> reportStyle;
 
         public ExportMeta(String reportTitle, String reportDate, String printedAt, String operator, int count) {
+            this(reportTitle, reportDate, printedAt, operator, count, Collections.emptyMap());
+        }
+
+        public ExportMeta(
+                String reportTitle,
+                String reportDate,
+                String printedAt,
+                String operator,
+                int count,
+                Map<String, ?> reportStyle
+        ) {
             this.reportTitle = reportTitle;
             this.reportDate = reportDate;
             this.printedAt = printedAt;
             this.operator = operator;
             this.count = count;
+            this.reportStyle = normalizeReportStyle(reportStyle);
         }
 
         public String getReportTitle() {
@@ -329,6 +449,41 @@
         public int getCount() {
             return count;
         }
+
+        public Map<String, Object> getReportStyle() {
+            return reportStyle;
+        }
+
+        public boolean isLandscape() {
+            return "landscape".equalsIgnoreCase(Objects.toString(getReportStyleValue("orientation"), ""));
+        }
+
+        public boolean isShowSequence() {
+            if (reportStyle.isEmpty() || !reportStyle.containsKey("showSequence")) {
+                return true;
+            }
+            Object value = getReportStyleValue("showSequence");
+            if (value instanceof Boolean boolValue) {
+                return boolValue;
+            }
+            return !"false".equalsIgnoreCase(Objects.toString(value, ""));
+        }
+
+        private Object getReportStyleValue(String key) {
+            return reportStyle.get(key);
+        }
+
+        private static Map<String, Object> normalizeReportStyle(Map<String, ?> reportStyle) {
+            if (reportStyle == null || reportStyle.isEmpty()) {
+                return Collections.emptyMap();
+            }
+
+            Map<String, Object> normalizedReportStyle = new HashMap<>();
+            for (Map.Entry<String, ?> entry : reportStyle.entrySet()) {
+                normalizedReportStyle.put(entry.getKey(), entry.getValue());
+            }
+            return Collections.unmodifiableMap(normalizedReportStyle);
+        }
     }
 
     /**

--
Gitblit v1.9.1