using OfficeOpenXml; using OfficeOpenXml.Style; using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using VolPro.Core.Extensions; using VolPro.Core.Utilities; namespace VolPro.Core.Generic { /// /// 通用导出 Excel 辅助类(参照 EPPlusHelper.Export 的行为, /// 实现字典数据源转换、字段类型显示、语言翻译等操作),仅返回内存字节,不落地文件。 /// internal static class GenericExcelExportHelper { public static byte[] BuildExportBytes( string tableName, List> rows, string[] exportColumns, List ignoreColumns) { // 1. 列配置(CellOptions) var mi = typeof(EPPlusHelper).GetMethod("GetExportColumnInfo", BindingFlags.NonPublic | BindingFlags.Static); if (mi == null) return null; var cellOptions = mi.Invoke(null, new object[] { tableName, false, true, exportColumns }) as List; if (cellOptions == null || cellOptions.Count == 0) return Array.Empty(); // 2. 最终要导出的列 var exportCols = BuildExportColumns(cellOptions, ignoreColumns); if (exportCols.Count == 0) return []; // 3. 字典列元数据 var dicNoKeys = cellOptions .Where(x => !string.IsNullOrEmpty(x.DropNo) && x.KeyValues != null && x.KeyValues.Keys.Count > 0) .Select(x => (x.ColumnName, x.SearchType, x.EditType)) .Distinct() .ToList(); var multiSelectColumns = dicNoKeys .Where(x => x.SearchType == "checkbox" || x.SearchType == "selectList" || x.SearchType == "treeSelect" || x.EditType == "checkbox" || x.EditType == "selectList" || x.EditType == "treeSelect") .Select(x => x.ColumnName) .ToArray(); using var package = new ExcelPackage(); var sheet = package.Workbook.Worksheets.Add("sheet1"); // 4. 表头 WriteHeaderRow(sheet, cellOptions, exportCols); // 5. 数据行 WriteDataRows(sheet, rows, exportCols, cellOptions, dicNoKeys, multiSelectColumns); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // 6. 自动列宽 if (sheet.Dimension != null) sheet.Cells[sheet.Dimension.Address].AutoFitColumns(); } using var ms = new MemoryStream(); package.SaveAs(ms); return ms.ToArray(); } /// 确定最终导出的列顺序,并应用忽略字段 private static List BuildExportColumns(List cellOptions, List ignoreColumns) { var cols = cellOptions.Select(c => c.ColumnName).ToList(); if (ignoreColumns != null && ignoreColumns.Count > 0) { var ignoreSet = new HashSet(ignoreColumns.Select(x => x.ToLower())); cols = cols.Where(c => !ignoreSet.Contains(c.ToLower())).ToList(); } return cols; } /// 写表头行(颜色、宽度、翻译与 EPPlusHelper.Export 中模板=false 一致) private static void WriteHeaderRow( ExcelWorksheet sheet, List cellOptions, List exportCols) { for (int i = 0; i < exportCols.Count; i++) { string colName = exportCols[i]; using (var cell = sheet.Cells[1, i + 1]) { cell.Style.Fill.PatternType = ExcelFillStyle.Solid; cell.Style.Fill.BackgroundColor.SetColor(Color.Gray); cell.Style.Font.Color.SetColor(Color.White); } var opt = cellOptions.FirstOrDefault(x => x.ColumnName == colName); if (opt == null) { sheet.Column(i + 1).Width = 15; sheet.Cells[1, i + 1].Value = colName.Translator(); continue; } sheet.Column(i + 1).Width = opt.ColumnWidth / 6.0; var header = opt.ColumnCNName; sheet.Cells[1, i + 1].Value = header.Translator(); } } /// 写数据行,包括日期格式、字典转换、图片及 long 文本处理 private static void WriteDataRows( ExcelWorksheet sheet, List> rows, List exportCols, List cellOptions, List<(string ColumnName, string SearchType, string EditType)> dicNoKeys, string[] multiSelectColumns) { if (rows == null || rows.Count == 0) return; const long imageLimitBytes = 10 * 1024 * 1024; long embeddedBytes = 0; for (int r = 0; r < rows.Count; r++) { var row = rows[r]; for (int c = 0; c < exportCols.Count; c++) { string colName = exportCols[c]; row.TryGetValue(colName, out object value); int? viewType = cellOptions .Where(x => x.ColumnName == colName) .Select(x => x.ViewType) .FirstOrDefault(); // 日期格式 value = FormatDate(value, viewType); // 字典转换 if (value != null && dicNoKeys.Any(x => x.ColumnName == colName)) value = TranslateDictionary(cellOptions, multiSelectColumns, colName, value); // 图片 if (viewType == 1 && value != null) { embeddedBytes = HandleImageCell(sheet, r, c, value, embeddedBytes, imageLimitBytes); continue; } // long 按文本导出 if (value is long || value is long?) { sheet.Cells[r + 2, c + 1].Style.Numberformat.Format = "@"; value = value?.ToString(); } sheet.Cells[r + 2, c + 1].Value = value; } } } /// 按 viewType/类型格式化日期 private static object FormatDate(object value, int? viewType) { if (value == null) return null; // 6year年、5month年月、4date年月日 if (viewType == 6 || viewType == 5 || viewType == 4) { if (value is DateTime dt) { return viewType switch { 6 => (object)dt.Year, 5 => dt.ToString("yyyy-MM"), _ => dt.ToString("yyyy-MM-dd") }; } return value; } if (value is DateTime dt2) return dt2.ToString("yyyy-MM-dd HH:mm:sss"); return value; } /// 字典数据源转换(多选 / 单选) private static object TranslateDictionary( List cellOptions, string[] multiSelectColumns, string colName, object value) { if (value == null) return null; if (multiSelectColumns.Contains(colName)) { return string.Join(",", GetMultiDictValues(cellOptions, colName, value.ToString())); } var map = cellOptions .Where(x => x.ColumnName == colName) .Select(x => x.KeyValues) .FirstOrDefault(); if (map == null) return value; return map.TryGetValue(value.ToString(), out var show) ? (object)show : value; } /// 多选字典列:“1,2,3” => “名称1,名称2,名称3” private static IEnumerable GetMultiDictValues( List cellOptions, string colName, string raw) { var map = cellOptions .Where(x => x.ColumnName == colName) .Select(x => x.KeyValues) .FirstOrDefault(); var parts = raw.Split(','); if (map == null) { foreach (var p in parts) yield return p; yield break; } foreach (var p in parts) { yield return map.TryGetValue(p, out var show) ? show : p; } } /// 图片导出:嵌入或超链接 private static long HandleImageCell( ExcelWorksheet sheet, int rowIndex, int colIndex, object value, long embeddedBytes, long imageLimitBytes) { string imgPath = value.ToString(); if (string.IsNullOrWhiteSpace(imgPath)) return embeddedBytes; bool isHttp = imgPath.StartsWith("http", StringComparison.OrdinalIgnoreCase); if (!isHttp) { imgPath = ("".MapPath(true) + "\\" + imgPath).ReplacePath(); if (!File.Exists(imgPath)) { sheet.Cells[rowIndex + 2, colIndex + 1].Value = value; return embeddedBytes; } var fi = new FileInfo(imgPath); long size = fi.Length; bool canEmbed = embeddedBytes + size <= imageLimitBytes; if (canEmbed) { var pic = sheet.Drawings.AddPicture($"img_{rowIndex}_{colIndex}_{Guid.NewGuid():N}", fi); pic.SetPosition(rowIndex + 1, 1, colIndex, 1); pic.SetSize(80, 80); if (sheet.Row(rowIndex + 2).Height < 60) sheet.Row(rowIndex + 2).Height = 60; if (sheet.Column(colIndex + 1).Width < 15) sheet.Column(colIndex + 1).Width = 15; sheet.Cells[rowIndex + 2, colIndex + 1].Value = null; return embeddedBytes + size; } var fileUri = new Uri(fi.FullName); sheet.Cells[rowIndex + 2, colIndex + 1].Hyperlink = fileUri; sheet.Cells[rowIndex + 2, colIndex + 1].Value = Path.GetFileName(imgPath); return embeddedBytes; } if (Uri.TryCreate(imgPath, UriKind.Absolute, out var uri)) { sheet.Cells[rowIndex + 2, colIndex + 1].Hyperlink = uri; sheet.Cells[rowIndex + 2, colIndex + 1].Value = Path.GetFileName(imgPath); } return embeddedBytes; } } }