Initial_commit_SecMPS_v2
This commit is contained in:
301
api_sqlsugar/VolPro.Core/Generic/GenericExcelExportHelper.cs
Normal file
301
api_sqlsugar/VolPro.Core/Generic/GenericExcelExportHelper.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user