|
@@ -5777,6 +5777,399 @@ namespace OASystem.API.OAMethodLib
|
|
|
#endregion
|
|
|
#endregion
|
|
|
|
|
|
+
|
|
|
+ #region Excel导出服务
|
|
|
+ #region 类
|
|
|
+ public class FileExportSettings
|
|
|
+ {
|
|
|
+ public string ExportBasePath { get; set; } = "wwwroot/exports";
|
|
|
+ public string DownloadBaseUrl { get; set; } = "/exports";
|
|
|
+ public int RetentionDays { get; set; } = 7;
|
|
|
+ public int MaxFileSizeMB { get; set; } = 50;
|
|
|
+ public string ExcelTemplatesPath { get; set; } = "Templates/Excel";
|
|
|
+ }
|
|
|
+
|
|
|
+ public class ExportResult
|
|
|
+ {
|
|
|
+ public bool Success { get; set; }
|
|
|
+ public string FilePath { get; set; }
|
|
|
+ public string DownloadUrl { get; set; }
|
|
|
+ public string FileName { get; set; }
|
|
|
+ public long FileSize { get; set; }
|
|
|
+ public string MimeType { get; set; } = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
|
+ public string ErrorMessage { get; set; }
|
|
|
+ public DateTime GeneratedAt { get; set; } = DateTime.Now;
|
|
|
+ }
|
|
|
+
|
|
|
+ public class ExportRequest
|
|
|
+ {
|
|
|
+ public IEnumerable<object> Data { get; set; }
|
|
|
+ public string FileName { get; set; }
|
|
|
+ public string SheetName { get; set; } = "Sheet1";
|
|
|
+ public string TemplateType { get; set; } = "default";
|
|
|
+ public Dictionary<string, string> CustomHeaders { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class ExportResponse
|
|
|
+ {
|
|
|
+ public string FilePath { get; set; }
|
|
|
+ public string DownloadUrl { get; set; }
|
|
|
+ public string FileName { get; set; }
|
|
|
+ public long FileSize { get; set; }
|
|
|
+ public DateTime GeneratedAt { get; set; }
|
|
|
+ public string Message { get; set; }
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public static async Task<ExportResult> ExportToExcel<T>(IEnumerable<T> data, string fileName, string sheetName = "Sheet1", string templateType = "default")
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ byte[] excelBytes = templateType.ToLower() switch
|
|
|
+ {
|
|
|
+ "styled" => GenerateStyledExcel(data, sheetName),
|
|
|
+ "with_formulas" => GenerateExcelWithFormulas(data, sheetName),
|
|
|
+ //"with_charts" => GenerateExcelWithCharts(data, sheetName),
|
|
|
+ _ => GenerateDefaultExcel(data, sheetName)
|
|
|
+ };
|
|
|
+
|
|
|
+ var filePath = await SaveFileAsync(excelBytes, fileName, "excel");
|
|
|
+ var downloadUrl = $"wwwroot/exports/{filePath}";
|
|
|
+
|
|
|
+ return new ExportResult
|
|
|
+ {
|
|
|
+ Success = true,
|
|
|
+ FilePath = filePath,
|
|
|
+ DownloadUrl = downloadUrl,
|
|
|
+ FileName = Path.GetFileName(filePath),
|
|
|
+ FileSize = excelBytes.Length,
|
|
|
+ MimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return new ExportResult
|
|
|
+ {
|
|
|
+ Success = false,
|
|
|
+ ErrorMessage = ex.Message
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static async Task<ExportResult> ExportWithTemplate<T>(IEnumerable<T> data, byte[] templateBytes, string fileName, string sheetName = "Sheet1")
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ using var templateStream = new MemoryStream(templateBytes);
|
|
|
+ using var workbook = new Workbook(templateStream);
|
|
|
+
|
|
|
+ var worksheet = workbook.Worksheets[sheetName] ?? workbook.Worksheets[0];
|
|
|
+
|
|
|
+ // 查找数据起始行(可以根据模板中的标记来定位)
|
|
|
+ int startRow = FindDataStartRow(worksheet);
|
|
|
+
|
|
|
+ // 填充数据
|
|
|
+ FillWorksheetWithData(worksheet, data, startRow);
|
|
|
+
|
|
|
+ using var outputStream = new MemoryStream();
|
|
|
+ workbook.Save(outputStream, Aspose.Cells.SaveFormat.Xlsx);
|
|
|
+ var excelBytes = outputStream.ToArray();
|
|
|
+
|
|
|
+ var filePath = await SaveFileAsync(excelBytes, fileName, "excel/templates");
|
|
|
+ var downloadUrl = $"wwwroot/exports/exports/{filePath}";
|
|
|
+
|
|
|
+ return new ExportResult
|
|
|
+ {
|
|
|
+ Success = true,
|
|
|
+ FilePath = filePath,
|
|
|
+ DownloadUrl = downloadUrl,
|
|
|
+ FileName = Path.GetFileName(filePath),
|
|
|
+ FileSize = excelBytes.Length,
|
|
|
+ MimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return new ExportResult
|
|
|
+ {
|
|
|
+ Success = false,
|
|
|
+ ErrorMessage = ex.Message
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static async Task<ExportResult> ExportMultipleSheets<T>(Dictionary<string, IEnumerable<T>> sheetsData, string fileName)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ using var workbook = new Workbook();
|
|
|
+ workbook.Worksheets.Clear(); // 清除默认工作表
|
|
|
+
|
|
|
+ foreach (var sheet in sheetsData)
|
|
|
+ {
|
|
|
+ var worksheet = workbook.Worksheets.Add(sheet.Key);
|
|
|
+ var data = sheet.Value;
|
|
|
+
|
|
|
+ if (data != null && data.Any())
|
|
|
+ {
|
|
|
+ FillWorksheetWithData(worksheet, data, 0);
|
|
|
+ worksheet.AutoFitColumns();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ using var stream = new MemoryStream();
|
|
|
+ workbook.Save(stream, Aspose.Cells.SaveFormat.Xlsx);
|
|
|
+ var excelBytes = stream.ToArray();
|
|
|
+
|
|
|
+ var filePath = await SaveFileAsync(excelBytes, fileName, "excel/multi-sheets");
|
|
|
+ var downloadUrl = $"wwwroot/exports/exports/{filePath}";
|
|
|
+
|
|
|
+ return new ExportResult
|
|
|
+ {
|
|
|
+ Success = true,
|
|
|
+ FilePath = filePath,
|
|
|
+ DownloadUrl = downloadUrl,
|
|
|
+ FileName = Path.GetFileName(filePath),
|
|
|
+ FileSize = excelBytes.Length,
|
|
|
+ MimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return new ExportResult
|
|
|
+ {
|
|
|
+ Success = false,
|
|
|
+ ErrorMessage = ex.Message
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static async Task<ExportResult> ExportWithCustomHeaders<T>(IEnumerable<T> data, Dictionary<string, string> headers, string fileName, string sheetName = "Sheet1")
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ using var workbook = new Workbook();
|
|
|
+ var worksheet = workbook.Worksheets[0];
|
|
|
+ worksheet.Name = sheetName;
|
|
|
+
|
|
|
+ // 设置自定义表头
|
|
|
+ int colIndex = 0;
|
|
|
+ foreach (var header in headers)
|
|
|
+ {
|
|
|
+ worksheet.Cells[0, colIndex].PutValue(header.Value);
|
|
|
+ colIndex++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充数据
|
|
|
+ if (data != null && data.Any())
|
|
|
+ {
|
|
|
+ int rowIndex = 1;
|
|
|
+ var properties = typeof(T).GetProperties();
|
|
|
+
|
|
|
+ foreach (var item in data)
|
|
|
+ {
|
|
|
+ colIndex = 0;
|
|
|
+ foreach (var header in headers)
|
|
|
+ {
|
|
|
+ var property = properties.FirstOrDefault(p => p.Name == header.Key);
|
|
|
+ if (property != null)
|
|
|
+ {
|
|
|
+ var value = property.GetValue(item);
|
|
|
+ worksheet.Cells[rowIndex, colIndex].PutValue(value);
|
|
|
+ }
|
|
|
+ colIndex++;
|
|
|
+ }
|
|
|
+ rowIndex++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ worksheet.AutoFitColumns();
|
|
|
+
|
|
|
+ using var stream = new MemoryStream();
|
|
|
+ workbook.Save(stream, Aspose.Cells.SaveFormat.Xlsx);
|
|
|
+ var excelBytes = stream.ToArray();
|
|
|
+
|
|
|
+ var filePath = await SaveFileAsync(excelBytes, fileName, "excel/custom-headers");
|
|
|
+ var downloadUrl = $"filePath";
|
|
|
+
|
|
|
+ return new ExportResult
|
|
|
+ {
|
|
|
+ Success = true,
|
|
|
+ FilePath = filePath,
|
|
|
+ DownloadUrl = downloadUrl,
|
|
|
+ FileName = Path.GetFileName(filePath),
|
|
|
+ FileSize = excelBytes.Length,
|
|
|
+ MimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return new ExportResult
|
|
|
+ {
|
|
|
+ Success = false,
|
|
|
+ ErrorMessage = ex.Message
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] GenerateDefaultExcel<T>(IEnumerable<T> data, string sheetName)
|
|
|
+ {
|
|
|
+ using var workbook = new Workbook();
|
|
|
+ var worksheet = workbook.Worksheets[0];
|
|
|
+ worksheet.Name = sheetName;
|
|
|
+
|
|
|
+ if (data != null && data.Any())
|
|
|
+ {
|
|
|
+ FillWorksheetWithData(worksheet, data, 0);
|
|
|
+ worksheet.AutoFitColumns();
|
|
|
+ }
|
|
|
+
|
|
|
+ return SaveWorkbookToBytes(workbook);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] GenerateStyledExcel<T>(IEnumerable<T> data, string sheetName)
|
|
|
+ {
|
|
|
+ using var workbook = new Workbook();
|
|
|
+ var worksheet = workbook.Worksheets[0];
|
|
|
+ worksheet.Name = sheetName;
|
|
|
+
|
|
|
+ if (data != null && data.Any())
|
|
|
+ {
|
|
|
+ FillWorksheetWithData(worksheet, data, 0);
|
|
|
+
|
|
|
+ // 设置样式
|
|
|
+ var headerStyle = workbook.CreateStyle();
|
|
|
+ headerStyle.Pattern = BackgroundType.Solid;
|
|
|
+ headerStyle.ForegroundColor = System.Drawing.Color.LightGray;
|
|
|
+ headerStyle.Font.IsBold = true;
|
|
|
+ headerStyle.HorizontalAlignment = TextAlignmentType.Center;
|
|
|
+
|
|
|
+ var properties = typeof(T).GetProperties();
|
|
|
+ Aspose.Cells.Range headerRange = worksheet.Cells.CreateRange(0, 0, 1, properties.Length);
|
|
|
+ headerRange.ApplyStyle(headerStyle, new StyleFlag { All = true });
|
|
|
+
|
|
|
+ worksheet.AutoFitColumns();
|
|
|
+ }
|
|
|
+
|
|
|
+ return SaveWorkbookToBytes(workbook);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] GenerateExcelWithFormulas<T>(IEnumerable<T> data, string sheetName)
|
|
|
+ {
|
|
|
+ using var workbook = new Workbook();
|
|
|
+ var worksheet = workbook.Worksheets[0];
|
|
|
+ worksheet.Name = sheetName;
|
|
|
+
|
|
|
+ if (data != null && data.Any())
|
|
|
+ {
|
|
|
+ FillWorksheetWithData(worksheet, data, 0);
|
|
|
+
|
|
|
+ // 添加公式(例如:求和公式)
|
|
|
+ var properties = typeof(T).GetProperties();
|
|
|
+ int lastRow = data.Count() + 1;
|
|
|
+
|
|
|
+ for (int i = 0; i < properties.Length; i++)
|
|
|
+ {
|
|
|
+ if (IsNumericType(properties[i].PropertyType))
|
|
|
+ {
|
|
|
+ worksheet.Cells[lastRow, i].Formula = $"=SUM({worksheet.Cells[1, i].Name}:{worksheet.Cells[lastRow - 1, i].Name})";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ worksheet.AutoFitColumns();
|
|
|
+ }
|
|
|
+
|
|
|
+ return SaveWorkbookToBytes(workbook);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void FillWorksheetWithData<T>(Worksheet worksheet, IEnumerable<T> data, int startRow)
|
|
|
+ {
|
|
|
+ var properties = typeof(T).GetProperties();
|
|
|
+
|
|
|
+ // 设置表头
|
|
|
+ for (int i = 0; i < properties.Length; i++)
|
|
|
+ {
|
|
|
+ worksheet.Cells[startRow, i].PutValue(properties[i].Name);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充数据
|
|
|
+ int rowIndex = startRow + 1;
|
|
|
+ foreach (var item in data)
|
|
|
+ {
|
|
|
+ for (int colIndex = 0; colIndex < properties.Length; colIndex++)
|
|
|
+ {
|
|
|
+ var value = properties[colIndex].GetValue(item);
|
|
|
+ worksheet.Cells[rowIndex, colIndex].PutValue(value);
|
|
|
+ }
|
|
|
+ rowIndex++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int FindDataStartRow(Worksheet worksheet)
|
|
|
+ {
|
|
|
+ // 在实际项目中,可以根据模板中的特定标记来定位数据起始行
|
|
|
+ // 例如查找 "{{DATA_START}}" 这样的标记
|
|
|
+ for (int row = 0; row < 50; row++)
|
|
|
+ {
|
|
|
+ for (int col = 0; col < 10; col++)
|
|
|
+ {
|
|
|
+ var value = worksheet.Cells[row, col].StringValue;
|
|
|
+ if (value == "{{DATA_START}}")
|
|
|
+ {
|
|
|
+ worksheet.Cells[row, col].PutValue(""); // 清空标记
|
|
|
+ return row + 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 1; // 默认从第2行开始
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] SaveWorkbookToBytes(Workbook workbook)
|
|
|
+ {
|
|
|
+ using var stream = new MemoryStream();
|
|
|
+ workbook.Save(stream, Aspose.Cells.SaveFormat.Xlsx);
|
|
|
+ return stream.ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool IsNumericType(Type type)
|
|
|
+ {
|
|
|
+ return Type.GetTypeCode(type) switch
|
|
|
+ {
|
|
|
+ TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or TypeCode.UInt32 or
|
|
|
+ TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or TypeCode.Int64 or
|
|
|
+ TypeCode.Decimal or TypeCode.Double or TypeCode.Single => true,
|
|
|
+ _ => false
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public static async Task<string> SaveFileAsync(byte[] fileData, string fileName, string subFolder = "")
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var basePath ="wwwroot/exports/exports";
|
|
|
+ var folderPath = Path.Combine(basePath, subFolder);
|
|
|
+ Directory.CreateDirectory(folderPath);
|
|
|
+
|
|
|
+ var uniqueFileName = $"{Path.GetFileNameWithoutExtension(fileName)}_{Guid.NewGuid():N}{Path.GetExtension(fileName)}.xlsx";
|
|
|
+ var filePath = Path.Combine(folderPath, uniqueFileName);
|
|
|
+
|
|
|
+ await System.IO.File.WriteAllBytesAsync(filePath, fileData);
|
|
|
+
|
|
|
+ var relativePath = Path.Combine(basePath, subFolder, uniqueFileName).Replace("\\", "/");
|
|
|
+
|
|
|
+ return relativePath;
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ throw;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
|