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
英文地址,請注明出處:http://en.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…