using OfficeOpenXml; using OfficeOpenXml.Drawing; using OfficeOpenXml.Style; using WorkCameraExport.Models; using WorkCameraExport.Services.Interfaces; namespace WorkCameraExport.Services { /// /// Excel 服务 - 负责生成 Excel 文件 /// public class ExcelService { private readonly ILogService? _logService; // Excel 图片配置 private const int ImageWidth = 100; private const int ImageHeight = 60; private const int ImageSpacing = 5; static ExcelService() { // 设置 EPPlus 许可证(非商业用途) ExcelPackage.LicenseContext = LicenseContext.NonCommercial; } public ExcelService(ILogService? logService = null) { _logService = logService; } /// /// 导出工作记录到 Excel /// public async Task ExportWorkRecordsToExcelAsync( List records, string outputPath, CancellationToken cancellationToken = default) { try { _logService?.Info($"[Excel] 开始生成 Excel,共 {records.Count} 条记录"); // 确保输出目录存在 var outputDir = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir)) { Directory.CreateDirectory(outputDir); _logService?.Info($"[Excel] 创建输出目录: {outputDir}"); } using var package = new ExcelPackage(); var worksheet = package.Workbook.Worksheets.Add("工作记录"); // 设置表头 SetWorkRecordHeaders(worksheet); // 填充数据 var row = 2; foreach (var record in records) { cancellationToken.ThrowIfCancellationRequested(); await FillWorkRecordRowAsync(worksheet, row, record, cancellationToken); row++; } // 自动调整列宽(除了图片列) AutoFitColumns(worksheet, excludeColumn: 3); // 保存文件 var fileInfo = new FileInfo(outputPath); _logService?.Info($"[Excel] 准备保存文件: {fileInfo.FullName}"); await package.SaveAsAsync(fileInfo, cancellationToken); // 验证文件 if (fileInfo.Exists) { fileInfo.Refresh(); _logService?.Info($"[Excel] 文件保存成功: {fileInfo.FullName}, 大小: {fileInfo.Length} 字节"); } else { _logService?.Error($"[Excel] 文件保存后不存在: {fileInfo.FullName}"); } _logService?.Info($"[Excel] Excel 导出成功: {outputPath}, 共 {records.Count} 条记录"); } catch (OperationCanceledException) { _logService?.Info("[Excel] Excel 导出已取消"); throw; } catch (Exception ex) { _logService?.Error($"[Excel] Excel 导出失败: {ex.Message}", ex); throw; } } /// /// 导出月报表到 Excel /// public async Task ExportMonthlyReportToExcelAsync( List data, string outputPath, CancellationToken cancellationToken = default) { try { using var package = new ExcelPackage(); var worksheet = package.Workbook.Worksheets.Add("月报表"); // 设置表头 worksheet.Cells[1, 1].Value = "序号"; worksheet.Cells[1, 2].Value = "时间"; worksheet.Cells[1, 3].Value = "部门名称"; worksheet.Cells[1, 4].Value = "人员名称"; worksheet.Cells[1, 5].Value = "工作天数"; // 设置表头样式 using (var range = worksheet.Cells[1, 1, 1, 5]) { range.Style.Font.Bold = true; range.Style.Fill.PatternType = ExcelFillStyle.Solid; range.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray); range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; } // 填充数据 for (int i = 0; i < data.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); var row = i + 2; var item = data[i]; worksheet.Cells[row, 1].Value = i + 1; worksheet.Cells[row, 2].Value = item.YearMonth; worksheet.Cells[row, 3].Value = item.DeptName; worksheet.Cells[row, 4].Value = item.WorkerName; worksheet.Cells[row, 5].Value = item.WorkDays; } // 自动调整列宽 worksheet.Cells.AutoFitColumns(); // 保存文件 var fileInfo = new FileInfo(outputPath); await package.SaveAsAsync(fileInfo, cancellationToken); _logService?.Info($"月报表导出成功: {outputPath}, 共 {data.Count} 条记录"); } catch (OperationCanceledException) { _logService?.Info("月报表导出已取消"); throw; } catch (Exception ex) { _logService?.Error($"月报表导出失败: {ex.Message}", ex); throw; } } /// /// 设置工作记录表头 /// private void SetWorkRecordHeaders(ExcelWorksheet worksheet) { var headers = new[] { "序号", "部门名称", "图片", "地址信息", "工作内容", "施工人员", "状态", "拍照时间", "创建时间", "更新时间" }; for (int i = 0; i < headers.Length; i++) { worksheet.Cells[1, i + 1].Value = headers[i]; } // 设置表头样式 using (var range = worksheet.Cells[1, 1, 1, headers.Length]) { range.Style.Font.Bold = true; range.Style.Fill.PatternType = ExcelFillStyle.Solid; range.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray); range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; } // 设置图片列宽度(根据最大图片数计算) worksheet.Column(3).Width = 50; // 默认宽度,会根据实际图片数调整 } /// /// 填充工作记录行数据 /// private async Task FillWorkRecordRowAsync( ExcelWorksheet worksheet, int row, WorkRecordExportItem record, CancellationToken cancellationToken) { worksheet.Cells[row, 1].Value = row - 1; // 序号 worksheet.Cells[row, 2].Value = record.DeptName; // 第3列是图片,稍后处理 worksheet.Cells[row, 4].Value = record.Address; worksheet.Cells[row, 5].Value = record.Content; worksheet.Cells[row, 6].Value = string.Join("、", record.Workers); worksheet.Cells[row, 7].Value = record.StatusName; worksheet.Cells[row, 8].Value = record.RecordTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""; worksheet.Cells[row, 9].Value = record.CreateTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""; worksheet.Cells[row, 10].Value = record.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""; // 设置行高以容纳图片 worksheet.Row(row).Height = ImageHeight + 10; // 嵌入图片 if (record.ImagePaths != null && record.ImagePaths.Count > 0) { await EmbedImagesAsync(worksheet, row, 3, record.ImagePaths, cancellationToken); } } /// /// 嵌入图片到单元格(水平排列) /// private async Task EmbedImagesAsync( ExcelWorksheet worksheet, int row, int column, List imagePaths, CancellationToken cancellationToken) { var validPaths = imagePaths.Where(p => !string.IsNullOrEmpty(p) && File.Exists(p)).ToList(); if (validPaths.Count == 0) return; // 计算所需的列宽 var totalWidth = validPaths.Count * ImageWidth + (validPaths.Count - 1) * ImageSpacing + 10; var currentWidth = worksheet.Column(column).Width * 7; // 大约转换为像素 if (totalWidth > currentWidth) { worksheet.Column(column).Width = totalWidth / 7.0 + 5; } var xOffset = 5; // 起始偏移 for (int i = 0; i < validPaths.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); try { var imagePath = validPaths[i]; var imageData = await File.ReadAllBytesAsync(imagePath, cancellationToken); using var stream = new MemoryStream(imageData); var picture = worksheet.Drawings.AddPicture( $"img_{row}_{column}_{i}", stream); // 设置图片位置和大小 picture.SetPosition(row - 1, 5, column - 1, xOffset); picture.SetSize(ImageWidth, ImageHeight); xOffset += ImageWidth + ImageSpacing; } catch (Exception ex) { _logService?.Warn($"嵌入图片失败: {validPaths[i]}, {ex.Message}"); } } } /// /// 自动调整列宽 /// private void AutoFitColumns(ExcelWorksheet worksheet, int excludeColumn = -1) { for (int col = 1; col <= worksheet.Dimension?.End.Column; col++) { if (col != excludeColumn) { worksheet.Column(col).AutoFit(); } } } /// /// 生成 Excel 文件(兼容旧版 MainForm 调用) /// /// 输出文件路径 /// 工作记录列表 /// 已下载的图片字典(URL -> 图片数据) /// 进度回调(当前行, 总行数) public void GenerateExcel( string outputPath, List records, Dictionary downloadedImages, Action? progressCallback = null) { try { using var package = new ExcelPackage(); var worksheet = package.Workbook.Worksheets.Add("工作记录"); // 设置表头 SetWorkRecordHeaders(worksheet); // 填充数据 for (int i = 0; i < records.Count; i++) { var row = i + 2; var record = records[i]; worksheet.Cells[row, 1].Value = i + 1; // 序号 worksheet.Cells[row, 2].Value = record.DeptName; // 第3列是图片 worksheet.Cells[row, 4].Value = record.Address; worksheet.Cells[row, 5].Value = record.Content; worksheet.Cells[row, 6].Value = string.Join("、", record.WorkerNames); worksheet.Cells[row, 7].Value = record.StatusName; worksheet.Cells[row, 8].Value = record.RecordTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""; worksheet.Cells[row, 9].Value = record.CreateTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""; worksheet.Cells[row, 10].Value = record.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""; // 设置行高以容纳图片 worksheet.Row(row).Height = ImageHeight + 10; // 嵌入图片 if (record.Images != null && record.Images.Count > 0) { EmbedImagesFromBytes(worksheet, row, 3, record.Images, downloadedImages); } progressCallback?.Invoke(i + 1, records.Count); } // 自动调整列宽(除了图片列) AutoFitColumns(worksheet, excludeColumn: 3); // 保存文件 var fileInfo = new FileInfo(outputPath); package.SaveAs(fileInfo); _logService?.Info($"Excel 导出成功: {outputPath}, 共 {records.Count} 条记录"); } catch (Exception ex) { _logService?.Error($"Excel 导出失败: {ex.Message}", ex); throw; } } /// /// 从字节数组嵌入图片到单元格(水平排列) /// private void EmbedImagesFromBytes( ExcelWorksheet worksheet, int row, int column, List imageUrls, Dictionary downloadedImages) { var validImages = imageUrls .Where(url => !string.IsNullOrEmpty(url) && downloadedImages.ContainsKey(url)) .Select(url => downloadedImages[url]) .Where(data => data != null && data.Length > 0) .ToList(); if (validImages.Count == 0) return; // 计算所需的列宽 var totalWidth = validImages.Count * ImageWidth + (validImages.Count - 1) * ImageSpacing + 10; var currentWidth = worksheet.Column(column).Width * 7; if (totalWidth > currentWidth) { worksheet.Column(column).Width = totalWidth / 7.0 + 5; } var xOffset = 5; for (int i = 0; i < validImages.Count; i++) { try { using var stream = new MemoryStream(validImages[i]); var picture = worksheet.Drawings.AddPicture( $"img_{row}_{column}_{i}", stream); picture.SetPosition(row - 1, 5, column - 1, xOffset); picture.SetSize(ImageWidth, ImageHeight); xOffset += ImageWidth + ImageSpacing; } catch (Exception ex) { _logService?.Warn($"嵌入图片失败: {ex.Message}"); } } } } /// /// 工作记录导出项 - 包含本地图片路径 /// public class WorkRecordExportItem { public int Id { get; set; } public string DeptName { get; set; } = ""; public DateTime? RecordTime { get; set; } public string Address { get; set; } = ""; public string Content { get; set; } = ""; public string StatusName { get; set; } = ""; public List Workers { get; set; } = new(); // 工作人员名称列表 public List ImagePaths { get; set; } = new(); // 本地图片路径 public DateTime? CreateTime { get; set; } public DateTime? UpdateTime { get; set; } /// /// 从 WorkRecordExportDto 创建 /// public static WorkRecordExportItem FromDto(WorkRecordExportDto dto) { return new WorkRecordExportItem { Id = dto.Id, DeptName = dto.DeptName, RecordTime = dto.RecordTime, Address = dto.Address, Content = dto.Content, StatusName = dto.StatusName, Workers = dto.WorkerNames, // 使用 WorkerNames 获取字符串列表 CreateTime = dto.CreateTime, UpdateTime = dto.UpdateTime }; } } }