springBoot 整合 poi 导出带有复杂表格(合并表格)的word文件

官萧何 / 2023-08-24 / 原文

1.Maven依赖见上一篇文章

直接贴源码如下:

package com.mingx.pms.web.system.file;

import cn.hutool.core.date.DateUtil;
import com.mingx.pms.constant.SystemInfo;
import com.mingx.pms.entities.workplan.plan.vo.WorkPlanDetailExportVO;
import com.mingx.pms.entities.workplan.plan.vo.WorkPlanDetailQueryRO;
import com.mingx.pms.entities.workplan.plan.vo.WorkPlanDetailVO;
import com.mingx.pms.entities.workplan.report.constant.ReportTypeDict;
import com.mingx.pms.entities.workplan.report.vo.*;
import com.mingx.pms.entity.PageResult;
import com.mingx.pms.entity.Paginator;
import com.mingx.pms.exception.CustomException;
import com.mingx.pms.tools.BillCodeUtil;
import com.mingx.pms.workplan.service.plan.IWorkPlanDetailService;
import com.mingx.pms.workplan.service.report.IWorkReportService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.poi.xwpf.usermodel.*;
import org.camunda.feel.syntaxtree.In;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * @Description: 生产计划报告模块导出
 * @Author: gch
 * @Date: 2023/8/18 10:34
 */
@Api(value = "WorkPlanReportExportProvider", tags = "导出-生产计划报告模块")
@Controller
public class WorkPlanReportExportProvider extends BaseExportProvider{

    @Resource
    private IWorkReportService workReportService;

    @PostMapping("/workPlanReport/export")
    @ApiOperation(value = "导出生产计划报告Word", notes = "导出生产计划报告Word", produces = "application/octet-stream")
    public void exportWord(@RequestBody GenerateDetailRO generateDetailRO, HttpServletResponse response) throws IOException {
        String reportTypeDesc;
        String descByType;
        if (ReportTypeDict.MONTH.getCode().equals(generateDetailRO.getReportType())) {
            reportTypeDesc = ReportTypeDict.MONTH.getDesc();
            descByType = "本月";
        } else if (ReportTypeDict.WEEK.getCode().equals(generateDetailRO.getReportType())) {
            reportTypeDesc = ReportTypeDict.WEEK.getDesc();
            descByType = "本周";
        } else {
            throw new CustomException("报告类型错误", WorkPlanReportExportProvider.class.toString());
        }
        if (Objects.isNull(generateDetailRO.getReportId())) {
            throw new CustomException("报告id必填", WorkPlanReportExportProvider.class.toString());
        }
        WorkReportDetailVO workReportDetailVO = workReportService.generateDetailInfo(generateDetailRO);
        // 创建一个新的Word文档
        XWPFDocument document = new XWPFDocument();
        String formatStart = DateUtil.format(workReportDetailVO.getReportStartTime(), SystemInfo.DATE_CHINESE);
        String formatEnd = DateUtil.format(workReportDetailVO.getReportEndTime(), SystemInfo.DAY_CHINESE);
        String title = workReportDetailVO.getProjectDepartName() +
                "生产作业" + reportTypeDesc + "" + formatStart + "-" + formatEnd + "";
        addParagraphCenter(document, title);
        // 添加段落
        addParagraph(document, "一、电网运行总体情况:");
        addContent(document,"1、电网安全情况:");
        if (workReportDetailVO.getSafeSituationTextList().isEmpty()) {
            addContent(document, "");
        } else {
            for (String content : workReportDetailVO.getSafeSituationTextList()) {
                addContent(document, content);
            }
        }

        addContent(document,"2、电网运行情况:");
        if (workReportDetailVO.getRunSituationTextList().isEmpty()) {
            addContent(document, "");
        } else {
            for (String content : workReportDetailVO.getRunSituationTextList()) {
                addContent(document, content);
            }
        }
        addParagraph(document, "二、违章情况");
        addContent(document, "1、本周发生恶性违章" + workReportDetailVO.getMalignancyBreakRuleNum() + "条,严重违章 " +
                workReportDetailVO.getSeriousBreakRuleNum() + " 条,一般违章 " + workReportDetailVO.getNormalBreakRuleNum() + " 条。");
        addParagraph(document, "三、作业计划执行情况");
        addContent(document, "1、月作业计划中本周(本月)应完成 " + workReportDetailVO.getShouldFinish() + " 条,实际完成 " +
                workReportDetailVO.getRealFinish() + "");
        // 添加表格
        List<String> headers = Arrays.asList("序号", "作业计划内容", "时间", "未执行原因");
        // 对象集合 转换为表格识别的 列表类型
        List<List<String>> data = new ArrayList<>();
        Integer order = 1;
        for (WorkPlanStatisticTableVO unExecutePlan : workReportDetailVO.getUnExecutePlans()) {
            List<String> attributeValues = getAttributeValues(unExecutePlan);
            // 在列表最前面添加序号
            attributeValues.add(0, order.toString());
            order++;
            data.add(attributeValues);
        }
        addTable(document, headers, data);

        addContent(document, "2、" + descByType + "新增其他作业计划 " + workReportDetailVO.getAddWorkPlanNum() + " 条(月计划中未计划的)");
        // 添加表格
        List<String> headers2 = Arrays.asList("序号", "作业计划内容", "时间");
        List<List<String>> data2 = new ArrayList<>();
        Integer order2 = 1;
        for (WorkPlanStatisticTableVO vo : workReportDetailVO.getAddPlans()) {
            List<String> attributeValues = getAttributeValues(vo);
            attributeValues.add(0, order2.toString());
            order2++;
            data.add(attributeValues);
        }
        addTable(document, headers2, data2);

        if (ReportTypeDict.WEEK.getCode().equals(generateDetailRO.getReportType())) {
            addContent(document, "3、下周新增其他作业计划 " + workReportDetailVO.getNextWeekAddWorkPlanNum() + " 条(月计划中未计划的)");
            // 添加表格
            List<String> headers3 = Arrays.asList("序号", "作业计划内容", "时间");
            List<List<String>> data3 = new ArrayList<>();
            Integer order3 = 1;
            for (WorkPlanStatisticTableVO vo : workReportDetailVO.getNextWeekAddPlans()) {
                List<String> attributeValues = getAttributeValues(vo);
                attributeValues.add(0, order3.toString());
                order3++;
                data.add(attributeValues);
            }
            addTable(document, headers3, data3);
        }

        addParagraph(document, "四、停电情况");
        addContent(document, "1、" + descByType + "主网停电 " + workReportDetailVO.getMainNetPowerCutNum() + " 次。");
        addContent(document, "2、" + descByType + "公司计划停电应执行 " + workReportDetailVO.getCompanyPlanNum() + " 次,实际执行 " +
                workReportDetailVO.getRealExecutePowerCutNum() + " 次,临时停电" + workReportDetailVO.getTempPowerCutNum()
                + "次,故障停电 " + workReportDetailVO.getBreakdownPowerCutNum() + " 次。重复停电 " + workReportDetailVO.getRepeatPowerCutNum() + "");
        addContent(document,descByType + "公司计划停电未执行情况");
        // 添加表格
        List<String> headers4 = Arrays.asList("序号", "项目部", "变电站/线路名称", "计划停电时间", "未执行原因");
        List<List<String>> data4 = new ArrayList<>();
        Integer order4 = 1;
        for (PowerCutUnExecuteStatisticTableVO vo : workReportDetailVO.getPowerCutUnExecuteInfos()) {
            List<String> attributeValues = getAttributeValues(vo);
            attributeValues.add(0, order4.toString());
            order4++;
            data.add(attributeValues);
        }
        addTable(document, headers4, data4);

        addContent(document, descByType + "临时停电情况");
        // 添加表格
        List<String> headers5 = Arrays.asList("序号", "项目部", "变电站/线路名称", "停电时间", "送电时间", "停电时长(h)", "损失电量(kWh)", "停电具体原因", "处理经过");
        List<List<String>> data5 = new ArrayList<>();
        Integer order5 = 1;
        for (PowerCutStatisticTableVO vo : workReportDetailVO.getTempPowerCutInfos()) {
            List<String> attributeValues = getAttributeValues(vo);
            attributeValues.add(0, order5.toString());
            order5++;
            data.add(attributeValues);
        }
        addTable(document, headers5, data5);

        addContent(document, descByType + "故障停电情况");
        // 添加表格
        List<String> headers6 = Arrays.asList("序号", "项目部", "变电站/线路名称", "停电时间", "送电时间", "停电时长(h)", "损失电量(kWh)", "停电具体原因", "处理经过");
        List<List<String>> data6 = new ArrayList<>();
        Integer order6 = 1;
        for (PowerCutStatisticTableVO vo : workReportDetailVO.getBreakdownPowerCutInfos()) {
            List<String> attributeValues = getAttributeValues(vo);
            attributeValues.add(0, order6.toString());
            order6++;
            data.add(attributeValues);
        }
        addTable(document, headers6, data6);

        addParagraph(document, "五、隐患及缺陷消除情况");
        // 添加表格
        List<List<String>> data7 = new ArrayList<>();
        Integer order7 = 1;
        for (DefectHandleStatisticTableVO vo : workReportDetailVO.getDefectHandleInfos()) {
            List<String> attributeValues = getAttributeValues(vo);
            attributeValues.add(0, order7.toString());
            order7++;
            data.add(attributeValues);
        }
        addMergeTable(document, data7);
        addContent(document,"严重及以上缺陷未消除情况说明:");
        if (workReportDetailVO.getSeriousDefectUneliminatedTextList().isEmpty()) {
            addContent(document, "");
        } else {
            for (String content : workReportDetailVO.getRunSituationTextList()) {
                addContent(document, content);
            }
        }
        // 这里是动态标题和内容
        for (OtherDetailVO otherDetailVO : workReportDetailVO.getOtherDetailInfoList()) {
            addParagraph(document, otherDetailVO.getTitle());
            for (String content : otherDetailVO.getContents()) {
                addContent(document, content);
            }
        }

        // 设置响应头信息
        response.setHeader("Content-Disposition", "attachment; filename=" + title + ".docx");
        response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        // 将文档写入输出流
        document.write(response.getOutputStream());
        document.close();
    }

    /**
     * @author: gch
     * @Description: 添加基础文本内容
     * @Date: 2023/8/19 12:12
     * @Param:
     * @return:
     */
    private void addContent(XWPFDocument document, String content) {
        document.createParagraph().createRun().setText(content);
    }

    /**
     * @author: gch
     * @Description: 添加段落
     * @Date: 2023/8/19 12:11
     * @Param:
     * @return:
     */
    private void addParagraph(XWPFDocument document, String s) {
        XWPFParagraph paragraph1 = document.createParagraph();
        paragraph1.setAlignment(ParagraphAlignment.LEFT);
        XWPFRun run1 = paragraph1.createRun();
        run1.setBold(true);
        run1.setFontSize(16); // 设置标题字体大小
        run1.setText(s);
    }

    /**
     * @author: gch
     * @Description: 添加段落 居中
     * @Date: 2023/8/19 12:11
     * @Param:
     * @return:
     */
    private void addParagraphCenter(XWPFDocument document, String s) {
        XWPFParagraph paragraph1 = document.createParagraph();
        paragraph1.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun run1 = paragraph1.createRun();
        run1.setBold(true);
        run1.setFontSize(16); // 设置标题字体大小
        run1.setText(s);
    }

    /**
     * @author: gch
     * @Description: 添加动态表格
     * @Date: 2023/8/19 12:11
     * @Param:
     * @return:
     */
    private void addTable(XWPFDocument document, List<String> headers, List<List<String>> data) {
        Integer rowNum = 2;
        if (!Objects.isNull(data) && !data.isEmpty()) {
            rowNum = data.size() + 1;
        }
        // 添加表格
        XWPFTable table = document.createTable(rowNum, headers.size()); // 创建一个3行3列的表格

        // 创建表格后给每个单元格设置样式
        for (int row = 0; row < rowNum; row++) {
            for (int col = 0; col < headers.size(); col++) {
                XWPFTableCell cell = table.getRow(row).getCell(col);
                cell.setWidth("1000");
                addCellCenterStyle(cell);
            }
        }

        // 设置表头
        for (int col = 0; col < headers.size(); col++) {
            XWPFTableCell cell = table.getRow(0).getCell(col);
            cell.setText(headers.get(col));
        }
        // 设置示例数据
        for (int row = 0; row < data.size(); row++) {
            for (int col = 0; col < headers.size(); col++) {
                XWPFTableCell cell = table.getRow(row + 1).getCell(col);
                cell.setText(data.get(row).get(col));
            }
        }
    }

    /**
     * @author: gch
     * @Description: 添加动态表格
     * @Date: 2023/8/19 12:11
     * @Param:
     * @return:
     */
    private void addMergeTable(XWPFDocument document, List<List<String>> data) {
        Integer rowNum = 4;
        if (!Objects.isNull(data) && !data.isEmpty()) {
            rowNum = data.size() + 3;
        }
        // 添加表格
        XWPFTable table = document.createTable(rowNum, 10);

        // 创建表格后给每个单元格设置样式
        for (int row = 0; row < rowNum; row++) {
            for (int col = 0; col < 10; col++) {
                XWPFTableCell cell = table.getRow(row).getCell(col);
                cell.setWidth("1000");
                addCellCenterStyle(cell);
            }
        }

        mergeCellsHorizontal(table, 0, 1, 6);
        mergeCellsHorizontal(table, 0, 7, 9);
        mergeCellsHorizontal(table, 1, 1, 2);
        mergeCellsHorizontal(table, 1, 3, 4);
        mergeCellsHorizontal(table, 1, 5, 6);

        mergeCellsVertically(table, 0, 0, 2);
        mergeCellsVertically(table, 7, 1, 2);
        mergeCellsVertically(table, 8, 1, 2);
        mergeCellsVertically(table, 9, 1, 2);
        //手动设置表头
        addTableContext(table, 0, 0, "项目部");
        addTableContext(table, 0, 1, "本周发现及处理情况");
        addTableContext(table, 0, 7, "累计未消除缺陷");

        addTableContext(table, 1, 1, "危急缺陷");
        addTableContext(table, 1, 3, "重大缺陷");
        addTableContext(table, 1, 5, "一般缺陷");

        addTableContext(table, 1, 7, "危急缺陷");
        addTableContext(table, 1, 8, "重大缺陷");
        addTableContext(table, 1, 9, "一般缺陷");

        addTableContext(table, 2, 1, "发现");
        addTableContext(table, 2, 2, "消除");
        addTableContext(table, 2, 3, "发现");
        addTableContext(table, 2, 4, "消除");
        addTableContext(table, 2, 5, "发现");
        addTableContext(table, 2, 6, "消除");

        // 设置示例数据
        for (int row = 0; row < data.size(); row++) {
            for (int col = 0; col < 10; col++) {
                XWPFTableCell cell = table.getRow(row + 3).getCell(col);
                cell.setText(data.get(row).get(col));
            }
        }
    }

    private static void addTableContext(XWPFTable table, int row, int col, String text) {
        XWPFTableCell cell = table.getRow(row).getCell(col);
        cell.setText(text);
    }

    /**
     * @author: gch
     * @Description: 添加水平居中韩国样式
     * @Date: 2023/8/19 12:11
     * @Param:
     * @return:
     */
    private void addCellCenterStyle(XWPFTableCell cell) {
        CTTc ctTc = cell.getCTTc();
        //获取 CTP
        CTP ctP = (ctTc.sizeOfPArray() == 0) ?
                ctTc.addNewP() : ctTc.getPArray(0);
        //获取段落
        XWPFParagraph par = cell.getParagraph(ctP);
        //设置水平居中
        par.setAlignment(ParagraphAlignment.CENTER);
        // 垂直居中
        cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);

    }

    /**
     * @Description: 跨列合并
     * table要合并单元格的表格
     * row要合并哪一行的单元格
     * fromCell开始合并的单元格
     * toCell合并到哪一个单元格
     */
    public static  void mergeCellsHorizontal(XWPFTable table, int row, int fromCell, int toCell) {
        for (int cellIndex = fromCell; cellIndex <= toCell; cellIndex++) {
            XWPFTableCell cell = table.getRow(row).getCell(cellIndex);
            if ( cellIndex == fromCell ) {
                // The first merged cell is set with RESTART merge value
                cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);
            } else {
                // Cells which join (merge) the first one, are set with CONTINUE
                cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
            }
        }
    }

    /**
     * @Description: 跨行合并
     * table要合并单元格的表格
     * col要合并哪一列的单元格
     * fromRow从哪一行开始合并单元格
     * toRow合并到哪一个行
     */
    public  void mergeCellsVertically(XWPFTable table, int col, int fromRow, int toRow) {
        for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
            XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
            if ( rowIndex == fromRow ) {
                // The first merged cell is set with RESTART merge value
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
            } else {
                // Cells which join (merge) the first one, are set with CONTINUE
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
            }
        }
    }

    /**
     * @Description: 获取对象属性值并转换成字符串列表
     * @Author: gch
     * @Date: 2023/8/23 10:54
     */
    public static List<String> getAttributeValues(Object obj) {
        Class<?> objClass = obj.getClass();
        List<String> valuesList = new ArrayList<>();
        // 获取对象定义的所有属性
        Field[] fields = objClass.getDeclaredFields();
        // 遍历属性并获取属性值
        for (Field field : fields) {
            field.setAccessible(true); // 设置私有属性可访问
            try {
                Object value = field.get(obj);
                String valueStr = (value != null) ? value.toString() : "";
                valuesList.add(valueStr);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return valuesList;
    }

}

运行结果如下: