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;
}
}
}