ExcelExporter.java

package edu.jiangxin.apktoolbox.utils;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;

import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.TableModel;
import java.awt.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 工具类:将 JTable 的 TableModel 数据导出为 .xlsx 文件。
 */
public class ExcelExporter {

    private static final Logger logger = LogManager.getLogger(ExcelExporter.class);

    private ExcelExporter() {
        // 工具类,禁止实例化
    }

    /**
     * 完整导出流程:空数据检查 → 文件选择 → 写文件 → 结果提示。
     * 必须在 EDT 中调用。
     *
     * @param tableModel      数据来源
     * @param defaultFileName 文件保存对话框的默认文件名,例如 "pdf_stat_export.xlsx"
     * @param parent          父组件,用于对话框定位
     */
    public static void export(TableModel tableModel, String defaultFileName, Component parent) {
        if (tableModel.getRowCount() == 0) {
            SwingUtilities.invokeLater(() ->
                JOptionPane.showMessageDialog(parent, "当前没有可导出的数据", "提示", JOptionPane.INFORMATION_MESSAGE));
            return;
        }

        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setDialogTitle("保存 Excel 文件");
        fileChooser.setFileFilter(new FileNameExtensionFilter("Excel 文件 (*.xlsx)", "xlsx"));
        fileChooser.setSelectedFile(new File(defaultFileName));

        int result = fileChooser.showSaveDialog(parent);
        if (result != JFileChooser.APPROVE_OPTION) {
            return;
        }

        File selectedFile = fileChooser.getSelectedFile();
        // 自动追加 .xlsx 后缀
        if (!selectedFile.getName().toLowerCase().endsWith(".xlsx")) {
            selectedFile = new File(selectedFile.getAbsolutePath() + ".xlsx");
        }
        final File targetFile = selectedFile;

        // 在后台线程写文件,避免阻塞 EDT
        final TableModel modelSnapshot = tableModel;
        new Thread(() -> {
            try {
                writeToFile(modelSnapshot, targetFile);
                final String absolutePath = targetFile.getAbsolutePath();
                SwingUtilities.invokeLater(() ->
                    JOptionPane.showMessageDialog(parent, "导出成功:" + absolutePath, "成功", JOptionPane.INFORMATION_MESSAGE));
            } catch (IOException e) {
                logger.error("导出 Excel 失败", e);
                final String errorMsg = e.getMessage();
                SwingUtilities.invokeLater(() ->
                    JOptionPane.showMessageDialog(parent, "导出失败:" + errorMsg, "错误", JOptionPane.ERROR_MESSAGE));
            }
        }).start();
    }

    /**
     * 将 TableModel 数据写入指定文件(在后台线程调用)。
     */
    static void writeToFile(TableModel tableModel, File targetFile) throws IOException {
        try (XSSFWorkbook workbook = new XSSFWorkbook()) {
            Sheet sheet = workbook.createSheet("Sheet1");

            // 写表头
            Row headerRow = sheet.createRow(0);
            for (int col = 0; col < tableModel.getColumnCount(); col++) {
                headerRow.createCell(col).setCellValue(tableModel.getColumnName(col));
            }

            // 写数据行
            for (int row = 0; row < tableModel.getRowCount(); row++) {
                Row dataRow = sheet.createRow(row + 1);
                for (int col = 0; col < tableModel.getColumnCount(); col++) {
                    Object value = tableModel.getValueAt(row, col);
                    dataRow.createCell(col).setCellValue(value == null ? "" : value.toString());
                }
            }

            try (FileOutputStream fos = new FileOutputStream(targetFile)) {
                workbook.write(fos);
            }
        }
    }
}