网站首页 网站源码
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using NPOI.SS.UserModel;
using NPOI.SS.Util;
using NPOI.XSSF.UserModel;
namespace Dpz.Core.Infrastructure.Excel;
/// <summary>
/// Excel导出扩展方法类
/// </summary>
public static class ExcelExtensions
{
/// <summary>
/// 根据标记导出Excel
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="columns">列配置</param>
/// <param name="sheetName">工作表名称</param>
/// <param name="startRow">起始行</param>
/// <param name="action">自定义处理操作</param>
/// <returns>Excel文件字节数组</returns>
public static byte[] ExportForMark<T>(
this IList<T> source,
IList<ExcelColumn> columns,
string sheetName,
int startRow = 0,
Action<ISheet>? action = null
)
where T : new()
{
var markProperties = GetMarkProperties(typeof(T));
using var workbook = new XSSFWorkbook();
var cellStyle = CreateCellStyle(workbook);
var sheet = workbook.CreateSheet(sheetName);
var rowCount = source.Count;
columns = columns
.Where(x => x.Show)
.IntersectBy(
markProperties.Select(x => x.Mark.MarkName ?? x.Property.Name),
x => x.ColumnName
)
.ToList();
var columnCount = columns.Any() ? columns.Count : markProperties.Count;
var row = sheet.CreateRow(startRow);
#region add column headers
var headerStyle = workbook.CreateCellStyle();
headerStyle.CloneStyleFrom(cellStyle);
headerStyle.FillForegroundColor = IndexedColors.Yellow.Index;
headerStyle.FillPattern = FillPattern.SolidForeground;
for (var i = 0; i < columnCount; i++)
{
string headValue;
if (columns.Any())
{
var (mark, _) = markProperties.FirstOrDefault(x =>
string.IsNullOrEmpty(x.Mark.MarkName)
? x.Property.Name == columns[i].ColumnName
: x.Mark.MarkName == columns[i].ColumnName
);
headValue = mark.MarkValue;
}
else
{
headValue = markProperties[i].Mark.MarkValue;
}
var cell = row.CreateCell(i);
cell.CellStyle = headerStyle;
cell.SetCellValue(headValue);
sheet.SetColumnWidth(i, 256 * 20);
}
#endregion
#region add data rows
for (var rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
var sheetRow = sheet.CreateRow(rowIndex + startRow + 1);
var item = source[rowIndex];
for (var columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
var (mark, property) = columns.Any()
? markProperties.FirstOrDefault(x =>
(x.Mark.MarkName ?? x.Property.Name) == columns[columnIndex].ColumnName
)
: markProperties[columnIndex];
var value = property.GetValue(item);
var cell = sheetRow.CreateCell(columnIndex);
cell.CellStyle = cellStyle;
SetCellValue(cell, value, mark);
if (columnIndex == columnCount)
{
sheet.AutoSizeColumn(columnIndex);
}
}
}
#endregion
if (action != null)
{
action(sheet);
}
else
{
sheet.SetAutoFilter(new CellRangeAddress(0, 0, 0, columns.Count - 1));
sheet.CreateFreezePane(0, 1);
}
using var stream = new MemoryStream();
workbook.Write(stream);
return stream.ToArray();
}
/// <summary>
/// 根据标记导出Excel(简化版)
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="sheetName">工作表名称</param>
/// <param name="startRow">起始行</param>
/// <param name="action">自定义处理操作</param>
/// <returns>Excel文件字节数组</returns>
public static byte[] ExportForMark<T>(
this IList<T> source,
string sheetName,
int startRow = 0,
Action<ISheet>? action = null
)
{
using var workbook = new XSSFWorkbook();
ProcessExcelExport(source, sheetName, startRow, action, workbook);
using var stream = new MemoryStream();
workbook.Write(stream);
return stream.ToArray();
}
/// <summary>
/// 处理Excel导出
/// </summary>
private static void ProcessExcelExport<T>(
IList<T> source,
string sheetName,
int startRow,
Action<ISheet>? action,
XSSFWorkbook workbook
)
{
var markProperties = GetMarkProperties(typeof(T));
var cellStyle = CreateCellStyle(workbook);
var sheet = workbook.CreateSheet(sheetName);
var rowCount = source.Count;
var columnCount = markProperties.Count;
CreateHeaderRow(startRow, sheet, workbook, cellStyle, columnCount, markProperties);
FillDataRows(source, startRow, rowCount, sheet, columnCount, markProperties, cellStyle);
if (action != null)
{
action(sheet);
}
else
{
sheet.SetAutoFilter(new CellRangeAddress(0, 0, 0, columnCount - 1));
sheet.CreateFreezePane(0, 1);
}
}
/// <summary>
/// 创建单元格样式
/// </summary>
private static ICellStyle CreateCellStyle(IWorkbook workbook)
{
var font = workbook.CreateFont();
font.FontName = "Calibri";
var cellStyle = workbook.CreateCellStyle();
// Set borders
cellStyle.BorderBottom = BorderStyle.Thin;
cellStyle.BottomBorderColor = IndexedColors.Black.Index;
cellStyle.BorderLeft = BorderStyle.Thin;
cellStyle.LeftBorderColor = IndexedColors.Black.Index;
cellStyle.BorderRight = BorderStyle.Thin;
cellStyle.RightBorderColor = IndexedColors.Black.Index;
cellStyle.BorderTop = BorderStyle.Thin;
cellStyle.TopBorderColor = IndexedColors.Black.Index;
cellStyle.SetFont(font);
return cellStyle;
}
private static readonly ConcurrentDictionary<Type, MethodInfo?> ProcessExcelExportMethodCache =
new();
/// <summary>
/// 导出多个工作表
/// </summary>
/// <param name="options">导出选项集合</param>
/// <returns>Excel文件字节数组</returns>
public static byte[]? ExportMultipleForMark(this IEnumerable<ExportMultipleSheet> options)
{
// 创建新的Excel工作簿
using var workbook = new XSSFWorkbook();
var hasValidGenericCollection = false;
foreach (var item in options)
{
// 获取数据源的类型
var sourceType = item.Source.GetType();
// 检查是否为泛型类型
if (sourceType.GetGenericArguments().Length <= 0)
{
continue;
}
// 获取泛型参数类型
var elementType = sourceType.GetGenericArguments()[0];
// 从缓存中获取或创建处理方法
var method = ProcessExcelExportMethodCache.GetOrAdd(
elementType,
type =>
typeof(ExcelExtensions)
.GetMethod(
nameof(ProcessExcelExport),
BindingFlags.NonPublic | BindingFlags.Static
)
?.MakeGenericMethod(type)
);
// 如果找到处理方法,则执行导出
if (method != null)
{
hasValidGenericCollection = true;
method.Invoke(
null,
[item.Source, item.SheetName, item.StartRow, item.Action, workbook]
);
}
}
if (!hasValidGenericCollection)
{
return null;
}
using var stream = new MemoryStream();
workbook.Write(stream);
return stream.ToArray();
}
/// <summary>
/// 填充数据行
/// </summary>
private static void FillDataRows<T>(
IList<T> source,
int startRow,
int rowCount,
ISheet sheet,
int columnCount,
List<MarkProperty> markProperties,
ICellStyle cellStyle
)
{
#region add data rows
for (var rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
var sheetRow = sheet.CreateRow(rowIndex + startRow + 1);
var item = source[rowIndex];
for (var columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
var (mark, property) = markProperties[columnIndex];
var value = property.GetValue(item);
var cell = sheetRow.CreateCell(columnIndex);
cell.CellStyle = cellStyle;
SetCellValue(cell, value, mark);
if (columnIndex == columnCount)
{
sheet.AutoSizeColumn(columnIndex);
}
}
}
#endregion
}
/// <summary>
/// 创建表头行
/// </summary>
private static void CreateHeaderRow(
int startRow,
ISheet sheet,
XSSFWorkbook workbook,
ICellStyle cellStyle,
int columnCount,
List<MarkProperty> markProperties
)
{
var row = sheet.CreateRow(startRow);
#region add column headers
var headerStyle = workbook.CreateCellStyle();
headerStyle.CloneStyleFrom(cellStyle);
headerStyle.FillForegroundColor = IndexedColors.Yellow.Index;
headerStyle.FillPattern = FillPattern.SolidForeground;
for (var i = 0; i < columnCount; i++)
{
var headValue = markProperties[i].Mark.MarkValue;
var cell = row.CreateCell(i);
cell.CellStyle = headerStyle;
cell.SetCellValue(headValue);
sheet.SetColumnWidth(i, 256 * 20);
}
#endregion
}
private readonly record struct MarkProperty(ExportMarkAttribute Mark, PropertyInfo Property);
private static readonly ConcurrentDictionary<Type, List<MarkProperty>> MarkPropertiesCache =
new();
/// <summary>
/// 获取标记属性
/// </summary>
private static List<MarkProperty> GetMarkProperties(Type type)
{
if (MarkPropertiesCache.TryGetValue(type, out var markProperties))
{
return markProperties;
}
markProperties = type.GetProperties()
.Select(x =>
{
var markAttr = x.GetCustomAttribute<ExportMarkAttribute>();
if (markAttr == null)
{
return (MarkProperty?)null;
}
return new MarkProperty(markAttr, x);
})
.Where(x => x != null)
.Select(x => x!.Value)
.ToList();
MarkPropertiesCache.TryAdd(type, markProperties);
return markProperties;
}
/// <summary>
/// 设置单元格值
/// </summary>
private static void SetCellValue(ICell cell, object? value, ExportMarkAttribute mark)
{
try
{
if (value is null || (value is string str && string.IsNullOrEmpty(str)))
{
cell.SetCellValue((string?)null);
return;
}
if (value is DateTime dateTimeValue)
{
var format = mark.Format ?? "yyyy-MM-dd";
cell.SetCellValue(dateTimeValue.ToString(format));
}
else if (value is int intValue)
{
if (string.IsNullOrEmpty(mark.Format))
{
cell.SetCellValue(intValue);
}
else
{
cell.SetCellValue(intValue.ToString(mark.Format));
}
}
else if (value is decimal decimalValue)
{
var dValue = (double)decimalValue;
if (string.IsNullOrEmpty(mark.Format))
{
cell.SetCellValue(dValue);
}
else
{
cell.SetCellValue(dValue.ToString(mark.Format));
}
}
else if (value is double doubleValue)
{
if (string.IsNullOrEmpty(mark.Format))
{
cell.SetCellValue(doubleValue);
}
else
{
cell.SetCellValue(doubleValue.ToString(mark.Format));
}
}
else if (value is bool boolValue)
{
cell.SetCellValue(boolValue);
}
else if (value is string strValue)
{
cell.SetCellValue(strValue);
}
else if (value is IEnumerable enumerable)
{
var enumerableResult = enumerable
.Cast<string>()
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToList();
var enumerableSplit = mark.EnumerableSplit ?? "\\";
var fillValue = string.Join(enumerableSplit, enumerableResult);
cell.SetCellValue(fillValue);
}
else
{
cell.SetCellValue(value.ToString());
}
}
catch (Exception)
{
cell.SetCellValue("");
}
}
}
上述代码是一个用于导出Excel文件的扩展方法类,使用了NPOI库来处理Excel文件的创建和写入。这个类提供了多种方法来根据特定的标记属性导出数据到Excel工作表中。以下是代码的主要功能和结构的详细解释:
导出Excel文件:
ExportForMark
方法,允许用户根据指定的列配置和数据源导出Excel文件。标记属性处理:
ExportMarkAttribute
特性来标记需要导出的属性。通过反射获取这些标记属性,并根据它们的配置生成Excel的列头和数据行。样式设置:
多工作表导出:
ExportMultipleForMark
方法,允许用户一次性导出多个工作表。每个工作表的数据源可以是不同类型的集合。数据填充:
FillDataRows
和CreateHeaderRow
等方法,分别处理数据行和表头的创建与填充。单元格值设置:
SetCellValue
方法,根据不同的数据类型(如字符串、日期、数字、布尔值等)设置单元格的值,并支持格式化。类和方法:
ExcelExtensions
类是一个静态类,包含多个静态方法。ExportForMark<T>
:根据标记导出Excel,支持列配置。ExportMultipleForMark
:导出多个工作表。ProcessExcelExport
:处理Excel导出的核心逻辑。CreateCellStyle
:创建单元格样式。GetMarkProperties
:获取标记属性。SetCellValue
:设置单元格的值。数据结构:
MarkProperty
:一个记录结构,包含标记属性和对应的属性信息。MarkPropertiesCache
:用于缓存标记属性,以提高性能。这个类可以用于需要将数据导出为Excel文件的场景,例如:
总体来说,这段代码提供了一个灵活且功能强大的Excel导出工具,能够根据用户定义的标记属性和配置生成格式良好的Excel文件,适用于多种数据导出需求。