Files
SecMPS/api_sqlsugar/VolPro.Core/Generic/GenericExcelExportHelper.cs
2026-05-15 23:22:48 +08:00

301 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// 通用导出 Excel 辅助类(参照 EPPlusHelper.Export 的行为,
/// 实现字典数据源转换、字段类型显示、语言翻译等操作),仅返回内存字节,不落地文件。
/// </summary>
internal static class GenericExcelExportHelper
{
public static byte[] BuildExportBytes(
string tableName,
List<Dictionary<string, object>> rows,
string[] exportColumns,
List<string> 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<CellOptions>;
if (cellOptions == null || cellOptions.Count == 0)
return Array.Empty<byte>();
// 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();
}
/// <summary>确定最终导出的列顺序,并应用忽略字段</summary>
private static List<string> BuildExportColumns(List<CellOptions> cellOptions, List<string> ignoreColumns)
{
var cols = cellOptions.Select(c => c.ColumnName).ToList();
if (ignoreColumns != null && ignoreColumns.Count > 0)
{
var ignoreSet = new HashSet<string>(ignoreColumns.Select(x => x.ToLower()));
cols = cols.Where(c => !ignoreSet.Contains(c.ToLower())).ToList();
}
return cols;
}
/// <summary>写表头行(颜色、宽度、翻译与 EPPlusHelper.Export 中模板=false 一致)</summary>
private static void WriteHeaderRow(
ExcelWorksheet sheet,
List<CellOptions> cellOptions,
List<string> 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();
}
}
/// <summary>写数据行,包括日期格式、字典转换、图片及 long 文本处理</summary>
private static void WriteDataRows(
ExcelWorksheet sheet,
List<Dictionary<string, object>> rows,
List<string> exportCols,
List<CellOptions> 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;
}
}
}
/// <summary>按 viewType/类型格式化日期</summary>
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;
}
/// <summary>字典数据源转换(多选 / 单选)</summary>
private static object TranslateDictionary(
List<CellOptions> 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;
}
/// <summary>多选字典列“1,2,3” => “名称1,名称2,名称3”</summary>
private static IEnumerable<string> GetMultiDictValues(
List<CellOptions> 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;
}
}
/// <summary>图片导出:嵌入或超链接</summary>
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;
}
}
}