EasyExcel 模板导出数据 + 自定义策略(合并单元格)

需求:

数据库里的主表+明细表,联查出数据并导出Excel,合并主表数据的单元格。

代码:

controller

    @PostMapping("export")@ApiOperation(value = "导出数据")protected void export(@ApiParam @Valid @RequestBody NewWmsExceptionCaseSearchCondition request, HttpServletResponse response) throws IOException {getService().export(request, response);}

service

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.ctsfreight.oseb.common.strategy.CustomRowMergeStrategy;
import com.ctsfreight.oseb.common.utils.TokenUtil;
import com.ctsfreight.oseb.common.vo.*;
import com.ctsfreight.oseb.common.vo.excel.ExceptionExcelVo;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.ResourceLoader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;@Resourceprivate ResourceLoader resourceLoader;private final String TEMPLATE_EXCEPTION_EXCEL_XLSX = "classpath:template/exception_excel.xlsx";@Overridepublic void export(NewWmsExceptionCaseSearchCondition request, HttpServletResponse response) throws IOException {String fileName = "明细_" + LocalDateTime.now();response.setContentType("application/vnd.ms-excel;charset=utf-8");response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(fileName + ".xlsx", "utf-8"));String template = TEMPLATE_EXCEPTION_EXCEL_XLSX;InputStream inputStream = resourceLoader.getResource(template).getInputStream();File xlsx = null;try {ByteArrayOutputStream bos = new ByteArrayOutputStream();List<ExceptionExcelVo> crossdockSeaFinanceVoList = baseMapper.listExceptionExcelVo(request);if (CollectionUtils.isNotEmpty(crossdockSeaFinanceVoList)) {AtomicInteger index = new AtomicInteger(0);AtomicReference<String> lastId = new AtomicReference<>("");crossdockSeaFinanceVoList.forEach(item -> {String currentId = item.getId();if (!lastId.get().equals(currentId)) {index.set(index.get() + 1);lastId.set(currentId);}item.setId(String.valueOf(index.get()));});ExcelWriter excelWriter = EasyExcel.write(bos).registerWriteHandler(new CustomRowMergeStrategy(ExceptionExcelVo.class)).withTemplate(inputStream).build();WriteSheet writeSheet = EasyExcel.writerSheet(0).build();excelWriter.write(crossdockSeaFinanceVoList, writeSheet);excelWriter.finish();}InputStreamSource inputStreamSource = new ByteArrayResource(bos.toByteArray());xlsx = File.createTempFile("明细_" + UUID.randomUUID(), ".xlsx");FileUtils.copyInputStreamToFile(inputStreamSource.getInputStream(), xlsx);IOUtils.copy(inputStreamSource.getInputStream(), response.getOutputStream());} catch (Exception e) {log.error("export error", e);throw new ApiException(ResultCode.FAULT);} finally {if (xlsx != null) {xlsx.delete();}inputStream.close();}}

这里的

template 是放在了src/main/resources/template/delivery_export_en.xlsx

xml:

    <select id="listExceptionExcelVo" resultType="com.ctsfreight.oseb.common.vo.excel.ExceptionExcelVo">SELECT ecs.id AS id,ecs.order_no       AS orderNo,ecs.container_no   AS containerNo,ecs.total_amount   AS totalAmount,ecsit.sort_note    AS sortNote,ecsit.consignee_name AS consigneeName,ecsit.fba_id       AS fbaId,ecsit.fba_number   AS fbaNumber,ecsit.package_num  AS packageNumFROM (SELECT id, order_no, container_no, total_amount, create_timeFROM exception_case_summaryWHERE delete_flag = 0<if test="request.summaryIdList != null and !request.summaryIdList.isEmpty()">AND id IN<foreach item="id" collection="request.summaryIdList" open="(" separator="," close=")">#{id}</foreach></if>ORDER BY create_time DESCLIMIT 100) ecsLEFT JOIN exception_case_sorting_item ecsitON ecs.id = ecsit.exception_case_summary_idWHERE ecsit.delete_flag = 0ORDER BY ecs.create_time DESC;</select>
LIMIT 100,是为了查询最新的100条数据,不然后面数据太多了

vo:

package com.ctsfreight.oseb.common.vo.excel;import com.alibaba.excel.annotation.ExcelProperty;
import com.ctsfreight.oseb.common.strategy.annotations.CustomRowMerge;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;/*** <p>*  信息VO* </p>***/
@Data
@Accessors(chain = true)
@ApiModel(value = "信息VO")
public class ExceptionExcelVo {@ApiModelProperty("主表id")@ExcelProperty(index = 0)@CustomRowMerge(needMerge = true, isPk = true)private String id;@ApiModelProperty("号")@ExcelProperty(index = 1)@CustomRowMerge(needMerge = true)private String containerNo;@ApiModelProperty("单号")@ExcelProperty(index = 2)@CustomRowMerge(needMerge = true)private String orderNo;@ApiModelProperty("总箱数")@ExcelProperty(index = 3)@CustomRowMerge(needMerge = true)private Integer totalAmount;@ApiModelProperty("标")@ExcelProperty(index = 4)private String sortNote;@ApiModelProperty("")@ExcelProperty(index = 5)private String consigneeName;@ApiModelProperty("")@ExcelProperty(index = 6)private String fbaId;@ApiModelProperty("")@ExcelProperty(index = 7)private String fbaNumber;@ApiModelProperty("箱数")@ExcelProperty(index = 8)private Integer packageNum;}

自定义单元格合并策略:

package com.ctsfreight.oseb.common.strategy;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.ctsfreight.oseb.common.strategy.annotations.CustomRowMerge;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;/*** 自定义单元格合并策略*/
public class CustomRowMergeStrategy implements RowWriteHandler {/*** 主键下标集合*/private List<Integer> pkColumnIndex = new ArrayList<>();/*** 需要合并的列的下标集合*/private List<Integer> needMergeColumnIndex = new ArrayList<>();/*** DTO数据类型*/private Class<?> elementType;public CustomRowMergeStrategy(Class<?> elementType) {this.elementType = elementType;}@Overridepublic void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {// 如果是标题,则直接返回if (isHead) {return;}// 获取当前sheetSheet sheet = writeSheetHolder.getSheet();// 获取标题行Row titleRow = sheet.getRow(0);if (pkColumnIndex.isEmpty()) {this.lazyInit(writeSheetHolder);}// 判断是否需要和上一行进行合并// 不能和标题合并,只能数据行之间合并if (row.getRowNum() <= 1) {return;}// 获取上一行数据Row lastRow = sheet.getRow(row.getRowNum() - 1);// 将本行和上一行是同一类型的数据(通过主键字段进行判断),则需要合并boolean margeBol = true;for (Integer pkIndex : pkColumnIndex) {String lastKey = lastRow.getCell(pkIndex).getCellType() == CellType.STRING ? lastRow.getCell(pkIndex).getStringCellValue() : String.valueOf(lastRow.getCell(pkIndex).getNumericCellValue());String currentKey = row.getCell(pkIndex).getCellType() == CellType.STRING ? row.getCell(pkIndex).getStringCellValue() : String.valueOf(row.getCell(pkIndex).getNumericCellValue());if (!StringUtils.equalsIgnoreCase(lastKey, currentKey)) {margeBol = false;break;}}if (margeBol) {for (Integer needMerIndex : needMergeColumnIndex) {CellRangeAddress cellRangeAddress = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(),needMerIndex, needMerIndex);sheet.addMergedRegionUnsafe(cellRangeAddress);}}}/*** 初始化主键下标和需要合并字段的下标*/private void lazyInit(WriteSheetHolder writeSheetHolder) {// 获取当前sheetSheet sheet = writeSheetHolder.getSheet();// 获取标题行Row titleRow = sheet.getRow(0);// 获取DTO的类型Class<?> eleType = this.elementType;// 获取DTO所有的属性Field[] fields = eleType.getDeclaredFields();int i = 0;// 遍历所有的字段,因为是基于DTO的字段来构建excel,所以字段数 >= excel的列数for (Field theField : fields) {// 获取@ExcelProperty注解,用于获取该字段对应在excel中的列的下标ExcelProperty easyExcelAnno = theField.getAnnotation(ExcelProperty.class);// 为空,则表示该字段不需要导入到excel,直接处理下一个字段if (null == easyExcelAnno) {continue;}// 获取自定义的注解,用于合并单元格CustomRowMerge customMerge = theField.getAnnotation(CustomRowMerge.class);// 没有@CustomMerge注解的默认不合并if (null == customMerge) {continue;}// 判断是否有主键标识if (customMerge.isPk()) {pkColumnIndex.add(i);}// 判断是否需要合并if (customMerge.needMerge()) {needMergeColumnIndex.add(i);}i++;}// 没有指定主键,则异常if (pkColumnIndex.isEmpty()) {throw new IllegalStateException("使用@CustomMerge注解必须指定主键");}}
}

效果图:

拓展:

可以增加居中策略

可以通过 EasyExcel 的 WriteHandlerAbstractCellStyleStrategy 来设置 Excel 单元格内容的 水平居中垂直居中

使用 WriteHandler 自定义单元格样式

你可以创建一个继承自 AbstractCellStyleStrategyAbstractCellWriteHandler 的类,设置单元格样式。

import com.alibaba.excel.write.handler.AbstractCellStyleStrategy;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;public class CenterCellStyleStrategy extends AbstractCellStyleStrategy {@Overrideprotected void setHeadCellStyle(Cell cell, Head head, Integer relativeRowIndex) {// 如果你也希望表头居中,可以在这里设置setCellStyle(cell);}@Overrideprotected void setContentCellStyle(Cell cell, Head head, Integer relativeRowIndex) {setCellStyle(cell);}private void setCellStyle(Cell cell) {Workbook workbook = cell.getSheet().getWorkbook();CellStyle cellStyle = workbook.createCellStyle();// 设置水平居中cellStyle.setAlignment(HorizontalAlignment.CENTER);// 设置垂直居中cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 可选:自动换行cellStyle.setWrapText(true);cell.setCellStyle(cellStyle);}
}

注册样式策略到导出逻辑中

ExcelWriter excelWriter = EasyExcel.write(bos).registerWriteHandler(new CenterCellStyleStrategy()) // 设置居中样式.registerWriteHandler(new CustomRowMergeStrategy(Arrays.asList("containerNo", "orderNo", "totalAmount", "sortNote", "consigneeName"))).withTemplate(inputStream).build();

如果你只想对某些列设置居中(可选)

你可以修改 setCellStyle 方法,根据 cell.getColumnIndex() 判断是否对某些列应用居中

private void setCellStyle(Cell cell) {Workbook workbook = cell.getSheet().getWorkbook();CellStyle cellStyle = workbook.createCellStyle();// 只对第 0 列(柜号)和第 2 列(登记总箱数)设置居中if (cell.getColumnIndex() == 0 || cell.getColumnIndex() == 2) {cellStyle.setAlignment(HorizontalAlignment.CENTER);cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);cellStyle.setWrapText(true);} else {// 其他列左对齐cellStyle.setAlignment(HorizontalAlignment.LEFT);cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);}cell.setCellStyle(cellStyle);
}

如果你使用的是 .xlsx 模板,并希望保留模板样式

你可以这样设置:

// 从模板中读取样式,避免覆盖原有样式
CellStyle originalStyle = cell.getCellStyle();CellStyle newStyle = workbook.createCellStyle();
newStyle.cloneStyleFrom(originalStyle); // 复制原样式
newStyle.setAlignment(HorizontalAlignment.CENTER);
newStyle.setVerticalAlignment(VerticalAlignment.CENTER);
newStyle.setWrapText(true);cell.setCellStyle(newStyle);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90405.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90405.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

股指期权可以随时平仓吗?

本文主要介绍股指期权可以随时平仓吗&#xff1f;股指期权是否可以随时平仓&#xff0c;需结合交易规则、合约状态及市场流动性综合判断&#xff0c;具体如下。股指期权可以随时平仓吗&#xff1f;一、正常交易时间内的平仓规则在交易日的交易时段内&#xff08;如国内上证50ET…

成品电池综合测试仪:保障电池品质与安全的核心工具|深圳鑫达能

随着新能源汽车、储能系统、消费电子等领域的快速发展&#xff0c;电池作为核心能源组件&#xff0c;其性能与安全性直接关系到产品的整体质量与用户体验。成品电池综合测试仪作为电池生产与质检环节的关键设备&#xff0c;通过模拟真实使用场景&#xff0c;对电池的电气性能、…

智慧工厂网络升级:新型 SD-WAN 技术架构与应用解析

1. 智慧工厂对网络的核心需求智慧工厂的网络需求高度复杂&#xff0c;主要体现在以下几个方面&#xff1a;高可靠性与低延迟工厂中的生产执行系统&#xff08;MES&#xff09;、设备监控系统&#xff08;如 PLC/SCADA&#xff09;、产品生命周期管理系统&#xff08;PLM&#x…

在 Windows 使用 Nginx/HAProxy 实现负载均衡

在本实验中&#xff0c;我们将在 Windows 系统 上使用 Python 编写一个 TCP 服务器&#xff0c;并启动两个服务实例。然后使用 Nginx 或 HAProxy 作为负载均衡器&#xff0c;将来自多个客户端的请求分发到这两个服务实例上&#xff0c;验证负载均衡效果。 &#x1f9e9; 环境准…

【物联网】基于树莓派的物联网开发【17】——物联网通信协议MQTT基础知识

使用背景 MQTT最初是为了解决物联网&#xff08;IoT&#xff09;领域设备之间的低带宽、高延迟、不稳定网络连接等问题而设计的。 场景介绍 广泛应用物联网领域&#xff0c;数据实时传输&#xff0c;连接各种智能设备和应用的关键桥梁 MQTT简介和概述 MQTT&#xff08;Message …

【qml-3】qml与c++交互第二次尝试(类型方式)

背景&#xff1a; 【qml-1】qml与c交互第一次尝试&#xff08;实例方式&#xff09; 【qml-2】尝试一个有模式的qml弹窗-CSDN博客 【qml-3】qml与c交互第二次尝试&#xff08;类型方式&#xff09; 还是qml学习笔记。 这次搁置太久了。其实不太会&#xff0c;还是以教程为主…

输电线路观冰精灵在线监测装置:科技赋能电网安全的新利器

一、技术架构与工作原理输电线路观冰精灵在线监测装置&#xff08;简称“观冰精灵”&#xff09;是一款集成多源感知、智能分析、远程通信于一体的专业化覆冰监测设备。其核心功能通过以下技术路径实现&#xff1a;1. 数据采集模块视觉识别系统&#xff1a;搭载工业级夜视摄像机…

Ubuntu22 上,用C++ gSoap 创建一个简单的webservice

创建calc.h// calc.h // gSOAP 服务定义 int ns__add(double a, double b, double &result); int ns__subtract(double a, double b, double &result);创建my_server.cpp#include "soapService.h" #include "ns.nsmap" class MyService : public S…

Java(LinkedList和ArrayList底层分析)

LinkedList全面说明:LinkedList底层操作机制:LinkedList的方法:add():增加节点对象remove():删除一个节点对象(默认删除第一个节点对象)set():修改一个节点对象get():得到一个节点对象LinkedList的遍历:增强for循环迭代器普通for循化LinkedList的源码解读:增加源码:1. LinkedLi…

开源项目XBuilder的user逻辑

stores \ userquery-keys.ts 统一管理Vue Query&#xff08;TanStack Query的Vue适配版本&#xff09;缓存键&#xff0c;在下面的文件中复用index.ts 入口文件&#xff0c;统一用户信息查询signed-in.ts 登录状态管理、认证逻辑在用户登录后&#xff0c;系统颁发一个令牌&…

第十五章 SEO的简单免费工具

SEO的基础工具和检测 前文中主要是讲一些SEO的网站基本功&#xff0c;而在这一章那&#xff0c;会讲到一些非常基本的工具&#xff0c;主要是关于&#xff1a;网站的流量、停留时长、关键词密度、内容、以及Google的站长工具。 Google Search Console Google Search Console这是…

SSL 证书与 HTTPS 的关系:一文理清核心关联

HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff09;和 SSL 证书&#xff08;Secure Sockets Layer Certificate&#xff09;是网络安全的两大基石&#xff0c;它们共同保障了互联网通信的安全性和可信度。以下从定义、功能、关系及实际应用层面进行解析&#xf…

使用Jmeter参数化实现接口自动化测试

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 本文记录如何使用Jmeter参数化&#xff08;csv)实现接口自动化——测试Token不同入参情况下&#xff0c;接口请求能够返回正确的结果1. 首先需要使用Jmeter获取一个…

X-plore File Manager v4.34.02 修改版:安卓设备上的全能文件管理器

在使用安卓设备时&#xff0c;文件管理是日常操作中不可或缺的一部分。X-plore File Manager 作为一款功能强大的文件管理器&#xff0c;凭借其丰富的功能和便捷的操作&#xff0c;成为安卓用户管理文件的首选工具之一。最新版 v4.34.02 修改版更是解锁了更多高级功能&#xff…

React+threejs两种3D多场景渲染方案

在现代 Web 开发中&#xff0c;3D 可视化需求日益增长&#xff0c;特别是在 React 生态系统中实现多 3D 场景的展示与交互。本文通过对比两种实现方案&#xff0c;探讨 React 中构建多 3D 场景的最佳实践&#xff0c;分析它们的技术特点、性能表现和适用场景。方案一&#xff1…

React性能优化终极指南:memo、useCallback、useMemo全解析

掌握 React.memo、useCallback、useMemo 的正确使用姿势&#xff0c;让你的 React 应用性能飞起来&#xff01; &#x1f3af; React.memo 作用 React.memo 是一个高阶组件&#xff0c;用于函数组件&#xff0c;通过浅比较 props 的变化来决定是否重新渲染。如果 props 没有变…

借助 VR 消防技术开展应急演练,检验完善应急预案​

应急演练是企业应对火灾事故的重要手段&#xff0c;而 VR 消防技术的应用&#xff0c;为应急演练带来了全新的体验和更高的效率。VR 消防技术通过虚拟现实技术模拟逼真的火灾场景&#xff0c;让参与者能够身临其境地感受火灾发生时的紧张氛围。某知名物流企业&#xff0c;仓库众…

【电赛学习笔记】MaxiCAM 项目实践——二维云台追踪指定目标

前言 本文是对视觉模块MaixCam实现二维云台人脸跟踪_哔哩哔哩_bilibili大佬的项目实践整理与拓展&#xff0c;侵权即删。 单路舵机基本控制 #导入必要模块 from maix import pwm, time , pinmap#定义全局变量&#xff0c;设初值 SERVO_FREQ 50 #主频 SERVO_MIN_DUT…

深入解析 ArkUI 触摸事件机制:从点击到滑动的开发全流程

摘要 随着 HarmonyOS NEXT 的不断发展&#xff0c;ArkUI 逐渐成为主流的 UI 构建方式。而用户交互在任何应用中都是基础而又关键的一环&#xff0c;如何利用 ArkUI 提供的触摸事件机制&#xff0c;如 onTouch、onClick、onSwipe 等&#xff0c;来实现自然、顺滑、用户友好的交互…

Tailwind CSS 自定义工具类与主题配置指南

一、自定义工具类配置在 src/tailwind.css 文件中&#xff0c;我们可以通过 layer utilities 指令添加自定义工具类&#xff1a;tailwind base; tailwind components; tailwind utilities;layer utilities {/* 自定义工具 上下浮动效果 */.animate-floatY {animation: floatY 3…