JAVA - 使用Apache POI 自定义报表字段手写导出(支持-合并单元格)


bash 复制代码
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
</dependency>
java 复制代码
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

//工具类导出(支持1单1商品,1单多商品)
public class ExcelExportUtil {

    public static String convertSpec(String spec) {
        JSONArray array = JSONUtil.parseArray(spec);
        return array.stream().map(obj -> {
            cn.hutool.json.JSONObject o = (cn.hutool.json.JSONObject) obj;
            return o.getStr("name") + ":" + o.getStr("value");
        }).collect(Collectors.joining(" "));
    }

    public static void putIfSelected(Map<String, Object> map, String field, Object value, List<String> exportFields) {
        if (exportFields.contains(field)) {
            map.put(field, value);
        }
    }


    //表头样式
    private static CellStyle createHeadStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        //背景色(浅灰)
        style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        //字体
        Font font = workbook.createFont();
        font.setFontName("微软雅黑");
        font.setFontHeightInPoints((short) 15);
        font.setBold(true);
        style.setFont(font);
        //对齐
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        //边框
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        return style;
    }

    //数据样式
    private static CellStyle createDataStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setFontName("微软雅黑");
        font.setFontHeightInPoints((short) 11);
        style.setFont(font);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        return style;
    }

    //初始化
    private static void initResponse(HttpServletResponse response, String sheetName) throws UnsupportedEncodingException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(sheetName, "UTF-8") + ".xlsx");
    }


    //---------------------------售后导出-------------------------------
    //自定义字段普通导出
    public static void export(HttpServletResponse response, List<Map<String, Object>> data, List<String> fields, String sheetName) throws IOException {
        initResponse(response, sheetName);
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet(sheetName);
        writeSheet(workbook, sheet, data, fields, false);
        workbook.write(response.getOutputStream());
        workbook.close();
    }

    //---------------------------一键下单导出-------------------------------
    //自定义字段订单信息合并导出
    public static void exportOrder(HttpServletResponse response, List<Map<String, Object>> data, List<String> fields, String sheetName) throws IOException {
        initResponse(response, sheetName);
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet(sheetName);
        writeSheet(workbook, sheet, data, fields, true);
        workbook.write(response.getOutputStream());
        workbook.close();
    }

    //写sheet
    private static void writeSheet(Workbook workbook, Sheet sheet, List<Map<String, Object>> data, List<String> fields, boolean mergeOrder) {
        CellStyle headStyle = createHeadStyle(workbook);
        CellStyle dataStyle = createDataStyle(workbook);
        writeHeader(sheet, fields, headStyle);
        writeRows(sheet, data, fields, dataStyle, mergeOrder);
    }

    //创建表头
    private static void writeHeader(Sheet sheet, List<String> fields, CellStyle headStyle) {
        Row headRow = sheet.createRow(0);
        headRow.setHeightInPoints(30);
        for (int i = 0; i < fields.size(); i++) {
            Cell cell = headRow.createCell(i);
            cell.setCellValue(fields.get(i));
            cell.setCellStyle(headStyle);
            sheet.setColumnWidth(i, 20 * 256);
        }
    }

    //写数据
    private static void writeRows(Sheet sheet, List<Map<String, Object>> data, List<String> fields, CellStyle dataStyle, boolean mergeOrder) {
        int rowIndex = 1;
        int mergeStartRow = 1;
        String lastOrderId = "";
        for (Map<String, Object> rowData : data) {
            String currentOrderId = String.valueOf(rowData.getOrDefault("订单编号", ""));
            //合并逻辑只在 mergeOrder = true 时生效
            if (mergeOrder && !lastOrderId.equals(currentOrderId)) {
                if (!lastOrderId.isEmpty()) {
                    mergeOrderCells(sheet, mergeStartRow, rowIndex - 1, fields);
                }
                mergeStartRow = rowIndex;
            }
            Row row = sheet.createRow(rowIndex++);
            for (int i = 0; i < fields.size(); i++) {
                String field = fields.get(i);
                Object value = rowData.get(field);
                Cell cell = row.createCell(i);
                cell.setCellStyle(dataStyle);
                if (value == null) {
                    cell.setCellValue("");
                } else if (value instanceof Number) {
                    cell.setCellValue(((Number) value).doubleValue());
                } else {
                    cell.setCellValue(value.toString());
                }
            }
            lastOrderId = currentOrderId;
        }
        // 最后一个订单合并
        if (mergeOrder && !lastOrderId.isEmpty()) {
            mergeOrderCells(sheet, mergeStartRow, rowIndex - 1, fields);
        }
    }


    // ===== 辅助方法:合并订单字段的单元格 =====
    private static void mergeOrderCells(Sheet sheet, int startRow, int endRow, List<String> fields) {
        // 如果起始行大于或等于结束行,说明只有一行数据,不需要合并,直接返回
        if (startRow >= endRow) {
            return;
        }
        for (int i = 0; i < fields.size(); i++) {
            String fieldName = fields.get(i);
            // 如果是订单字段,则合并
            if (ORDER_FIELDS.contains(fieldName)) {
                sheet.addMergedRegion(new CellRangeAddress(startRow, endRow, i, i));
            }
        }
    }

    //定义合并的表头
    private static final Set<String> ORDER_FIELDS = Set.of(
            "订单编号", "店铺名称", "店铺ID", "成交时间", "订单状态", "实收款",
            "支付方式", "发货时间", "旗帜颜色", "用户留言", "店铺备注", "软件备注"
    );
}
java 复制代码
	@PostMapping("/export")
    public void export(HttpServletResponse response, @Valid @RequestBody OrderReq req) throws IOException {
        List<OrderVO> list = baseService.getOneClickOrderList(req);		//换成自己的查询列表
        Assert.isFalse(StringUtils.isEmpty(list), "导出失败,数据为空");
        List<Map<String, Object>> data = otherService.buildOrderExportData(list, req.getExportFields());
        ExcelExportUtil.exportOrder(response, data, req.getExportFields(), "oneClickOrder");
    }

//业务层,整合数据
public List<Map<String, Object>> buildOrderExportData(List<OrderVO> list, List<String> exportFields) {
        List<Map<String, Object>> result = new ArrayList<>();
        for (OrderVO vo : list) {
            for (ProductVO p : vo.getProductVOS()) {
                //-----------------------订单信息---------------------
                Map<String, Object> map = new HashMap<>();
                ExcelExportUtil.putIfSelected(map, "订单编号", vo.getOrderId(), exportFields);
                ExcelExportUtil.putIfSelected(map, "店铺名称", vo.getShopName(), exportFields);
                ExcelExportUtil.putIfSelected(map, "店铺ID", vo.getShopId(), exportFields);
                ExcelExportUtil.putIfSelected(map, "成交时间", vo.getPayTime(), exportFields);
                ExcelExportUtil.putIfSelected(map, "订单状态", vo.getOrderStatusDesc(), exportFields);
                ExcelExportUtil.putIfSelected(map, "实收款", vo.getActualReceiveAmount(), exportFields);
                ExcelExportUtil.putIfSelected(map, "支付方式", TagConstants.getDesc(vo.getPayType()), exportFields);
                ExcelExportUtil.putIfSelected(map, "发货时间", vo.getShipTimeStr(), exportFields);
                ExcelExportUtil.putIfSelected(map, "旗帜颜色", TagConstants.getStarsDesc(vo.getSellerRemarkStars()), exportFields);
                ExcelExportUtil.putIfSelected(map, "用户留言", vo.getBuyerWords(), exportFields);
                ExcelExportUtil.putIfSelected(map, "店铺备注", vo.getSellerWords(), exportFields);
                ExcelExportUtil.putIfSelected(map, "软件备注", vo.getLocalRemark(), exportFields);
                //-----------------------商品信息---------------------
                String afterSaleStatus = p.getAfterSaleStatus() == 0 ?
                        p.getProductOrderStatusDesc() : AfterSaleConstants.getAfterSaleStatusDesc(p.getAfterSaleStatus());
                ExcelExportUtil.putIfSelected(map, "商品标题", p.getProductName(), exportFields);
                ExcelExportUtil.putIfSelected(map, "商品ID", p.getProductId(), exportFields);
                ExcelExportUtil.putIfSelected(map, "商品单价", p.getProductOrderAmount(), exportFields);
                ExcelExportUtil.putIfSelected(map, "商品数量", p.getItemNum(), exportFields);
                ExcelExportUtil.putIfSelected(map, "售后状态", afterSaleStatus, exportFields);
                ExcelExportUtil.putIfSelected(map, "商品实收", p.getProductPayAmount(), exportFields);
                ExcelExportUtil.putIfSelected(map, "运费", p.getPostAmount(), exportFields);
                ExcelExportUtil.putIfSelected(map, "运费优惠", p.getPostInsuranceAmount(), exportFields);
                ExcelExportUtil.putIfSelected(map, "规格", ExcelExportUtil.convertSpec(p.getSpec()), exportFields);
                ExcelExportUtil.putIfSelected(map, "商品编码", p.getCode(), exportFields);
                if (StringUtils.isNotEmpty(p.getAlProductVOList())) {
                    AlProductVO item = p.getAlProductVOList().get(0);
                    ExcelExportUtil.putIfSelected(map, "物流名称", item.getLogisticsCompany(), exportFields);
                    ExcelExportUtil.putIfSelected(map, "物流单号", item.getBillNo(), exportFields);
                }
                result.add(map);
            }
        }
        return result;
    }
相关推荐
肩上风骋2 小时前
C++基本知识点积累之d指针,invokemethod函数(一)
开发语言·c++·d指针·invokemethod()
明志数科2 小时前
具身智能数据标注工具对比评测:6大平台横向测评
开发语言·python
念何架构之路2 小时前
Go pprof性能剖析
开发语言·后端·golang
zhz52142 小时前
Spring Boot 接入国密实战:传输加密(TLCP)+ 密码加密(SM4)
java·spring boot·后端·国密·sm4
码界筑梦坊2 小时前
132-基于Python的中老年体检数据可视化分析系统
开发语言·python·信息可视化·flask·毕业设计
Harm灬小海2 小时前
【云计算学习之路】企业常用服务搭建:构建Apache WEB服务器
运维·服务器·学习·云计算·apache
人道领域2 小时前
【LeetCode刷题日记】617.合并二叉树(空间换安全,还是原地省内存)
java·数据结构·算法·leetcode
曹牧2 小时前
Bug定位
开发语言
linbaiwan6662 小时前
PD和QC快充协议电压诱骗(取电)芯片:USB-C口支持PD,USB-A口支持QC
c语言·开发语言