在报表中显示指定URL的照片

本文介绍如何使用ReportMachine在报表中加载指定URL的照片。通过OnBeforePrint事件,从URL下载图片并显示在TRMPictureView控件中。需注意在uses中引入URLMon单元。
在报表中显示指定URL的照片

以下源码演示如何在ReportMachine报表中显示对应记录指定文件名称的照片,并且照片从指定的URL中下载。其原理是,在报表显示前(OnBeforePrint事件),找到要显示照片的TRMPictureView控件,再从揚hoto?#23383;段得到文件,从指定的URL下载该文件名称的照片(jpg文件)到临时文件夹,再将临时文件夹中的照片显示到TRMPictureView控件中。

需要注意的是,首先要在uses 子句中加入URLMon单元的引用。在窗口创建时得到临时文件,当然可以是其它具体的文件,只不过每次报表预览积累下来的照片会占用不少的磁盘空间。

主要代码在RMReport1的OnBeforePrint事件中,首先找到要显示的TRMPictureView控件,这个控件在设计ReportMachine报表时加入,并赋予名称(这里为損icPhoto?#65289;,代码中的名称要与报表中的名称对应,否则永远找不到。在找到控件后,读取保存照片文件名称的字段(这里为揚hoto?#65289;,再将指定的URL(这里定义为常数URL)加上文件名称作为完整的下载URL,必须是有效的URL,可以在服务器中指定对应的虚拟目录,事先在IE中测试是否有效。将文件下载到临时目录下相同名称的文件,再将该文件显示到picPhoto控件中。

该程序在Windows2000 Server + Delphi6 + ReportMachine3.0中测试过。已知的BUG:下载URL无效时响应会变得很慢,且DownLoadFile()函数的返回结果总为True。

unit u_frmURLReport;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, DB, Grids, DBGrids, ADODB, ExtCtrls, RM_Dataset,
  RM_Class, RM_Designer, RM_Common, ComCtrls, URLMon;   // Use "URLMon" first!

type
  TfrmURLReport = class(TForm)
    ADOConnection1: TADOConnection;
    ADOQuery1: TADOQuery;
    DataSource1: TDataSource;
    DBGrid1: TDBGrid;
    ADOQuery1EmpNo: TStringField;
    ADOQuery1EmpName: TStringField;
    ADOQuery1Company: TStringField;
    ADOQuery1ClassName: TStringField;
    ADOQuery1CardNo: TIntegerField;
    ADOQuery1Photo: TStringField;
    Panel1: TPanel;
    Edit1: TEdit;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    RMReport1: TRMReport;
    RMDesigner1: TRMDesigner;
    RMDBDataSet1: TRMDBDataSet;
    StatusBar1: TStatusBar;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure RMReport1BeforePrint(Memo: TStrings; View: TRMReportView);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    PhotoPath: String;
  public
    { Public declarations }
  end;

var
  frmURLReport: TfrmURLReport;

implementation

uses u_Download;

{$R *.dfm}

{ Get the temporary folder }
function GetTempPathName: String;
var
  Buf: PChar;
begin
  Result:= '';
  GetMem(Buf, 255);
  try
    if GetTempPath(255, Buf) <> 0 then
      Result:= String(Buf);
  finally
    FreeMem(Buf, 255);
  end;
end;

{ Download file from the specified URL }
function DownloadFile(Source, Dest: string): Boolean;
begin
  try
    Result := UrlDownloadToFile(nil, PChar(source), PChar(Dest),
      0, nil) = 0;
  except
    Result := False;
  end;
end;

{ Filter the records }
procedure TfrmURLReport.Button1Click(Sender: TObject);
begin
  ADOQuery1.Filtered:= False;
  ADOQuery1.Filter:= Edit1.Text;
  ADOQuery1.Filtered:= True;
end;

{ Preview the report }
procedure TfrmURLReport.Button2Click(Sender: TObject);
begin
  RMReport1.LoadFromFile(ChangeFileExt(Application.ExeName, '.rmf'));
  RMReport1.ShowReport;
end;

{ Design the report }
procedure TfrmURLReport.Button3Click(Sender: TObject);
begin
  RMReport1.LoadFromFile(ChangeFileExt(Application.ExeName, '.rmf'));
  RMReport1.DesignReport;
end;

{ Download the specified photo and show on the report }
procedure TfrmURLReport.RMReport1BeforePrint(Memo: TStrings;
  View: TRMReportView);
const
  URL = 'http://infoserver/gkpic/gkpic/Ext/';
var
  picPhoto: TRMPictureView;
  FileName: String;
begin
  picPhoto:= (RMReport1.FindObject('picPhoto') as TRMPictureView);
  if picPhoto <> nil then
  begin
    FileName:= RMReport1.Dataset.GetFieldValue('Photo');        
    StatusBar1.SimpleText:= 'Downloading photos...';
    StatusBar1.Update;
    DownloadFile(URL + FileName, PhotoPath + FileName);
    if FileExists(PhotoPath + FileName) then
      picPhoto.Picture.LoadFromFile(PhotoPath + FileName)
    else
      picPhoto.Picture.Graphic:= nil;
  end;
end;

{ Get the temporary folder to save the photos first }
procedure TfrmURLReport.FormCreate(Sender: TObject);
begin
  PhotoPath:= GetTempPathName;
  //PhotoPath:= ExtractFilePath(Application.ExeName);
end;

end.


@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) {} } 这是合并策略类,帮我分析一下,为什么我导出的照片无法占满整个合并单元格,只是占了合并单元格里面的其中一格
最新发布
09-04
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值