Merge branch 'dev'

This commit is contained in:
haomingming 2026-01-04 14:40:31 +08:00
commit eb69b60ec4
2 changed files with 299 additions and 90 deletions

View File

@ -8,7 +8,7 @@ import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import net.lab1024.sa.admin.module.app.medicalrecord.service.MedicalRecordService;
import net.lab1024.sa.admin.module.business.caseplatformcase.converter.CustomExcelWriteHandler;
import net.lab1024.sa.admin.module.business.caseplatformcase.domain.form.CaseplatformCaseQueryForm;
import net.lab1024.sa.admin.module.business.caseplatformcase.domain.vo.EasyExcelCaseDetailVO;
import net.lab1024.sa.admin.module.business.caseplatformcase.domain.vo.ExportExpertCaseExcelVo;
@ -19,15 +19,14 @@ import net.lab1024.sa.common.module.support.operatelog.annoation.OperateLog;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.FileOutputStream;
import java.net.URLEncoder;
import java.util.*;
@ -107,9 +106,6 @@ public class CaseplatformCaseExcelController {
double totalTax = processedList.stream().mapToDouble(e -> Optional.ofNullable(e.getTaxAmount()).orElse(0.0)).sum();
double totalTotal = processedList.stream().mapToDouble(e -> Optional.ofNullable(e.getTotalAmount()).orElse(0.0)).sum();
String filePath = "./人工肝诊疗专家劳务费.xlsx";
FileOutputStream outputStream = new FileOutputStream(filePath);
// 字段标题
List<List<String>> head = Arrays.asList(
Collections.singletonList("序号"),
@ -145,11 +141,14 @@ public class CaseplatformCaseExcelController {
contentStyle.setBorderLeft(BorderStyle.THIN);
contentStyle.setBorderRight(BorderStyle.THIN);
// ExcelWriter writer = EasyExcel.write(outputStream, ExportExpertCaseExcelVo.class)
// 使用 CustomExcelWriteHandler 在正确的时机创建标题行和合计行
CustomExcelWriteHandler customHandler = new CustomExcelWriteHandler(totalActual, totalTax, totalTotal, processedList.size());
ExcelWriter writer = EasyExcel.write(response.getOutputStream(), ExportExpertCaseExcelVo.class)
.head(head)
.excelType(ExcelTypeEnum.XLSX)
.registerWriteHandler(new HorizontalCellStyleStrategy(headStyle, contentStyle))
.registerWriteHandler(customHandler)
.build();
WriteSheet writeSheet = EasyExcel.writerSheet("劳务费明细")
@ -160,90 +159,130 @@ public class CaseplatformCaseExcelController {
// 写数据
writer.write(processedList, writeSheet);
// 合并大标题
Workbook workbook = (Workbook) writer.writeContext().writeWorkbookHolder().getWorkbook();
Sheet sheet = workbook.getSheetAt(0);
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, head.size() - 1));
//Row titleRow = sheet.createRow(0);
Row titleRow = sheet.getRow(0);
if (titleRow == null) {
titleRow = sheet.createRow(0);
}
titleRow.setHeightInPoints(30); // 设置标题行高
Cell titleCell = titleRow.createCell(0);
titleCell.setCellValue("人工肝诊疗病例征集项目专家劳务费表");
CellStyle titleStyle = workbook.createCellStyle();
Font font = workbook.createFont();
font.setFontHeightInPoints((short) 16);
font.setBold(true);
titleStyle.setFont(font);
titleStyle.setAlignment(HorizontalAlignment.CENTER);
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
titleStyle.setBorderRight(BorderStyle.THIN); // 最后一格加右边框
titleCell.setCellStyle(titleStyle);
// === 设置行高 ===
sheet.getRow(1).setHeightInPoints(25); // 表头行
for (int i = 2; i <= processedList.size() + 2; i++) {
Row row = sheet.getRow(i);
if (row != null) {
row.setHeightInPoints(22);
// 设置标题和合计行在数据写入后finish 之前
try {
Workbook workbook = writer.writeContext().writeWorkbookHolder().getWorkbook();
Sheet sheet = workbook.getSheetAt(0);
// 确保标题行存在并设置标题
Row titleRow = sheet.getRow(0);
if (titleRow == null) {
titleRow = sheet.createRow(0);
}
}
// === 设置列宽 ===
int[] columnWidths = {
6, 25, 15, 15, 25, 10, 10, 30, 8, 8, 30, 25, 30, 15
};
for (int i = 0; i < columnWidths.length; i++) {
sheet.setColumnWidth(i, columnWidths[i] * 256); // 256 Excel 单位
}
// 写合计行
int totalRowIndex = processedList.size() + 2; // +1是数据从第3行开始+2是表头行数
Row totalRow = sheet.createRow(totalRowIndex);
totalRow.setHeightInPoints(22);
// 合计行样式黑底白字
CellStyle totalStyle = workbook.createCellStyle();
Font totalFont = workbook.createFont();
totalFont.setBold(true);
totalFont.setColor(IndexedColors.WHITE.getIndex());
totalStyle.setFont(totalFont);
totalStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
totalStyle.setAlignment(HorizontalAlignment.CENTER);
totalStyle.setVerticalAlignment(VerticalAlignment.CENTER);
totalStyle.setBorderTop(BorderStyle.THIN);
totalStyle.setBorderBottom(BorderStyle.THIN);
totalStyle.setBorderLeft(BorderStyle.THIN);
totalStyle.setBorderRight(BorderStyle.THIN);
// 合并前3列合计
sheet.addMergedRegion(new CellRangeAddress(totalRowIndex, totalRowIndex, 0, 5));
Cell cell0 = totalRow.createCell(0);
cell0.setCellValue("合计");
// 设置边框样式
CellStyle borderStyle = workbook.createCellStyle();
borderStyle.setBorderBottom(BorderStyle.THIN);
borderStyle.setBorderTop(BorderStyle.THIN);
borderStyle.setBorderLeft(BorderStyle.THIN);
borderStyle.setBorderRight(BorderStyle.THIN);
borderStyle.setAlignment(HorizontalAlignment.CENTER);
// 创建合计单元格并添加边框
for (int i = 0; i < head.size(); i++) {
Cell cell = totalRow.getCell(i);
if (cell == null) {
cell = totalRow.createCell(i);
titleRow.setHeightInPoints(30);
Cell titleCell = titleRow.getCell(0);
if (titleCell == null) {
titleCell = titleRow.createCell(0);
}
cell.setCellStyle(borderStyle);
}
titleCell.setCellValue("人工肝诊疗病例征集项目专家劳务费表");
// 合并标题单元格
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, head.size() - 1));
// 设置标题样式
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setFontHeightInPoints((short) 16);
titleFont.setBold(true);
titleStyle.setFont(titleFont);
titleStyle.setAlignment(HorizontalAlignment.CENTER);
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
titleStyle.setBorderBottom(BorderStyle.THIN);
titleStyle.setBorderTop(BorderStyle.THIN);
titleStyle.setBorderLeft(BorderStyle.THIN);
titleStyle.setBorderRight(BorderStyle.THIN);
titleCell.setCellStyle(titleStyle);
int totalRowIndex = 2 + processedList.size(); // 0=大标题1=表头2~1+size=数据2+size=合计
Row totalRow = null;
// 如果是 SXSSFSheet需要特殊处理
if (sheet instanceof SXSSFSheet) {
SXSSFSheet sxssfSheet = (SXSSFSheet) sheet;
// 先刷新所有已写入的行到磁盘
sxssfSheet.flushRows();
// 然后尝试创建新行
try {
totalRow = sxssfSheet.createRow(totalRowIndex);
} catch (IllegalArgumentException e) {
// 如果行已经存在尝试获取它
totalRow = sxssfSheet.getRow(totalRowIndex);
if (totalRow == null) {
// 如果获取失败尝试使用随机访问窗口
sxssfSheet.setRandomAccessWindowSize(-1); // 禁用随机访问窗口限制
totalRow = sxssfSheet.createRow(totalRowIndex);
}
}
} else {
// 普通 Sheet直接创建或获取
totalRow = sheet.getRow(totalRowIndex);
if (totalRow == null) {
totalRow = sheet.createRow(totalRowIndex);
}
}
if (totalRow == null) {
throw new RuntimeException("无法创建合计行");
}
totalRow.setHeightInPoints(22);
// 实发 G = index 6个税I = 8应发J = 9
totalRow.getCell(6).setCellValue(totalActual);
totalRow.getCell(8).setCellValue(totalTax);
totalRow.getCell(9).setCellValue(totalTotal);
// 合计行样式黑底白字
CellStyle totalStyle = workbook.createCellStyle();
Font totalFont = workbook.createFont();
totalFont.setBold(true);
totalFont.setColor(IndexedColors.BLACK.getIndex());
totalStyle.setFont(totalFont);
totalStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
totalStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
totalStyle.setAlignment(HorizontalAlignment.CENTER);
totalStyle.setVerticalAlignment(VerticalAlignment.CENTER);
totalStyle.setBorderTop(BorderStyle.THIN);
totalStyle.setBorderBottom(BorderStyle.THIN);
totalStyle.setBorderLeft(BorderStyle.THIN);
totalStyle.setBorderRight(BorderStyle.THIN);
// 合并前6列写"合计"
sheet.addMergedRegion(new CellRangeAddress(totalRowIndex, totalRowIndex, 0, 5));
Cell totalCell = totalRow.createCell(0);
totalCell.setCellValue("合计");
totalCell.setCellStyle(totalStyle);
// 实发个税应发赋值+样式
Cell actualCell = totalRow.createCell(6);
actualCell.setCellValue(totalActual);
actualCell.setCellStyle(totalStyle);
Cell taxCell = totalRow.createCell(8);
taxCell.setCellValue(totalTax);
taxCell.setCellStyle(totalStyle);
Cell totalAmtCell = totalRow.createCell(9);
totalAmtCell.setCellValue(totalTotal);
totalAmtCell.setCellStyle(totalStyle);
// 其他列补充边框
CellStyle borderStyle = workbook.createCellStyle();
borderStyle.setBorderBottom(BorderStyle.THIN);
borderStyle.setBorderTop(BorderStyle.THIN);
borderStyle.setBorderLeft(BorderStyle.THIN);
borderStyle.setBorderRight(BorderStyle.THIN);
borderStyle.setAlignment(HorizontalAlignment.CENTER);
for (int i = 0; i < head.size(); i++) {
if (i == 0 || i == 6 || i == 8 || i == 9) {
continue;
}
Cell cell = totalRow.getCell(i);
if (cell == null) {
cell = totalRow.createCell(i);
}
cell.setCellStyle(borderStyle);
}
} catch (Exception e) {
// 如果创建合计行失败记录错误但不影响导出
e.printStackTrace();
}
// 6通知浏览器以附件的形式下载处理设置返回头要注意文件名有中文
String fileName = URLEncoder.encode("病例数据", "UTF-8").replaceAll("\\+", "%20");
@ -252,8 +291,6 @@ public class CaseplatformCaseExcelController {
response.setCharacterEncoding("utf-8");
writer.finish();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}

View File

@ -0,0 +1,172 @@
package net.lab1024.sa.admin.module.business.caseplatformcase.converter;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import java.io.IOException;
import java.util.List;
public class CustomExcelWriteHandler implements WriteHandler {
private final double totalActual;
private final double totalTax;
private final double totalTotal;
private final int dataSize;
private boolean totalRowCreated = false;
private int processedDataRowCount = 0; // 已处理的数据行数
// 缓存 Workbook afterSheetCreate 中初始化
private Workbook workbook;
public CustomExcelWriteHandler(double totalActual, double totalTax, double totalTotal, int dataSize) {
this.totalActual = totalActual;
this.totalTax = totalTax;
this.totalTotal = totalTotal;
this.dataSize = dataSize;
}
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
// 初始化 Workbook 缓存关键兼容低版本
this.workbook = writeWorkbookHolder.getWorkbook();
Sheet sheet = writeSheetHolder.getSheet();
// 1. 处理大标题第0行
Row titleRow = sheet.createRow(0);
titleRow.setHeightInPoints(30);
Cell titleCell = titleRow.createCell(0);
titleCell.setCellValue("人工肝诊疗病例征集项目专家劳务费表");
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 13));
// 大标题样式
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setFontHeightInPoints((short) 16);
titleFont.setBold(true);
titleStyle.setFont(titleFont);
titleStyle.setAlignment(HorizontalAlignment.CENTER);
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
titleStyle.setBorderBottom(BorderStyle.THIN);
titleStyle.setBorderTop(BorderStyle.THIN);
titleStyle.setBorderLeft(BorderStyle.THIN);
titleStyle.setBorderRight(BorderStyle.THIN);
titleCell.setCellStyle(titleStyle);
// 2. 设置列宽
int[] columnWidths = {6, 25, 15, 15, 25, 10, 10, 30, 8, 8, 30, 25, 30, 15};
for (int i = 0; i < columnWidths.length; i++) {
sheet.setColumnWidth(i, columnWidths[i] * 256);
}
// 3. 预设置表头行第1行样式和行高
Row headRow = sheet.createRow(1);
headRow.setHeightInPoints(25);
}
// 设置数据行行高并在最后一行数据写入后创建合计行
public void afterRowDispose(WriteSheetHolder writeSheetHolder, Row row, Integer relativeRowIndex, Boolean isHead) throws IOException {
// 设置数据行行高排除表头行
if (isHead == null || !isHead) {
processedDataRowCount++;
if (row.getRowNum() >= 2 && row.getRowNum() <= 1 + dataSize) {
row.setHeightInPoints(22);
}
// 如果是最后一行数据创建合计行
if (!totalRowCreated && processedDataRowCount == dataSize && dataSize > 0) {
createTotalRow(writeSheetHolder.getSheet());
}
}
}
// 创建合计行的辅助方法
private void createTotalRow(Sheet sheet) throws IOException {
// 修正合计行索引0=大标题1=表头2~1+dataSize=数据2+dataSize=合计
int totalRowIndex = 2 + dataSize;
Row totalRow = sheet.createRow(totalRowIndex);
totalRow.setHeightInPoints(22);
// 合计行样式黑底白字
CellStyle totalStyle = workbook.createCellStyle();
Font totalFont = workbook.createFont();
totalFont.setBold(true);
totalFont.setColor(IndexedColors.WHITE.getIndex());
totalStyle.setFont(totalFont);
totalStyle.setFillForegroundColor(IndexedColors.BLACK.getIndex());
totalStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
totalStyle.setAlignment(HorizontalAlignment.CENTER);
totalStyle.setVerticalAlignment(VerticalAlignment.CENTER);
totalStyle.setBorderTop(BorderStyle.THIN);
totalStyle.setBorderBottom(BorderStyle.THIN);
totalStyle.setBorderLeft(BorderStyle.THIN);
totalStyle.setBorderRight(BorderStyle.THIN);
// 合并前6列写"合计"
sheet.addMergedRegion(new CellRangeAddress(totalRowIndex, totalRowIndex, 0, 5));
Cell totalCell = totalRow.createCell(0);
totalCell.setCellValue("合计");
totalCell.setCellStyle(totalStyle);
// 实发个税应发赋值+样式
Cell actualCell = totalRow.createCell(6);
actualCell.setCellValue(totalActual);
actualCell.setCellStyle(totalStyle);
Cell taxCell = totalRow.createCell(8);
taxCell.setCellValue(totalTax);
taxCell.setCellStyle(totalStyle);
Cell totalAmtCell = totalRow.createCell(9);
totalAmtCell.setCellValue(totalTotal);
totalAmtCell.setCellStyle(totalStyle);
// 其他列补充边框
CellStyle borderStyle = workbook.createCellStyle();
borderStyle.setBorderBottom(BorderStyle.THIN);
borderStyle.setBorderTop(BorderStyle.THIN);
borderStyle.setBorderLeft(BorderStyle.THIN);
borderStyle.setBorderRight(BorderStyle.THIN);
borderStyle.setAlignment(HorizontalAlignment.CENTER);
for (int i = 0; i < 14; i++) {
if (i == 0 || i == 6 || i == 8 || i == 9) {
continue;
}
Cell cell = totalRow.getCell(i);
if (cell == null) {
cell = totalRow.createCell(i);
}
cell.setCellStyle(borderStyle);
}
// 标记合计行已创建
totalRowCreated = true;
// 强制刷新兼容SXSSF
if (sheet instanceof SXSSFSheet) {
((SXSSFSheet) sheet).flushRows();
}
}
// 备用在所有数据写入完成后创建合计行如果 afterRowDispose 中没有创建
public void afterSheetDispose(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) throws IOException {
// 如果合计行还未创建则创建备用方案
if (!totalRowCreated && dataSize > 0) {
createTotalRow(writeSheetHolder.getSheet());
}
}
// 其他默认方法无需修改
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}
public void beforeWorkbookCreate(WriteWorkbookHolder writeWorkbookHolder) {}
public void beforeWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) {}
public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) {}
public void afterRowCreate(WriteSheetHolder writeSheetHolder, Row row, Integer relativeRowIndex, Boolean isHead) {}
public void beforeRowCreate(WriteSheetHolder writeSheetHolder, Integer rowIndex, Integer relativeRowIndex, Boolean isHead) {}
public void beforeRowDispose(WriteSheetHolder writeSheetHolder, Row row, Integer relativeRowIndex, Boolean isHead) {}
public void afterCellCreate(WriteSheetHolder writeSheetHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {}
public void afterCellDispose(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {}
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {}
}