陈昶聿
2026-04-27 69f18ff49510ea52a3a7ec40019e18343141c63a
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
@@ -38,14 +38,9 @@
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFPicture;
import org.apache.poi.xssf.usermodel.XSSFShape;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -1449,19 +1444,6 @@
    }
    /**
     * 判断是否为低版本浏览器
     *
     * @param request 浏览器请求头
     * @return
     */
    public static boolean isLowVersionBrowser(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        return userAgent.contains("msie")
                || userAgent.contains("Trident")
                || userAgent.contains("Edge");
    }
    /**
     * 设置导出Excel文件名称
     * @param response 返回数据
     * @param request 浏览器请求头
@@ -1472,18 +1454,157 @@
            //会清空响应缓冲区的内容,确保后续输出不会受到之前数据的影响
            response.reset();
            // 定义浏览器响应表头,顺带定义下载名
            if (isLowVersionBrowser(request)) {
                fileName = URLEncoder.encode(fileName, "UTF8") + ".xlsx";
                response.setHeader("Content-Disposition",
                        "attachment;filename=" + fileName);
            } else {
                response.setHeader("Content-Disposition",
                        "attachment;filename=" + new String((fileName + ".xlsx").getBytes("gb2312"), "ISO8859-1"));
            }
            fileName = fileName + ".xlsx";
            // 对文件名进行URL编码,支持中文文件名
            String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            // 使用 filename* 参数支持 UTF-8 编码(RFC 5987),同时保留 filename 参数兼容旧浏览器
            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName +
                    "\"; filename*=utf-8''" + encodedFileName);
            // 定义下载的类型,标明是excel文件
            response.setContentType("application/vnd.ms-excel");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
        }catch (Exception e){
            log.error("{}设置导出Excel文件名称异常,{}", fileName, e.getMessage());
        }
    }
//    public static void main(String[] args) {
//        // 1. 创建工作簿
//        try (XSSFWorkbook workbook = new XSSFWorkbook()) {
//            // 2. 生成柱状图 Sheet
//            createBarChartSheet(workbook);
//            // 3. 生成折线图 Sheet
//            createLineChartSheet(workbook);
//            // 4. 生成饼状图 Sheet
//            createPieChartSheet(workbook);
//
//            // 5. 导出到本地文件
//            try (FileOutputStream fos = new FileOutputStream("D:/图表导出.xlsx")) {
//                workbook.write(fos);
//                System.out.println("Excel 图表导出成功!路径:D:/图表导出.xlsx");
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }
    // ==================== 1. 创建柱状图 Sheet ====================
    private static void createBarChartSheet(XSSFWorkbook workbook) {
        XSSFSheet sheet = workbook.createSheet("柱状图");
        writeChartData(sheet);
        XSSFDrawing drawing = sheet.createDrawingPatriarch();
        XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 3, 1, 12, 15);
        XSSFChart chart = drawing.createChart(anchor);
        chart.setTitleText("产品销量-柱状图");
        chart.setTitleOverlay(false);
        // 坐标轴
        XDDFCategoryAxis xAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
        XDDFValueAxis yAxis = chart.createValueAxis(AxisPosition.LEFT);
        yAxis.setCrosses(AxisCrosses.AUTO_ZERO);
        // 数据范围:分类A2:A6,数值B2:B6
        XDDFDataSource<String> cat = XDDFDataSourcesFactory.fromStringCellRange(sheet,
                new CellRangeAddress(1, 5, 0, 0));
        XDDFNumericalDataSource<Double> val = XDDFDataSourcesFactory.fromNumericCellRange(sheet,
                new CellRangeAddress(1, 5, 1, 1));
        // 柱状图数据
        XDDFBarChartData barChart = (XDDFBarChartData) chart.createData(ChartTypes.BAR, xAxis, yAxis);
        XDDFBarChartData.Series series = (XDDFBarChartData.Series) barChart.addSeries(cat, val);
        series.setTitle("销量", null);
        barChart.setBarDirection(BarDirection.COL);
        // 绘制(关键:缺失就不显示图表)
        chart.plot(barChart);
    }
    // ==================== 2. 创建折线图 Sheet ====================
    private static void createLineChartSheet(XSSFWorkbook workbook) {
        XSSFSheet sheet = workbook.createSheet("折线图");
        // 写入表头 + 数据
        writeChartData(sheet);
        // 绘图区域
        XSSFDrawing drawing = sheet.createDrawingPatriarch();
        XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 5, 10, 20);
        XSSFChart chart = drawing.createChart(anchor);
        // 图表标题
        chart.setTitleText("月度销售额折线图");
        chart.setTitleOverlay(false);
        // 图例
        XDDFChartLegend legend = chart.getOrAddLegend();
        legend.setPosition(LegendPosition.TOP_RIGHT);
        // X轴、Y轴
        XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
        bottomAxis.setTitle("月份");
        XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
        leftAxis.setTitle("销售额(万元)");
        // 数据范围
        XDDFDataSource<String> months = XDDFDataSourcesFactory.fromStringCellRange(sheet, new CellRangeAddress(0, 5, 0, 0));
        XDDFNumericalDataSource<Double> values = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, 5, 1, 1));
        // 创建折线图
        XDDFLineChartData lineData = (XDDFLineChartData) chart.createData(ChartTypes.LINE, bottomAxis, leftAxis);
        XDDFLineChartData.Series series = (XDDFLineChartData.Series) lineData.addSeries(months, values);
        series.setTitle("销售额", null);
        series.setMarkerStyle(MarkerStyle.CIRCLE); // 显示圆点
        chart.plot(lineData);
    }
    // ========== 新增:饼状图 ==========
    private static void createPieChartSheet(XSSFWorkbook workbook) {
        XSSFSheet sheet = workbook.createSheet("饼状图");
        writeChartData(sheet);
        XSSFDrawing drawing = sheet.createDrawingPatriarch();
        XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 3, 1, 12, 15);
        XSSFChart chart = drawing.createChart(anchor);
        chart.setTitleText("产品占比-饼状图");
        chart.setTitleOverlay(false);
        // 饼图不需要XY轴
        XDDFDataSource<String> cat = XDDFDataSourcesFactory.fromStringCellRange(sheet,
                new CellRangeAddress(1, 5, 0, 0));
        XDDFNumericalDataSource<Double> val = XDDFDataSourcesFactory.fromNumericCellRange(sheet,
                new CellRangeAddress(1, 5, 1, 1));
        // 饼图数据
        XDDFPieChartData pieChart = (XDDFPieChartData) chart.createData(ChartTypes.PIE, null, null);
        pieChart.addSeries(cat, val);
        chart.plot(pieChart);
    }
    // ==================== 公共方法:写入图表数据 ====================
    private static void writeChartData(XSSFSheet sheet) {
        Object[][] data = {
                {"分类", "数值"},
                {"产品A", 35},
                {"产品B", 28},
                {"产品C", 42},
                {"产品D", 19},
                {"产品E", 26}
        };
        int rowIdx = 0;
        for (Object[] rows : data) {
            Row row = sheet.createRow(rowIdx++);
            int cellIdx = 0;
            for (Object val : rows) {
                if (val instanceof String) {
                    row.createCell(cellIdx++).setCellValue((String) val);
                } else if (val instanceof Number) {
                    row.createCell(cellIdx++).setCellValue(((Number) val).doubleValue());
                }
            }
        }
        sheet.autoSizeColumn(0);
        sheet.autoSizeColumn(1);
    }
}