使用FastExcel数据导出:官网: https://idev.cn/fastexcel/zh-CN

需求

信用代码、填报人,唯一时,将:信用代码、单位名称、填报人,进行 row 合并,并垂直居中对齐

思路

这边不需要做列合并,所以采用了 RowWriteHandler

思路,

  • 指定唯一值,根据某个或多个单元格确定相当的数据行(代码中的 ExcelCellMergeStrategy. uniqueCol)
  • 判断当前行的唯一列的数据和上一行是否相等,如果相等继续,要合并的行数 mergeCount + 1
  • 如果当前行和上一行不相等,说明前面的数据需要做合并处理了。同时将当前行做为下一次待合并的起始行

实现

Excel导出单元格全量合并策略

package com.vipsoft.handler;

import cn.idev.excel.write.handler.RowWriteHandler;
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
import cn.idev.excel.write.metadata.holder.WriteTableHolder; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import java.util.ArrayList;
import java.util.List; /**
* Excel导出单元格全量合并策略
*/
public class ExcelCellMergeStrategy implements RowWriteHandler { private int mergeRowIndex;//从哪一行开始合并
private List<Integer> mergeColumnIndex = new ArrayList<>();//excel合并的列
private int[] uniqueCol;//合并的唯一标识,根据指定的列,确定数据是否相同
private int totalRow;//总行数 private int lastRow;
private int firstCol;
private int lastCol;
private int firstRow; private int mergeCount = 1; /**
* @param mergeRowIndex
* @param mergeColIndex 支持范围如:0-3,6,9
* @param uniqueCol 唯一标识,1列或多列 数据组成唯一值
* @param totalRow 总行数(从0开始):List.size -1 + 跳过的表头
*/
public ExcelCellMergeStrategy(int mergeRowIndex, Object[] mergeColIndex, int[] uniqueCol, int totalRow) {
this.mergeRowIndex = mergeRowIndex;
for (Object item : mergeColIndex) {
if (item.toString().contains("-")) {
String[] spCol = item.toString().split("-");
int start = Integer.parseInt(spCol[0]);
int end = Integer.parseInt(spCol[1]);
for (int i = start; i <= end; i++) {
mergeColumnIndex.add(i);
}
} else {
int colIndex = Integer.parseInt(item.toString());
mergeColumnIndex.add(colIndex);
} }
this.uniqueCol = uniqueCol;
this.totalRow = totalRow;
} @Override
public void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer rowIndex, Integer relativeRowIndex, Boolean isHead) { } @Override
public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) { } @Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
//当前行
int curRowIndex = row.getRowNum();
//每一行的最大列数
short lastCellNum = row.getLastCellNum();
//当前行为开始合并行时,标记
if (curRowIndex == mergeRowIndex) {
//赋初值 第一行
firstRow = curRowIndex;
}
//开始合并位置
if (curRowIndex > mergeRowIndex && !row.getCell(0).getStringCellValue().equals("")) {
for (int i = 0; i < lastCellNum; i++) {
if (mergeColumnIndex.contains(i)) {
//当前行号 当前行对象 合并的标识位
mergeWithPrevAnyRow(writeSheetHolder.getSheet(), curRowIndex, row, uniqueCol);
break;//已经进入到合并单元格操作里面了,执行一次就行
} }
}
} public void mergeWithPrevAnyRow(Sheet sheet, int curRowIndex, Row row, int[] uniqueCol) {
Object currentData = "";
Object preData = "";
for (int col : uniqueCol) {
currentData = currentData + row.getCell(col).getStringCellValue();
Row preRow = row.getSheet().getRow(curRowIndex - 1);
preData = preData + preRow.getCell(col).getStringCellValue();
} //判断是否合并单元格
boolean curEqualsPre = currentData.equals(preData);
//判断前一个和后一个相同 并且 标识位相同
if (curEqualsPre) {
lastRow = curRowIndex;
mergeCount++;
}
//excel过程中合并
if (!curEqualsPre && mergeCount > 1) {
mergeSheet(firstRow, lastRow, mergeColumnIndex, sheet);
mergeCount = 1;
} //excel结尾处合并
if (mergeCount > 1 && totalRow == curRowIndex) {
mergeSheet(firstRow, lastRow, mergeColumnIndex, sheet);
mergeCount = 1;
}
//重置下一个要合并的行
if (!curEqualsPre) {
firstRow = curRowIndex;
} } private void mergeSheet(int firstRow, int lastRow, List<Integer> mergeColumnIndex, Sheet sheet) {
for (int colNum : mergeColumnIndex) {
firstCol = colNum;
lastCol = colNum;
CellRangeAddress cellRangeAddress = new CellRangeAddress(firstRow, lastRow, firstCol, lastCol);
sheet.addMergedRegion(cellRangeAddress); // 设置合并后的单元格样式为垂直居中
CellStyle style = sheet.getWorkbook().createCellStyle();
style.setVerticalAlignment(VerticalAlignment.CENTER);
//style.setAlignment(HorizontalAlignment.CENTER);
Cell mergedCell = sheet.getRow(firstRow).getCell(colNum, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
mergedCell.setCellStyle(style);
}
}
}

日期格式转换

EasyExcel => FastExcel ,导入支持多种时间格式

package com.vipsoft.base.util;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date; import cn.idev.excel.converters.Converter;
import cn.idev.excel.enums.CellDataTypeEnum;
import cn.idev.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 日期格式转换器
*/
public class ExcelDateConverter implements Converter<Date> {
private static final Logger log = LoggerFactory.getLogger(ExcelDateConverter.class);
// 定义所有要尝试的日期格式
SimpleDateFormat[] formats = {
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"),
new SimpleDateFormat("yyyy/MM/dd"),
new SimpleDateFormat("yyyy-MM-dd"),
new SimpleDateFormat("yyyy-MM"),
new SimpleDateFormat("yyyy/MM"),
new SimpleDateFormat("yyyyMMdd")
}; @Override
public Class<Date> supportJavaTypeKey() {
return Date.class;
} @Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
} @Override
public Date convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) throws Exception {
String cellValue = "";
if (cellData.getType().equals(CellDataTypeEnum.NUMBER)) {
long cellIntValue = cellData.getNumberValue().longValue();
if (cellIntValue > 19900100) {
try {
// 1. 第一种解析,传入的是数字形式的日期,形如yyyyMMdd
SimpleDateFormat originalFormat = new SimpleDateFormat("yyyyMMdd");
return originalFormat.parse(String.valueOf(cellIntValue));
} catch (Exception e) {
log.warn("exception when parse numerical time with format yyyyMMdd");
cellValue=String.valueOf(cellIntValue);
}
} // 2. 第二种解析, excel是从1900年开始计算,最终通过计算与1900年间隔的天数计算目标日期
LocalDate localDate = LocalDate.of(1900, 1, 1); //excel 有些奇怪的bug, 导致日期数差2
localDate = localDate.plusDays(cellIntValue - 2); // 转换为ZonedDateTime(如果需要时区信息)
ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
return Date.from(zonedDateTime.toInstant());
} else if (cellData.getType().equals(CellDataTypeEnum.STRING)) {
// 3. 第三种解析
Date date = null;
cellValue = cellData.getStringValue();
for (SimpleDateFormat format : formats) {
try {
date = format.parse(cellValue);
if (date != null) {
// 这一步是将日期格式化为Java期望的格式
return date;
}
} catch (Exception e) {
// 如果有异常,捕捉异常后继续解析
//log.error(e.getMessage(), e);
}
}
}
// 没转成功,抛出异常
throw new UnsupportedOperationException("The current operation is not supported by the current converter." + cellValue);
} @Override
public WriteCellData<?> convertToExcelData(Date value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateValue = sdf.format(value);
return new WriteCellData<>(dateValue);
}
}

接口代码

导出代码

package com.vipsoft.api.controller;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.util.Map; /**
* 企业信息
*/
@RestController
@RequestMapping("/detail")
public class CooperationDetailController extends BaseController { /**
* 企业信息
*
* @return
*/
@PostMapping("/export")
public void exportInfo(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String, Object> param) {
try {
Page page = buildPage(param, CooperationInfo.class);
QueryWrapper<SysOrganization> queryWrapper = buildQueryWrapper(SysOrganization.class, param);
cooperationDetailService.exportInfo(response, queryWrapper);
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
}
}
}

Service

@Service
public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMapper, SysOrganization> implements ISysOrganizationService { @Override
public void exportInfo(HttpServletResponse response, QueryWrapper<SysOrganization> queryWrapper) {
String templateFileName = "";
try {
templateFileName = cuworConfig.getFilePath() + "/template/企业导出模板.xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和 FastExcel 没有关系
String fileName = URLEncoder.encode("企业数据", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
//获取要导出的数据 DTO
List<SysOrganizationExcelDTO> dataList = data(queryWrapper);
int mergeRowIndex = 2; // 从那一行开始合并 -- 跳过表头
int[] uniqueCol = {0, 7}; //根据指定的列,确定相同的数据
Object[] mergeColIndex = {"0-1", 6, 7}; //需要合并的列
int totalRow = dataList.size() - 1 + mergeRowIndex;
// 这里需要设置不关闭流
ExcelCellMergeStrategy excelCellMergeStrategy = new ExcelCellMergeStrategy(mergeRowIndex, mergeColIndex, uniqueCol, totalRow);
FastExcel.write(response.getOutputStream(), SysOrganizationExcelDTO.class)
.needHead(false)
.withTemplate(templateFileName)
.autoCloseStream(Boolean.FALSE)
.registerWriteHandler(excelCellMergeStrategy) //合并单元格
.sheet("企业数据")
.doWrite(dataList);
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
//异常时,向前端抛出 JSON
ApiResult result = new ApiResult(6001, "下载文件失败 " + templateFileName + " " + e.getMessage());
try {
response.getWriter().println(PojoUtil.pojoToJson(result));
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
throw new CustomException(ex.getMessage());
}
}
} /**
* 获得要到出的数据
*/
private List<SysOrganizationExcelDTO> data(QueryWrapper<SysOrganization> queryWrapper) {
IPage list = this.page(new Page(1, 10000), queryWrapper);
List<SysOrganizationExcelDTO> result = new ArrayList<>();
for (Object obj : list.getRecords()) {
if (obj instanceof SysOrganization) {
SysOrganization item = (SysOrganization) obj;
SysOrganizationExcelDTO info = new SysOrganizationExcelDTO();
BeanUtils.copyProperties(item, info);
//组装数据
result.add(info);
}
}
return result;
}
}

DTO

package com.vipsoft.base.dto;

import cn.idev.excel.annotation.ExcelIgnore;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.format.DateTimeFormat;
import com.vipsoft.base.util.ExcelDateConverter; import java.io.Serializable;
import java.util.Date; /**
* Excel 导出使用
*/
public class SysOrganizationExcelDTO implements Serializable { /**
* 统一社会信用代码
*/
//@ExcelProperty(value = "统一社会信用代码")
@ExcelProperty(index = 0)
private String unifiedSocialCode; /**
* 机构名称
*/
@ExcelProperty(index = 1)
private String orgName; /**
* 岗位大类名称
*/
@ExcelProperty(index = 2)
private String jobBigName;
/**
* 岗位中类名称
*/
@ExcelProperty(index = 3)
private String jobMiddleName;
/**
* 岗位小类名称
*/
@ExcelProperty(index = 4)
private String jobSmallName;
/**
* 岗位数量
*/
@ExcelProperty(index = 5)
private Integer jobQty; /**
* 填报日期*
*/
@ExcelProperty(index = 6, converter = ExcelDateConverter.class)
private Date inputDate;
/**
* 填报人
*/
@ExcelProperty(index = 7)
private String inputUser; ......省略get set }

FastExcel 合并单元格(相当的行数据,进行合并)的更多相关文章

  1. DataGridView合并单元格(多行多列合并)

    一.点击在拖入的显示控件(TreeList)右上方的箭头,在Treelist任务中选择数据源,添加项目数据源,依次选择数据库.数据集,新建连接,浏览选择数据库(*.mdb),依次点击 下一步,选择“表 ...

  2. 实操记录之-----Ant Design of Vue 增强版动态合并单元格,自动根据数据进行合并,可自定义横纵向合并

    前几天搞了个简易版的动态合并单元格 但是需求有变化,就只能稍微改改了~~ 欢迎路过的各位大佬指出我代码的问题~~~~ 另: 代码执行效率不是很高,如果需要大量渲染更多数据建议可以直接使用原生 < ...

  3. gridview 合并单元格 并原样导出数据

    使用的方式都是比较简单的,asp.net 如何进行数据的导出有好多种方法,大家可以在网上找到, 一下提供一些合并并原样输出的一个简单的代码: public void ToExcel(System.We ...

  4. Aspose.Cells 首次使用,用到模版填充数据,合并单元格,换行

    Aspose.Cells 首次使用,用到模版填充数据,合并单元格,换行 模版格式,图格式是最简单的格式,但实际效果不是这种,实际效果图如图2 图2 ,注意看红色部分,一对一是正常的,但是有一对多的订单 ...

  5. 议:如何将树形菜单形式的数据转化成HTML的二维表(相同内容需合并单元格)

    一般做OA类管理系统,经常涉及到“组织架构”的概念,那么像这种有上下层级关系的数据一般会做成树形菜单的方式显示,底层代码必定会用到递归算法.这篇随笔的目的就是要谈谈除了用树形菜单来显示这种上下层级关系 ...

  6. layui:数据表格如何合并单元格

    layui.use('table', function () { var table = layui.table; table.render({ elem: '#applyTab' , url: '$ ...

  7. poi获取合并单元格内的第一行第一列的值

    当读取如图所示的excel时,显示为第1行 第1列 的内容是:合并单元格 其它在合并单元格区域内的单元格不显示 示例代码如下: import java.io.FileInputStream; impo ...

  8. 雷林鹏分享:jQuery EasyUI 数据网格 - 合并单元格

    jQuery EasyUI 数据网格 - 合并单元格 数据网格(datagrid)经常需要合并一些单元格.本教程将向您展示如何在数据网格(datagrid)中合并单元格. 为了合并数据网格(datag ...

  9. Apache POI 合并单元格

    合并单元格所使用的方法: sheet.addMergedRegion( CellRangeAddress  cellRangeAddress  );   CellRangeAddress  对象的构造 ...

  10. 【记录】解析具有合并单元格的Excel

    最近公司让做各种数据表格的导入导出,就涉及到电子表格的解析,做了这么多天总结一下心得. 工具:NOPI 语言:C# 目的:因为涉及到导入到数据库,具有合并单元格的多行必然要拆分,而NPOI自动解析的时 ...

随机推荐

  1. thinkphp在原字段上面进行加减操作

    经常有需要对某个数据表的计数字段进行加减操作,我们来看下在ThinkPHP中的具体使用办法. 最简单的,使用下面方法对score自加,第二个参数也可以不要,默认加1:   M('User')-> ...

  2. 【USB3.0协议学习】Topic2·USB3.0的LTSSM分析

    一.什么是LTSSM,处于USB层次中的哪个位置? LTSSM是链路训练状态机的简称,位于USB3.0协议的link layer,共有12种状态,在链路的两端,也就是Downstream port和U ...

  3. Android复习(五)设备兼容—>屏幕适配

    1. 适配使用的布局 目前版本Google还是希望开发者通过 ConstraintLayout 布局完成适配 2. 对于特定屏幕 创建备用布局,即在res/layout/目录下创建对应尺寸的布局文件 ...

  4. Monaco Editor 实现一个日志查看器

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值. 本文作者:文长 前言 在 Web IDE 中,控制台中展示日志是至关 ...

  5. KubeSphere 宣布开源 Thanos 的企业级发行版 Whizard

    日前,青云科技宣布开源 Thanos 的企业级发行版 Whizard,为企业带来真正高可用.可扩展.可存储与查询海量监控数据.易运维.安全的 Prometheus 长期存储方案. Prometheus ...

  6. OpenCv Mat 数据结构

    前言 OpenCv的Mat数据结构可以存储图片信息.但是以坐标系构建来说,Mat是以左上角为原点,而我们自己的日常习惯是以左下角为原点. 本文提供了这两者之间的一种转换. 假设 Mat : (x,y) ...

  7. hbase的架构

    HBase中的存储包括HMaster.HRegionSever.HRegion.HLog.Store.MemStore.StoreFile.HFile等角色构成,具体如下HMaster的作用 1.为H ...

  8. Mysql数据库个人整理笔记

    数据类型 tinyint/smallint/int/bigint float double char/varchar date/time/datetime/timestamp DDL 数据库 crea ...

  9. 3. jenkins的管理

    1. jenkins的插件管理 ​ Jenkins本身不提供很多功能,我们可以通过使用插件来满足我们的使用.例如从Gitlab拉取代码,使用Maven构建项目等功能需要依靠插件完成.接下来演示如何下载 ...

  10. C++之OpenCV入门到提高005:005 图像操作

    一.介绍 今天是这个系列<C++之 Opencv 入门到提高>得第五篇文章.这篇文章也不难,介绍如何图像的基本操作,比如:读取一张图片的像素值,如何修改一张图片中的像素值,如何读取一张图片 ...