@Value("${excel.export.photo.timeout:30}") // 照片下载超时时间(秒)
private int photoDownloadTimeout;
@Override
public ProjectDailyReportVo getFullReport(String projectDailyId,String baseUrl) {
// 1. 查询主表数据
ProjectDailyReportVo mainReport = projectDailyMainMapper.selectMainReport(projectDailyId);
if (mainReport == null) {
return null;
}
// 2. 查询所有子表数据
List<ProjectDailyReportVo.ProjectProgressState> progressStates =
projectDailyMainMapper.selectProgressStates(projectDailyId);
List<ProjectDailyReportVo.ProjectKeyStatus> keyStatuses =
projectDailyMainMapper.selectKeyStatuses(projectDailyId);
List<ProjectDailyReportVo.ProjectRiskMajor> riskMajors =
projectDailyMainMapper.selectRiskMajors(projectDailyId);
List<ProjectDailyReportVo.ProjectQualityControl> qualityControls =
projectDailyMainMapper.selectQualityControls(projectDailyId);
List<ProjectDailyReportVo.ProjectDayVisa> dayVisas =
projectDailyMainMapper.selectDayVisas(projectDailyId);
List<ProjectDailyReportVo.ProjectPhotosDay> photosDays =
projectDailyMainMapper.selectPhotosDays(projectDailyId);
// 3. 设置子表数据到主对象
mainReport.setProjectProgressState(progressStates);
mainReport.setProjectKeyStatus(keyStatuses);
mainReport.setProjectRiskMajor(riskMajors);
mainReport.setProjectQualityControl(qualityControls);
mainReport.setProjectDayVisa(dayVisas);
mainReport.setProjectPhotosDay(photosDays);
// 4. 为所有子表生成序号(从1开始) - 无接口方案
setOrderNoForProgressStates(progressStates);
setOrderNoForKeyStatuses(keyStatuses);
setOrderNoForRiskMajors(riskMajors);
setOrderNoForQualityControls(qualityControls);
setOrderNoForDayVisas(dayVisas);
setOrderNoForPhotosDays(photosDays);
// 5. 下载照片数据并转换为CellData
downloadPhotosForExport(riskMajors, qualityControls, photosDays, baseUrl);
// 6. 拆分照片数据到各个字段
splitPhotosToColumns(photosDays);
// 7. 创建合并策略并设置照片数据
ProjectDailyMergeStrategy strategy = createAndSetupMergeStrategy(
progressStates, keyStatuses, riskMajors,
qualityControls, dayVisas, photosDays
);
// 8. 将策略设置到主报表对象中
mainReport.setMergeStrategy(strategy);
return mainReport;
}
private ProjectDailyMergeStrategy createAndSetupMergeStrategy(
List<ProjectDailyReportVo.ProjectProgressState> progressStates,
List<ProjectDailyReportVo.ProjectKeyStatus> keyStatuses,
List<ProjectDailyReportVo.ProjectRiskMajor> riskMajors,
List<ProjectDailyReportVo.ProjectQualityControl> qualityControls,
List<ProjectDailyReportVo.ProjectDayVisa> dayVisas,
List<ProjectDailyReportVo.ProjectPhotosDay> photosDays) {
// 计算各表格的数据行数
int progressRows = progressStates != null ? progressStates.size() : 0;
int keyNodeRows = keyStatuses != null ? keyStatuses.size() : 0;
int riskMajorRows = riskMajors != null ? riskMajors.size() : 0;
int qualityRows = qualityControls != null ? qualityControls.size() : 0;
int visaRows = dayVisas != null ? dayVisas.size() : 0;
int photosRows = photosDays != null ? photosDays.size() : 0;
// 创建合并策略实例
ProjectDailyMergeStrategy strategy = new ProjectDailyMergeStrategy(
progressRows, keyNodeRows, visaRows, riskMajorRows, qualityRows,photosRows
);
// 设置照片数据到合并策略
if (riskMajors != null) {
for (int i = 0; i < riskMajors.size(); i++) {
ProjectDailyReportVo.ProjectRiskMajor item = riskMajors.get(i);
if (item.getPhotoData() != null && item.getPhotoData().length > 0) {
strategy.setPhotoDataForRiskMajor(strategy.getRiskMajorStartRow() + i, item.getPhotoData());
}
}
}
if (qualityControls != null) {
for (int i = 0; i < qualityControls.size(); i++) {
ProjectDailyReportVo.ProjectQualityControl item = qualityControls.get(i);
if (item.getPhotoData() != null && item.getPhotoData().length > 0) {
strategy.setPhotoDataForQualityControl(strategy.getQualityStartRow() + i, item.getPhotoData());
}
}
}
if (photosDays != null) {
for (int i = 0; i < photosDays.size(); i++) {
ProjectDailyReportVo.ProjectPhotosDay item = photosDays.get(i);
// 使用第一张照片
if (item.getPhoto1() != null && item.getPhoto1().length > 0) {
strategy.setPhotoDataForPhotosDay(strategy.getVisaStartRow() + i, item.getPhoto1());
}
}
}
return strategy;
}
/**
* 将photoFileData中的照片拆分到各个photoX字段
*/
private void splitPhotosToColumns(List<ProjectDailyReportVo.ProjectPhotosDay> photosDays) {
for (ProjectDailyReportVo.ProjectPhotosDay item : photosDays) {
// 确保所有照片字段都被初始化
item.setPhoto1(null);
item.setPhoto2(null);
item.setPhoto3(null);
item.setPhoto4(null);
if (item.getPhotoFileData() == null || item.getPhotoFileData().isEmpty()) {
continue;
}
List<byte[]> photos = item.getPhotoFileData();
// 分配照片到各个字段
if (photos.size() > 0 && photos.get(0) != null && photos.get(0).length > 0) {
item.setPhoto1(photos.get(0));
}
if (photos.size() > 1 && photos.get(1) != null && photos.get(1).length > 0) {
item.setPhoto2(photos.get(1));
}
if (photos.size() > 2 && photos.get(2) != null && photos.get(2).length > 0) {
item.setPhoto3(photos.get(2));
}
if (photos.size() > 3 && photos.get(3) != null && photos.get(3).length > 0) {
item.setPhoto4(photos.get(3));
}
// 清理临时数据(可选)
item.setPhotoFileData(null);
}
}
private void downloadPhotosForExport(
List<ProjectDailyReportVo.ProjectRiskMajor> riskMajors,
List<ProjectDailyReportVo.ProjectQualityControl> qualityControls,
List<ProjectDailyReportVo.ProjectPhotosDay> photosDays,
String baseUrl) {
// 清理baseUrl格式
String cleanBaseUrl = baseUrl.trim();
if (cleanBaseUrl.endsWith("/")) {
cleanBaseUrl = cleanBaseUrl.substring(0, cleanBaseUrl.length() - 1);
}
List<CompletableFuture<Void>> futures = new ArrayList<>();
// 危大工程照片
for (ProjectDailyReportVo.ProjectRiskMajor item : riskMajors) {
if (item.getPhoto() != null && !item.getPhoto().isEmpty()) {
String fullUrl = cleanBaseUrl + sanitizePhotoPath(item.getPhoto());
futures.add(downloadPhotoAsync(fullUrl, bytes -> item.setPhotoData(bytes)));
}
}
// 质量管控照片
for (ProjectDailyReportVo.ProjectQualityControl item : qualityControls) {
if (item.getPhoto() != null && !item.getPhoto().isEmpty()) {
String fullUrl = cleanBaseUrl + sanitizePhotoPath(item.getPhoto());
futures.add(downloadPhotoAsync(fullUrl, bytes -> item.setPhotoData(bytes)));
}
}
// 施工照片
// 施工照片 - 修改后的逻辑
for (ProjectDailyReportVo.ProjectPhotosDay item : photosDays) {
if (item.getPhotoFile() != null && !item.getPhotoFile().isEmpty()) {
// 关键:保持照片路径的顺序
List<String> orderedPaths = Arrays.stream(item.getPhotoFile().split(";"))
.filter(path -> !path.trim().isEmpty())
.collect(Collectors.toList());
// 保留原始顺序下载照片
for (String path : orderedPaths) {
String fullUrl = cleanBaseUrl + sanitizePhotoPath(path.trim());
futures.add(downloadPhotoAsync(fullUrl, bytes -> {
if (item.getPhotoFileData() == null) {
item.setPhotoFileData(new ArrayList<>());
}
// 添加到列表但保持下载顺序
item.getPhotoFileData().add(bytes);
}));
}
}
}
// 等待所有下载完成
if (!futures.isEmpty()) {
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.get(photoDownloadTimeout, TimeUnit.SECONDS);
log.info("所有照片下载完成");
} catch (Exception e) {
log.error("照片下载异常", e);
}
}
}
// 路径处理方法 - 添加安全处理
private String sanitizePhotoPath(String path) {
if (path == null || path.isEmpty()) {
return path;
}
// 移除两端的空格
path = path.trim();
// 统一路径分隔符
path = path.replace("\\", "/");
// 确保以斜杠开头
if (!path.startsWith("/")) {
path = "/" + path;
}
// 移除特殊字符
path = path.replace(";", "").replace("%3B", "");
return path;
}
// 异步下载方法
private CompletableFuture<Void> downloadPhotoAsync(String url, Consumer<byte[]> setter) {
return CompletableFuture.runAsync(() -> {
try {
byte[] photoBytes = ImageDownloadUtil.downloadImage(url);
setter.accept(photoBytes);
log.debug("照片下载成功: {}", url);
} catch (IOException e) {
log.error("照片下载失败: {},错误: {}", url, e.getMessage());
// 设置空照片避免导出失败
setter.accept(new byte[0]);
}
});
}
// 为各个子表分别设置序号的方法
private void setOrderNoForProgressStates(List<ProjectDailyReportVo.ProjectProgressState> list) {
if (list != null) {
for (int i = 0; i < list.size(); i++) {
list.get(i).setOrderNo(i + 1);
}
}
}
private void setOrderNoForKeyStatuses(List<ProjectDailyReportVo.ProjectKeyStatus> list) {
if (list != null) {
for (int i = 0; i < list.size(); i++) {
list.get(i).setOrderNo(i + 1);
}
}
}
private void setOrderNoForRiskMajors(List<ProjectDailyReportVo.ProjectRiskMajor> list) {
if (list != null) {
for (int i = 0; i < list.size(); i++) {
list.get(i).setOrderNo(i + 1);
}
}
}
private void setOrderNoForQualityControls(List<ProjectDailyReportVo.ProjectQualityControl> list) {
if (list != null) {
for (int i = 0; i < list.size(); i++) {
list.get(i).setOrderNo(i + 1);
}
}
}
private void setOrderNoForDayVisas(List<ProjectDailyReportVo.ProjectDayVisa> list) {
if (list != null) {
for (int i = 0; i < list.size(); i++) {
list.get(i).setOrderNo(i + 1);
}
}
}
private void setOrderNoForPhotosDays(List<ProjectDailyReportVo.ProjectPhotosDay> list) {
if (list != null) {
for (int i = 0; i < list.size(); i++) {
list.get(i).setOrderNo(i + 1);
}
}
}
这是Service实现
@ApiOperation("项目每日情况报告导出")
@PostMapping("/exportProjectDailyReport")
public void exportProjectDailyReport(HttpServletResponse response,
@RequestBody ProjectDailyReportVo projectDailyReportVo) {
try {
// 0. 设置响应头
response.setContentType("application/vnd.ms-excel");
String fileName = new String("项目每日情况报告.xlsx".getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
// 1. 获取完整报表数据
ProjectDailyReportVo reportData = projectDailyMainService.getFullReport(
projectDailyReportVo.getProjectDailyID(),
projectDailyReportVo.getBaseUrl()
);
if (reportData == null) {
// 确保在返回错误信息前重置响应
response.reset();
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpStatus.NOT_FOUND.value());
response.getWriter().write("未找到日报数据");
return;
}
// 2. 使用模板导出
try (InputStream templateInputStream = new ClassPathResource("template/项目每日情况报告导出模版.xlsx").getInputStream();
ServletOutputStream outputStream = response.getOutputStream()) {
// 在导出方法中获取各表格的数据数
int progressRows = reportData.getProjectProgressState() != null ?
reportData.getProjectProgressState().size() : 0;
int keyNodeRows = reportData.getProjectKeyStatus() != null ?
reportData.getProjectKeyStatus().size() : 0;
int riskMajorRows = reportData.getProjectRiskMajor() != null ?
reportData.getProjectRiskMajor().size() : 0;
int qualityRows = reportData.getProjectQualityControl() != null ?
reportData.getProjectQualityControl().size() : 0;
int visaRows = reportData.getProjectDayVisa() != null ?
reportData.getProjectDayVisa().size() : 0;
int photosRows = reportData.getProjectPhotosDay() != null ?
reportData.getProjectPhotosDay().size() : 0;
// 创建合并策略实例 - 添加缺失的参数
ProjectDailyMergeStrategy strategy =
new ProjectDailyMergeStrategy(
progressRows,
keyNodeRows,
visaRows,
riskMajorRows,
qualityRows,
photosRows
);
ExcelWriter writer = EasyExcel
.write(outputStream)
.withTemplate(templateInputStream)
.registerWriteHandler(strategy)
.build();
WriteSheet sheet = EasyExcel.writerSheet(0).build();
FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
// 3. 填充主数据
writer.fill(reportData, fillConfig, sheet);
// 4. 填充子表数据 - 传递第一条结束行信息
fillSubTables(writer, reportData, fillConfig, sheet);
// 5. 完成写入
writer.finish();
} catch (IOException e) {
// 确保在返回错误信息前重置响应
response.reset();
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.getWriter().write("模板文件加载失败: " + e.getMessage());
}
} catch (Exception e) {
try {
// 修复点:重置响应内容类型
response.reset();
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.getWriter().write("导出失败: " + e.getMessage());
} catch (IOException ex) {
log.error("错误响应写入失败", ex);
}
}
}
// 修改后的子表填充方法
private void fillSubTables(ExcelWriter writer, ProjectDailyReportVo reportData,
FillConfig fillConfig, WriteSheet sheet) {
// 1. 进度完成情况
if (reportData.getProjectProgressState() != null && !reportData.getProjectProgressState().isEmpty()) {
writer.fill(new FillWrapper("progress", reportData.getProjectProgressState()), fillConfig, sheet);
}
// 2. 关键节点完成情况
if (reportData.getProjectKeyStatus() != null && !reportData.getProjectKeyStatus().isEmpty()) {
writer.fill(new FillWrapper("keyStatus", reportData.getProjectKeyStatus()), fillConfig, sheet);
}
// 3.危大工程
if (reportData.getProjectRiskMajor() != null && !reportData.getProjectRiskMajor().isEmpty()) {
writer.fill(new FillWrapper("riskMajor",reportData.getProjectRiskMajor()), fillConfig, sheet);
}
// 4. 质量管控情况
if (reportData.getProjectQualityControl() != null && !reportData.getProjectQualityControl().isEmpty()) {
writer.fill(new FillWrapper("qualityControl", reportData.getProjectQualityControl()), fillConfig, sheet);
}
// 5. 当日签证情况
if (reportData.getProjectDayVisa() != null && !reportData.getProjectDayVisa().isEmpty()) {
writer.fill(new FillWrapper("dayVisa", reportData.getProjectDayVisa()), fillConfig, sheet);
}
// 6. 当日施工照片
if (reportData.getProjectPhotosDay() != null && !reportData.getProjectPhotosDay().isEmpty()) {
writer.fill(new FillWrapper("photosDay", reportData.getProjectPhotosDay()), fillConfig, sheet);
}
}
这是Controller
package com.bandway.business.domain;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import jdk.nashorn.internal.objects.annotations.Getter;
import lombok.Data;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.*;
@Data
public class ProjectDailyMergeStrategy implements CellWriteHandler {
// 表一固定起始行
private static final int PROGRESS_START_ROW = 6;
private static final int TABLE_GAP_ROWS = 2;
private final int progressDataCount;
private final int keyNodeDataCount;
private final int visaDataCount;
private final int riskMajorDataCount;
private final int qualityDataCount;
private final int photosDayCount;
// 所有行位置变量
private int progressEndRow;
private int keyNodeStartRow;
private int keyNodeEndRow;
private int riskMajorStartRow;
private int riskMajorEndRow;
private int qualityStartRow;
private int qualityEndRow;
private int visaStartRow;
private int visaEndRow;
private int photosDayStartRow;
private int photosDayEndRow;
// 当然进度完成情况合并规则
private final List<CellRangeAddress> progressMerges = Arrays.asList(
new CellRangeAddress(0, 0, 1, 3),
new CellRangeAddress(0, 0, 4, 7),
new CellRangeAddress(0, 0, 8, 11),
new CellRangeAddress(0, 0, 18, 19)
);
// 本月关键节点完成情况合并规则
private final List<CellRangeAddress> keyNodeMerges = Arrays.asList(
new CellRangeAddress(0, 0, 1, 3),
new CellRangeAddress(0, 0, 4, 5),
new CellRangeAddress(0, 0, 6, 7),
new CellRangeAddress(0, 0, 8, 10),
new CellRangeAddress(0, 0, 12, 14),
new CellRangeAddress(0, 0, 15, 17),
new CellRangeAddress(0, 0, 18, 19)
);
// 危大工程管控情况合并规则
private final List<CellRangeAddress> riskMajorMerges = Arrays.asList(
new CellRangeAddress(0, 0, 1, 2), // 名称合并
new CellRangeAddress(0, 0, 3, 4), // 类型合并
new CellRangeAddress(0, 0, 5, 6), // 开始时间
new CellRangeAddress(0, 0, 7, 8), // 计划结束时间
new CellRangeAddress(0, 0, 11, 12), // 主要管控措施合并
new CellRangeAddress(0, 0, 13, 14), // 旁站监督人合并
new CellRangeAddress(0, 0, 15, 16), // 备注合并
new CellRangeAddress(0, 0, 17, 19) // 照片合并
);
// 质量管控情况合并规则
private final List<CellRangeAddress> qualityControlMerges = Arrays.asList(
new CellRangeAddress(0, 0, 1, 3),
new CellRangeAddress(0, 0, 4, 5),
new CellRangeAddress(0, 0, 6, 7),
new CellRangeAddress(0, 0, 8, 10),
new CellRangeAddress(0, 0, 11, 12),
new CellRangeAddress(0, 0, 13, 14),
new CellRangeAddress(0, 0, 15, 16),
new CellRangeAddress(0, 0, 17, 19)
);
// 当日签证情况合并规则
private final List<CellRangeAddress> visaMerges = Arrays.asList(
new CellRangeAddress(0, 0, 1, 3),
new CellRangeAddress(0, 0, 4, 6),
new CellRangeAddress(0, 0, 7, 8),
new CellRangeAddress(0, 0, 9, 10),
new CellRangeAddress(0, 0, 11, 12),
new CellRangeAddress(0, 0, 13, 14),
new CellRangeAddress(0, 0, 15, 17),
new CellRangeAddress(0, 0, 18, 19)
);
//当日各工作面施工照片合并规则
private final List<CellRangeAddress> photosDayMerges = Arrays.asList(
new CellRangeAddress(0, 0, 1, 3),
new CellRangeAddress(0, 0, 4, 7),
new CellRangeAddress(0, 0, 8, 11),
new CellRangeAddress(0, 0, 12, 15),
new CellRangeAddress(0, 0, 16, 19)
);
public ProjectDailyMergeStrategy(int progressDataCount,
int keyNodeDataCount,
int visaDataCount,
int riskMajorDataCount,
int qualityDataCount,
int photosDayCount) {
this.progressDataCount = progressDataCount;
this.keyNodeDataCount = keyNodeDataCount;
this.visaDataCount = visaDataCount;
this.riskMajorDataCount = riskMajorDataCount;
this.qualityDataCount = qualityDataCount;
this.photosDayCount = photosDayCount;
calculateRowPositions();
}
// 计算行位置
private void calculateRowPositions() {
// 表一结束
progressEndRow = PROGRESS_START_ROW + progressDataCount - 1;
// 表二起始行(在表一后加间隔)
keyNodeStartRow = progressEndRow + TABLE_GAP_ROWS + 1;
keyNodeEndRow = keyNodeStartRow + keyNodeDataCount - 1;
// 表三(危大工程)起始行(在表二后加间隔)
riskMajorStartRow = keyNodeEndRow + TABLE_GAP_ROWS + 1;
riskMajorEndRow = riskMajorStartRow + riskMajorDataCount - 1;
// 表四(质量管控)起始行(在表三后加间隔)
qualityStartRow = riskMajorEndRow + TABLE_GAP_ROWS + 1;
qualityEndRow = qualityStartRow + qualityDataCount - 1;
// 表五(签证情况)起始行(在表四后加间隔)
visaStartRow = qualityEndRow + TABLE_GAP_ROWS + 1;
visaEndRow = visaStartRow + visaDataCount - 1;
// 表六(当日各工作面施工照片)起始行(在表五后加间隔)
photosDayStartRow = visaEndRow + TABLE_GAP_ROWS + 1;
photosDayEndRow = photosDayStartRow + photosDayCount - 1;
System.out.println("危大工程行数计算: 起始行=" + riskMajorStartRow +
", 结束行=" + riskMajorEndRow);
}
// 添加照片处理相关变量
private Map<String, byte[]> photoDataMap = new HashMap<>();
private Map<String, List<byte[]>> multiPhotoDataMap = new HashMap<>();
// 添加设置照片数据的方法
public void setPhotoDataForRiskMajor(int rowIndex, byte[] photoData) {
photoDataMap.put("riskMajor_" + rowIndex, photoData);
}
public void setPhotoDataForQualityControl(int rowIndex, byte[] photoData) {
photoDataMap.put("qualityControl_" + rowIndex, photoData);
}
public void setPhotoDataForPhotosDay(int rowIndex, byte[] photoData) {
photoDataMap.put("photosDay_" + rowIndex, photoData);
}
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
try {
Sheet sheet = context.getWriteSheetHolder().getSheet();
int rowIndex = context.getRowIndex();
// 当然进度完成情况合并处理
if (rowIndex >= PROGRESS_START_ROW && rowIndex <= progressEndRow) {
applyProgressMerges(sheet, rowIndex);
}
// 本月关键节点完成清理合并处理
if (rowIndex >= keyNodeStartRow && rowIndex <= keyNodeEndRow) {
applyKeyNodeMerges(sheet, rowIndex);
}
// 危大工程清理合并处理
if (rowIndex >= riskMajorStartRow && rowIndex <= riskMajorEndRow) {
applyRiskMajorMerges(sheet, rowIndex);
}
// 质量管控清理合并处理
if (rowIndex >= qualityStartRow && rowIndex <= qualityEndRow) {
applyQualityControlMerges(sheet, rowIndex);
}
// 当日签证清理合并处理
if (rowIndex >= visaStartRow && rowIndex <= visaEndRow) {
applyVisaMergesWithCheck(sheet, rowIndex);
}
// 当日各工作面施工照片合并处理
if (rowIndex >= photosDayStartRow && rowIndex <= photosDayEndRow) {
applyPhotosDayMerges(sheet, rowIndex);
}
// 在所有合并完成后,统一处理照片
if (rowIndex == riskMajorEndRow || rowIndex == qualityEndRow || rowIndex == visaEndRow) {
handleAllPhotos(sheet);
}
} catch (Exception e) {
System.err.println("合并单元格时发生错误: " + e.getMessage());
}
}
// 新增方法:统一处理所有照片
private void handleAllPhotos(Sheet sheet) {
Workbook workbook = sheet.getWorkbook();
// 处理危大工程照片
for (int row = riskMajorStartRow; row <= riskMajorEndRow; row++) {
String key = "riskMajor_" + row;
if (photoDataMap.containsKey(key)) {
byte[] photoData = photoDataMap.get(key);
// 检查合并区域是否存在
if (isMergedRegionByCoords(sheet, row, 17, row, 19)) {
insertPhotoInMergedArea(workbook, sheet, row, 17, 19, photoData);
}
}
}
// 处理质量管控照片
for (int row = qualityStartRow; row <= qualityEndRow; row++) {
String key = "qualityControl_" + row;
if (photoDataMap.containsKey(key)) {
byte[] photoData = photoDataMap.get(key);
if (isMergedRegionByCoords(sheet, row, 17, row, 19)) {
insertPhotoInMergedArea(workbook, sheet, row, 17, 19, photoData);
}
}
}
// 处理施工照片
for (int row = visaStartRow; row <= visaEndRow; row++) {
String key = "photosDay_" + row;
if (multiPhotoDataMap.containsKey(key)) {
List<byte[]> photoList = multiPhotoDataMap.get(key);
if (photoList != null && !photoList.isEmpty() &&
isMergedRegionByCoords(sheet, row, 17, row, 19)) {
insertPhotoInMergedArea(workbook, sheet, row, 17, 19, photoList.get(0));
}
}
}
}
private boolean isMergedRegionByCoords(Sheet sheet, int firstRow, int firstCol, int lastRow, int lastCol) {
for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
CellRangeAddress merged = sheet.getMergedRegion(i);
if (merged.getFirstRow() == firstRow &&
merged.getLastRow() == lastRow &&
merged.getFirstColumn() == firstCol &&
merged.getLastColumn() == lastCol) {
return true;
}
}
return false;
}
// 在合并区域插入照片
private void insertPhotoInMergedArea(Workbook workbook, Sheet sheet,
int rowIndex, int startCol, int endCol,
byte[] photoData) {
if (photoData == null || photoData.length == 0) {
return;
}
try {
// 添加图片到工作簿
int pictureIdx = workbook.addPicture(photoData, Workbook.PICTURE_TYPE_JPEG);
// 创建绘图对象
Drawing<?> drawing = sheet.createDrawingPatriarch();
if (drawing == null) {
drawing = sheet.createDrawingPatriarch();
}
// 创建锚点,设置图片位置
CreationHelper helper = workbook.getCreationHelper();
ClientAnchor anchor = helper.createClientAnchor();
// 设置锚点类型为移动并调整大小
anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
// 设置图片位置为合并单元格的区域
anchor.setCol1(startCol);
anchor.setCol2(endCol);
anchor.setRow1(rowIndex);
anchor.setRow2(rowIndex); // 不跨行,但确保占满宽度
// 创建图片
Picture picture = drawing.createPicture(anchor, pictureIdx);
// 设置行高以适应照片
Row row = sheet.getRow(rowIndex);
if (row != null) {
// 根据照片比例计算合适行高
double colWidth = 0;
for (int i = startCol; i <= endCol; i++) {
colWidth += sheet.getColumnWidth(i);
}
// 假设照片宽高比为4:3,计算合适行高
double widthInPoints = colWidth / 256 * 9; // 转换为点
double heightInPoints = widthInPoints * 0.75; // 4:3比例
row.setHeightInPoints((float) Math.max(heightInPoints, 60)); // 最小行高60点
}
// 设置图片随单元格大小调整 - 确保占满整个合并区域
picture.resize();
} catch (Exception e) {
System.err.println("插入图片失败: " + e.getMessage());
}
}
// 当然进度完成情况合并方法
private void applyProgressMerges(Sheet sheet, int currentRow) {
for (CellRangeAddress baseMerge : progressMerges) {
applyMergeRule(sheet, baseMerge, currentRow);
}
}
// 本月关键节点完成情况合并方法
private void applyKeyNodeMerges(Sheet sheet, int currentRow) {
for (CellRangeAddress baseMerge : keyNodeMerges) {
applyMergeRule(sheet, baseMerge, currentRow);
}
}
// 危大工程管控情况合并方法
private void applyRiskMajorMerges(Sheet sheet, int currentRow) {
for (CellRangeAddress baseMerge : riskMajorMerges) {
applyMergeRule(sheet, baseMerge, currentRow);
}
}
// 质量管控情况合并方法
private void applyQualityControlMerges(Sheet sheet, int currentRow) {
for (CellRangeAddress baseMerge : qualityControlMerges) {
applyMergeRule(sheet, baseMerge, currentRow);
}
}
// 保留签证表的合并方法
private void applyVisaMergesWithCheck(Sheet sheet, int currentRow) {
// 检查当前行是否在表五的实际数据范围内
if (currentRow < visaStartRow || currentRow > visaEndRow) {
System.out.println("警告: 尝试在表五范围外应用合并规则,行: " + currentRow);
return;
}
for (CellRangeAddress baseMerge : visaMerges) {
// 创建当前行的实际合并区域
CellRangeAddress rowMerge = new CellRangeAddress(
currentRow,
currentRow,
baseMerge.getFirstColumn(),
baseMerge.getLastColumn()
);
// 检查是否与现有合并区域冲突
if (isOverlappingWithExistingMerge(sheet, rowMerge)) {
System.out.println("跳过合并区域 due to conflict: " + rowMerge.formatAsString());
continue;
}
if (!isMergedRegion(sheet, rowMerge)) {
sheet.addMergedRegion(rowMerge);
System.out.println("应用合并: " + rowMerge.formatAsString() + " at row " + currentRow);
}
}
}
//当日各工作面施工照片合并方法
private void applyPhotosDayMerges(Sheet sheet, int currentRow) {
for (CellRangeAddress baseMerge : photosDayMerges){
applyMergeRule(sheet, baseMerge, currentRow);
}
}
// 保留辅助方法
private void applyMergeRule(Sheet sheet, CellRangeAddress baseMerge, int currentRow) {
CellRangeAddress rowMerge = new CellRangeAddress(
currentRow,
currentRow,
baseMerge.getFirstColumn(),
baseMerge.getLastColumn()
);
if (!isMergedRegion(sheet, rowMerge)) {
sheet.addMergedRegion(rowMerge);
}
}
private boolean isMergedRegion(Sheet sheet, CellRangeAddress newMerge) {
for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
CellRangeAddress existing = sheet.getMergedRegion(i);
if (existing.getFirstRow() == newMerge.getFirstRow() &&
existing.getLastRow() == newMerge.getLastRow() &&
existing.getFirstColumn() == newMerge.getFirstColumn() &&
existing.getLastColumn() == newMerge.getLastColumn()) {
return true;
}
}
return false;
}
// 保留重叠检查方法
private boolean isOverlappingWithExistingMerge(Sheet sheet, CellRangeAddress newMerge) {
for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
CellRangeAddress existing = sheet.getMergedRegion(i);
// 检查行重叠
boolean rowsOverlap = (newMerge.getFirstRow() >= existing.getFirstRow() &&
newMerge.getFirstRow() <= existing.getLastRow()) ||
(newMerge.getLastRow() >= existing.getFirstRow() &&
newMerge.getLastRow() <= existing.getLastRow());
// 检查列重叠
boolean colsOverlap = (newMerge.getFirstColumn() >= existing.getFirstColumn() &&
newMerge.getFirstColumn() <= existing.getLastColumn()) ||
(newMerge.getLastColumn() >= existing.getFirstColumn() &&
newMerge.getLastColumn() <= existing.getLastColumn());
if (rowsOverlap && colsOverlap) {
System.out.println("发现重叠: " + newMerge.formatAsString() + " with " + existing.formatAsString());
return true;
}
}
return false;
}
// 保留移除合并区域方法
private void removeExistingMergesInRange(Sheet sheet, int startRow, int endRow) {
for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) {
CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
if (mergedRegion.getFirstRow() >= startRow && mergedRegion.getLastRow() <= endRow) {
sheet.removeMergedRegion(i);
}
}
}
@Override
public void beforeCellCreate(CellWriteHandlerContext context) {}
@Override
public void afterCellCreate(CellWriteHandlerContext context) {}
@Override
public void afterCellDataConverted(CellWriteHandlerContext context) {}
}
这是合并策略类,帮我分析一下,为什么我导出的照片无法占满整个合并单元格,只是占了合并单元格里面的其中一格
最新发布