irc-netcore-api/IRaCIS.Core.Application/Helper/FileDocProcess/ExcelExportHelper.cs

989 lines
38 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using ClosedXML.Excel;
using DocumentFormat.OpenXml.Spreadsheet;
using IRaCIS.Application.Contracts;
using IRaCIS.Application.Interfaces;
using IRaCIS.Core.Application.Helper;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using MiniExcelLibs;
using MiniExcelLibs.OpenXml;
using Newtonsoft.Json;
using System.Collections;
using System.Globalization;
namespace IRaCIS.Core.Application.Service;
public static class ExcelExportHelper
{
//MiniExcel_Export
/// <summary>
///
/// </summary>
/// <param name="code"></param>
/// <param name="data"></param>
/// <param name="exportFileNamePrefix">文件名前缀</param>
/// <param name="_commonDocumentRepository"></param>
/// <param name="_hostEnvironment"></param>
/// <param name="_dictionaryService"></param>
/// <param name="translateType"></param>
/// <param name="criterionType"></param>
/// <returns></returns>
public static async Task<IActionResult> DataExportAsync(string code, ExcelExportInfo data, string exportFileNamePrefix, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment, IDictionaryService? _dictionaryService = null, Type? translateType = null, CriterionType? criterionType = null)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
//判断是否有字典翻译
object translateData = data;
if (_dictionaryService != null && translateType != null)
{
//一个值 对应不同的字典翻译
var needTranslatePropertyList = translateType.GetProperties().Where(t => t.IsDefined(typeof(DictionaryTranslateAttribute), true))
.SelectMany(c =>
c.GetCustomAttributes(typeof(DictionaryTranslateAttribute), false).Select(f => (DictionaryTranslateAttribute?)f).Where(t => t?.CriterionType == criterionType || t?.CriterionType == null)
.Select(k => new { c.Name, k.DicParentCode, k.IsTranslateDenpendOtherProperty, k.DependPropertyName, k.DependPropertyValueStr })
).ToList();
//字典表查询出所有需要翻译的数据
var translateDataList = await _dictionaryService.GetBasicDataSelect(needTranslatePropertyList.Select(t => t.DicParentCode).Distinct().ToArray());
var dic = data.ConvertToDictionary();
foreach (var key in dic.Keys)
{
//是数组 那么找到对应的属性 进行翻译
if (dic[key] != null && dic[key].GetType().GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)))
{
var newObjList = new List<object>();
var no = 1;
foreach (var item in dic[key] as IList)
{
var itemDic = item.ConvertToDictionary();
////处理集合里面时间类型,根据当前语言将时间转变为字符串
//foreach (var itemValuePair in itemDic)
//{
// // 临床数据 11 会变成2024-01-01
// if (itemValuePair.Value?.ToString().Length > 8 && DateTime.TryParse(itemValuePair.Value?.ToString(), out DateTime result))
// {
// itemDic[itemValuePair.Key] = ExportExcelConverterDate.DateTimeInternationalToString(result);
// }
//}
foreach (var needTranslateProperty in needTranslatePropertyList)
{
if (itemDic.Keys.Any(t => t == needTranslateProperty.Name))
{
//翻译的属性依赖其他属性
if (needTranslateProperty.IsTranslateDenpendOtherProperty)
{
if (itemDic[needTranslateProperty.DependPropertyName]?.ToString().ToLower() == needTranslateProperty.DependPropertyValueStr.ToLower())
{
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
}
}
//普通翻译 或者某一标准翻译
else
{
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
}
}
}
itemDic.Add("No", no++);
newObjList.Add(itemDic);
}
dic[key] = newObjList;
}
}
//data = dic;
translateData = dic;
}
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
//模板路径
var tplPath = physicalPath;
#region 根据中英文 删除模板sheet
// 打开模板文件
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
// 获取文件流
var templateStream = new MemoryStream();
templateFile.CopyTo(templateStream);
templateStream.Seek(0, SeekOrigin.Begin);
using var workbook = new XLWorkbook(templateStream);
int sheetCount = workbook.Worksheets.Count;
if (sheetCount == 2)
{
if (isEn_US)
{
workbook.Worksheets.Delete(1); // 删除第1个索引1
}
else
{
workbook.Worksheets.Delete(2); // 删除第2个索引2
}
//中文替换项目术语
if (data.TrialObjectNameList?.Count > 0)
{
var replaceObjectList = data.TrialObjectNameList;
// ClosedXML 获取第一个工作表
var worksheet = workbook.Worksheet(1); // 索引从1开始
#region sheet 名字修改 以及导表文件名字修改
var currentSheetName = worksheet.Name;
// 查找匹配的工作表名(支持部分匹配)
var findObj = replaceObjectList.FirstOrDefault(t => currentSheetName.Contains(t.Name));
if (findObj != null)
{
// 直接匹配或替换包含关键字的工作表名
var newSheetName = currentSheetName.Replace(findObj.Name, findObj.TrialName);
worksheet.Name = newSheetName;
}
var findFileName = replaceObjectList.FirstOrDefault(t => fileName.Contains(t.Name));
if (findFileName != null)
{
// 直接匹配或替换包含关键字的工作表名
var newFileName = currentSheetName.Replace(findFileName.Name, findFileName.TrialName);
fileName = newFileName;
}
#endregion
// 获取使用的行范围(不包括空行)
var rowsUsed = worksheet.RowsUsed();
foreach (var row in rowsUsed)
{
// 获取该行有数据的单元格
var cellsUsed = row.CellsUsed(c => c.DataType == XLDataType.Text);
foreach (var cell in cellsUsed)
{
var cellValue = cell.GetString();
var newValue = cellValue;
var isChanged = false;
var find = replaceObjectList.FirstOrDefault(t => cellValue.Trim() == t.Name.Trim());
if (find != null)
{
newValue = newValue.Replace(find.Name, find.TrialName);
isChanged = true;
}
else
{
// 遍历所有替换规则,进行关键字匹配替换
foreach (var replaceItem in replaceObjectList.Where(t => cellValue.Contains(t.Name)))
{
newValue = newValue.Replace(replaceItem.Name, replaceItem.TrialName);
isChanged = true;
}
}
if (isChanged)
{
cell.SetValue(newValue);
}
}
}
}
using (var memoryStream2 = new MemoryStream())
{
workbook.SaveAs(memoryStream2);
memoryStream2.Seek(0, SeekOrigin.Begin);
templateStream = memoryStream2;
}
}
// 文件名称 从sheet里面取
//fileNmae = workbook.GetSheetName(0);
#endregion
#region MiniExcel
var memoryStream = new MemoryStream();
var config = new OpenXmlConfiguration()
{
IgnoreTemplateParameterMissing = true,
};
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), translateData, config);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = $"{(string.IsNullOrEmpty(exportFileNamePrefix) ? "" : exportFileNamePrefix + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
};
#endregion
}
public class DynamicColumnConfig
{
/// <summary>
/// 增加动态列开始索引 npoi从0开始 现在ClosedXML 从1开始
/// </summary>
public int AutoColumnStartIndex { get; set; }
/// <summary>
/// 动态列开始的行index npoi从0开始 现在ClosedXML 从1开始
/// </summary>
public int AutoColumnTitleRowIndex { get; set; }
/// <summary>
/// 模板列最后的索引 npoi从0开始 现在ClosedXML 从1开始
/// </summary>
public int TempalteLastColumnIndex { get; set; }
public bool IsCDISCExport { get; set; } = false;
//public List<string> CDISCList { get; set; } = new List<string>();
/// <summary>
/// 动态的列名 如果Id 重复那么就按照名称填充否则就按照Id 填充列数据
/// </summary>
public List<ColumItem> ColumnIdNameList { get; set; }
/// <summary>
/// 动态翻译的字典名
/// </summary>
public List<string> TranslateDicNameList { get; set; } = new List<string>();
/// <summary>
/// 动态取数据的集合名
/// </summary>
public string DynamicListName { get; set; }
/// <summary>
/// 动态数据翻译的取字典的属性名
/// </summary>
public string DynamicItemDicName { get; set; }
/// <summary>
/// 取值的属性名
/// </summary>
public string DynamicItemValueName { get; set; }
/// <summary>
/// Excel Title Name
/// </summary>
public string DynamicItemTitleName { get; set; }
public string DynamicItemTitleId { get; set; }
public List<int> RemoveColunmIndexList { get; set; } = new List<int>();
public class ColumItem
{
public Guid Id { get; set; }
public string Name { get; set; }
public string CDISCCode { get; set; }
public override bool Equals(object obj)
{
if (obj is not ColumItem other) return false;
return Id == other.Id && Name == other.Name;
}
public override int GetHashCode()
{
return HashCode.Combine(Id, Name);
}
}
public List<string> ColumnIdList => ColumnIdNameList == null ? new List<string>() : ColumnIdNameList.Select(t => t.Id.ToString()).ToList();
public List<string> ColumnNameList => ColumnIdNameList == null ? new List<string>() : ColumnIdNameList.Select(t => t.Name).ToList();
}
public static async Task<(MemoryStream, string)> DataExport_ClosedXMLAsync(string code, ExcelExportInfo data, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment, IDictionaryService? _dictionaryService = null, Type? translateType = null, CriterionType? criterionType = null, DynamicColumnConfig? dynamicColumnConfig = null)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
//判断是否有字典翻译
object translateData = data;
Dictionary<string, object> translatedDic = default;
if (_dictionaryService != null && translateType != null)
{
//一个值 对应不同的字典翻译
var needTranslatePropertyList = translateType.GetProperties().Where(t => t.IsDefined(typeof(DictionaryTranslateAttribute), true))
.SelectMany(c =>
c.GetCustomAttributes(typeof(DictionaryTranslateAttribute), false).Select(f => (DictionaryTranslateAttribute?)f).Where(t => t.CriterionType == criterionType || t.CriterionType == null)
.Select(k => new { c.Name, k.DicParentCode, k.IsTranslateDenpendOtherProperty, k.DependPropertyName, k.DependPropertyValueStr })
).ToList();
//字典表查询出所有需要翻译的数据
var translateDataList = await _dictionaryService.GetBasicDataSelect(needTranslatePropertyList.Select(t => t.DicParentCode).Distinct().ToArray());
var dic = data.ConvertToDictionary();
//var dic = (JsonConvert.DeserializeObject<IDictionary<string, object>>(data.ToJsonNotIgnoreNull())).IfNullThrowException();
foreach (var key in dic.Keys)
{
//是数组 那么找到对应的属性 进行翻译
if (dic[key] != null && dic[key].GetType().GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)))
//if (dic[key].GetType().IsAssignableFrom(typeof(JArray)))
{
var newObjList = new List<object>();
var no = 1;
foreach (var item in dic[key] as IList)
//foreach (var item in dic[key] as JArray)
{
//var itemDic = JsonConvert.DeserializeObject<IDictionary<string, object>>(item.ToJsonNotIgnoreNull());
var itemDic = item.ConvertToDictionary();
////处理集合里面时间类型,根据当前语言将时间转变为字符串
//foreach (var itemValuePair in itemDic)
//{
// if (itemValuePair.Value?.ToString().Length > 8 && DateTime.TryParse(itemValuePair.Value?.ToString(), out DateTime result))
// {
// itemDic[itemValuePair.Key] = ExportExcelConverterDate.DateTimeInternationalToString(result);
// }
//}
foreach (var needTranslateProperty in needTranslatePropertyList)
{
if (itemDic.Keys.Any(t => t == needTranslateProperty.Name))
{
//翻译的属性依赖其他属性
if (needTranslateProperty.IsTranslateDenpendOtherProperty)
{
if (itemDic[needTranslateProperty.DependPropertyName]?.ToString().ToLower() == needTranslateProperty.DependPropertyValueStr.ToLower())
{
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
}
}
//普通翻译 或者某一标准翻译
else
{
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
}
}
}
itemDic.Add("No", no++);
newObjList.Add(itemDic);
}
dic[key] = newObjList;
}
}
//data = dic;
translateData = dic;
translatedDic = dic;
}
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
//模板路径
var tplPath = physicalPath;
#region 根据中英文 删除模板sheet
// 打开模板文件
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
// 获取文件流
var templateStream = new MemoryStream();
templateFile.CopyTo(templateStream);
templateStream.Seek(0, SeekOrigin.Begin);
using var workbook = new XLWorkbook(templateStream);
int sheetCount = workbook.Worksheets.Count;
if (sheetCount == 2)
{
if (isEn_US)
{
workbook.Worksheets.Delete(1); // 删除第1个索引1
}
else
{
workbook.Worksheets.Delete(2); // 删除第2个索引2
}
#region 中文替换项目术语
//中文替换项目术语
if (data.TrialObjectNameList?.Count > 0)
{
var replaceObjectList = data.TrialObjectNameList;
// ClosedXML 获取第一个工作表
var worksheet = workbook.Worksheet(1); // 索引从1开始
#region sheet 名字修改 以及导表文件名字修改
var currentSheetName = worksheet.Name;
// 查找匹配的工作表名(支持部分匹配)
var findObj = replaceObjectList.FirstOrDefault(t => currentSheetName.Contains(t.Name));
if (findObj != null)
{
// 直接匹配或替换包含关键字的工作表名
var newSheetName = currentSheetName.Replace(findObj.Name, findObj.TrialName);
worksheet.Name = newSheetName;
}
var findFileName = replaceObjectList.FirstOrDefault(t => fileName.Contains(t.Name));
if (findFileName != null)
{
// 直接匹配或替换包含关键字的工作表名
var newFileName = currentSheetName.Replace(findFileName.Name, findFileName.TrialName);
fileName = newFileName;
}
#endregion
// 获取使用的行范围(不包括空行)
var rowsUsed = worksheet.RowsUsed();
foreach (var row in rowsUsed)
{
// 获取该行有数据的单元格
var cellsUsed = row.CellsUsed(c => c.DataType == XLDataType.Text);
foreach (var cell in cellsUsed)
{
var cellValue = cell.GetString();
var newValue = cellValue;
var isChanged = false;
var find = replaceObjectList.FirstOrDefault(t => cellValue.Trim() == t.Name.Trim());
if (find != null)
{
newValue = newValue.Replace(find.Name, find.TrialName);
isChanged = true;
}
else
{
// 遍历所有替换规则,进行关键字匹配替换
foreach (var replaceItem in replaceObjectList.Where(t => cellValue.Contains(t.Name)))
{
newValue = newValue.Replace(replaceItem.Name, replaceItem.TrialName);
isChanged = true;
}
}
if (isChanged)
{
cell.SetValue(newValue);
}
}
}
}
#endregion
if (dynamicColumnConfig != null)
{
var isCdics = dynamicColumnConfig.IsCDISCExport;
// ClosedXML 获取工作表索引从1开始
var worksheet = workbook.Worksheet(1);
// 获取行ClosedXML 行索引从1开始AutoColumnTitleRowIndex 已经改了保持不变)
var cdicsRow = worksheet.Row(dynamicColumnConfig.AutoColumnTitleRowIndex - 1); // 原 NPOI: -1
var titelRow = worksheet.Row(dynamicColumnConfig.AutoColumnTitleRowIndex); // 原 NPOI: 不变
var templateRow = worksheet.Row(dynamicColumnConfig.AutoColumnTitleRowIndex + 1); // 原 NPOI: +1
// 动态移除列的数量
var dynamicRemoveColunmCount = dynamicColumnConfig.RemoveColunmIndexList.Count();
// 在动态列开始前移除的数量
var beforeDynamicRemoveCount = dynamicColumnConfig.RemoveColunmIndexList.Count(t => t < dynamicColumnConfig.AutoColumnStartIndex);
// 动态添加列的数量
var needAddCount = dynamicColumnConfig.ColumnIdNameList.Count;
// 原始表最终索引NPOI 从0开始ClosedXML 从1开始但这里存储的是逻辑索引后续转换
var originTotalEndIndex = dynamicColumnConfig.TempalteLastColumnIndex;
// 减去动态移除后原始结束索引
var originRemoveEndIndex = originTotalEndIndex - dynamicRemoveColunmCount;
// 最终表动态列开始索引
var dynamicColunmStartIndex = dynamicColumnConfig.AutoColumnStartIndex - beforeDynamicRemoveCount;
// 最终表动态列的终止索引
var dynamicColunmEndIndex = dynamicColunmStartIndex + needAddCount - 1;
// 最终表最终索引
var totalColunmEndIndex = originTotalEndIndex + needAddCount - dynamicRemoveColunmCount;
// 删除需要动态删除的列 从大到小移除,否则索引会变
// 动态列后需要移动的数量
var backMoveCount = totalColunmEndIndex - dynamicColunmEndIndex;
// 删除需要动态删除的列(从大到小移除,否则索引会变)
// 注意ClosedXML 可以直接使用 Delete() 方法,但为了保持原逻辑,这里使用手动移动
foreach (var removeIndex in dynamicColumnConfig.RemoveColunmIndexList.OrderByDescending(t => t))
{
// 将后面的列向前移动
for (var i = 0; i < originTotalEndIndex - removeIndex; i++)
{
int currentCol = removeIndex + i;
int nextCol = removeIndex + i + 1;
Console.WriteLine(titelRow.Cell(nextCol).GetString());
// 移动值
titelRow.Cell(currentCol).SetValue(titelRow.Cell(nextCol).GetString());
templateRow.Cell(currentCol).SetValue(templateRow.Cell(nextCol).GetString());
// 清空后面的数据
titelRow.Cell(nextCol).SetValue(string.Empty);
templateRow.Cell(nextCol).SetValue(string.Empty);
}
}
// 创建新的列ClosedXML 不需要显式创建,直接设置值即可)
for (int i = originRemoveEndIndex; i < originRemoveEndIndex + needAddCount; i++)
{
int colIndex = i + 1;
// 不需要 CreateCell直接设置值即可
if (isCdics)
{
cdicsRow.Cell(colIndex).SetValue(string.Empty);
}
titelRow.Cell(colIndex).SetValue(string.Empty);
templateRow.Cell(colIndex).SetValue(string.Empty);
}
// 移动 Title 和下面的模板标识
var gap = totalColunmEndIndex - originRemoveEndIndex;
for (int i = totalColunmEndIndex; i > dynamicColunmEndIndex; i--)
{
int currentCol = i;
int sourceCol = i - gap;
titelRow.Cell(currentCol).SetValue(titelRow.Cell(sourceCol).GetString());
templateRow.Cell(currentCol).SetValue(templateRow.Cell(sourceCol).GetString());
if (isCdics)
{
cdicsRow.Cell(currentCol).SetValue(cdicsRow.Cell(sourceCol).GetString());
}
}
// 设置动态 Title
for (int i = dynamicColunmStartIndex; i < dynamicColunmStartIndex + needAddCount; i++)
{
int colIndex = i;
var index = i - dynamicColunmStartIndex;
var name = dynamicColumnConfig.ColumnIdNameList[index].Name;
titelRow.Cell(colIndex).SetValue(name);
templateRow.Cell(colIndex).SetValue(string.Empty);
if (isCdics)
{
var cdicsCode = dynamicColumnConfig.ColumnIdNameList[index].CDISCCode;
cdicsRow.Cell(colIndex).SetValue(cdicsCode);
}
}
}
using (var memoryStream2 = new MemoryStream())
{
workbook.SaveAs(memoryStream2);
memoryStream2.Seek(0, SeekOrigin.Begin);
templateStream = memoryStream2;
}
}
#endregion
#region MiniExcel
var memoryStream = new MemoryStream();
var config = new OpenXmlConfiguration()
{
IgnoreTemplateParameterMissing = true,
};
//await MiniExcel.SaveAsByTemplateAsync("testmini.xlsx", templateStream.ToArray(), translateData);
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), translateData, config);
memoryStream.Seek(0, SeekOrigin.Begin);
if (dynamicColumnConfig != null)
{
//Excel 列是按照名称填充 还是Id 填充
var isExcelAddDataWithName = dynamicColumnConfig.ColumnIdNameList.Select(t => t.Id).Distinct().Count() == 1;
var dynamicTranslateDataList = await _dictionaryService.GetBasicDataSelect(dynamicColumnConfig.TranslateDicNameList.ToArray());
// 使用 ClosedXML 进行二次处理
using var wb = new XLWorkbook(memoryStream);
var worksheet = wb.Worksheet(1); // 获取第一个工作表索引从1开始
var list = translatedDic["List"] as IList;
foreach (var itemResult in list)
{
var index = list.IndexOf(itemResult);
// 从第四行开始处理动态列
var row = worksheet.Row(index + dynamicColumnConfig.AutoColumnTitleRowIndex + 1); // +2 因为 NPOI 从0ClosedXML 从1
var itemDic = itemResult.ToDictionary();
var itemList = itemDic[dynamicColumnConfig.DynamicListName] as IList;
//这个数组是动态的有的多有的少所以在此对比Title 一致才赋值
foreach (var itemObj in itemList)
{
var iteObjDic = itemObj.ToDictionary();
var itemDicName = iteObjDic.ContainsKey(dynamicColumnConfig.DynamicItemDicName) ? iteObjDic[dynamicColumnConfig.DynamicItemDicName]?.ToString() : "";
var itemValue = iteObjDic[dynamicColumnConfig.DynamicItemValueName]?.ToString();
//var writeIndex = itemList.IndexOf(itemObj) + dynamicColumnConfig.AutoColumnStartIndex;
var writeIndex = 0;
if (isExcelAddDataWithName)
{
writeIndex = dynamicColumnConfig.ColumnNameList.IndexOf(iteObjDic[dynamicColumnConfig.DynamicItemTitleName].ToString()) + dynamicColumnConfig.AutoColumnStartIndex; // +1 因为 ClosedXML 列从1开始
}
else
{
writeIndex = dynamicColumnConfig.ColumnIdList.IndexOf(iteObjDic[dynamicColumnConfig.DynamicItemTitleId].ToString()) + dynamicColumnConfig.AutoColumnStartIndex; // +1 因为 ClosedXML 列从1开始
}
if (itemDicName.IsNotNullOrEmpty())
{
var optionTypeEnumStr = iteObjDic.ContainsKey("OptionTypeEnum") ? iteObjDic["OptionTypeEnum"]?.ToString() : "0";
var translatedItemData = "";
//多选
if (optionTypeEnumStr == "1")
{
int[] enumValues = new int[0];
// 1. 反序列化 JSON 数组 (字符串枚举)
if (!itemValue.StartsWith("[") || !itemValue.EndsWith("]"))
{
enumValues = new int[1] { int.Parse(itemValue) };
}
else
{
enumValues = JsonConvert.DeserializeObject<int[]>(itemValue) ?? new int[0];
}
// 2. 翻译每一项并输出逗号拼接字符串
translatedItemData = string.Join(",",
enumValues.Select(code =>
dynamicTranslateDataList[itemDicName]
.FirstOrDefault(t =>
string.Equals(code.ToString(), t.Code, StringComparison.OrdinalIgnoreCase)
) is var r && r != null
? (isEn_US ? r.Value : r.ValueCN)
: string.Empty
));
}
else
{
translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
}
// ClosedXML 设置单元格值
row.Cell(writeIndex).SetValue(translatedItemData);
}
else
{
var unit = iteObjDic.ContainsKey("Unit") ? iteObjDic["Unit"]?.ToString() : null;
if (unit.IsNotNullOrEmpty() && unit == "9")
{
row.Cell(writeIndex).SetValue(itemValue + "%");
}
else
{
row.Cell(writeIndex).SetValue(itemValue);
}
}
}
}
var memoryStream2 = new MemoryStream();
wb.SaveAs(memoryStream2, true);
memoryStream2.Seek(0, SeekOrigin.Begin);
memoryStream = memoryStream2;
}
return (memoryStream, fileName);
#endregion
}
public static async Task<IActionResult> MutiSheetDataExportAsync(string code, object data, string exportFileNamePrefix, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment)
{
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
//模板路径
var tplPath = physicalPath;
var templateStream = new MemoryStream();
#region npoi 移除某一行
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
if (isEn_US)
{
// 打开模板文件
using var templateFileStream = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
using var workbook = new XLWorkbook(templateFileStream);
int sheetCount = workbook.Worksheets.Count;
int removeRowIndex = 2; // ClosedXML 中行索引从 1 开始所以第2行对应 NPOI 的第1行
for (int i = 1; i <= sheetCount; i++) // ClosedXML 工作表索引从 1 开始
{
var worksheet = workbook.Worksheet(i);
// 删除行
var row = worksheet.Row(removeRowIndex);
if (!row.IsEmpty() || row.CellsUsed().Any()) // 检查行是否存在数据
{
row.Delete(); // ClosedXML 直接删除行
}
}
workbook.SaveAs(templateStream);
templateStream.Position = 0;
}
else
{
using (var fs = new FileStream(tplPath, FileMode.Open, FileAccess.Read))
{
fs.CopyTo(templateStream);
}
templateStream.Position = 0;
}
#endregion
var memoryStream = new MemoryStream();
var config = new OpenXmlConfiguration()
{
IgnoreTemplateParameterMissing = true,
};
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), data, config);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = $"{(string.IsNullOrEmpty(exportFileNamePrefix) ? "" : exportFileNamePrefix + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
};
}
/// <summary>
/// 导出文件模板
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
public static async Task<FileResult> ExportTemplateAsync(ExportTemplateServiceDto inDto)
{
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(inDto.hostEnvironment, inDto.commonDocumentRepository, inDto.TemplateCode);
//模板路径
var tplPath = physicalPath;
#region 根据中英文删除模板 sheet使用 ClosedXML
using var templateFileStream = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
using var workbook = new XLWorkbook(templateFileStream);
int sheetCount = workbook.Worksheets.Count;
if (sheetCount == 2)
{
if (inDto.IsEnglish)
{
workbook.Worksheet(1).Delete(); // 删除第一个工作表
}
else
{
workbook.Worksheet(2).Delete(); // 删除第二个工作表
}
}
#endregion
// 获取第一个工作表(删除后剩下的那个)
var worksheet = workbook.Worksheet(1);
// 遍历所有行和列,替换模板占位符
var rowsUsed = worksheet.RowsUsed();
foreach (var row in rowsUsed)
{
var cellsUsed = row.CellsUsed(c => c.DataType == XLDataType.Text);
foreach (var cell in cellsUsed)
{
var cellValue = cell.GetString();
// 检查是否包含模板占位符 {{PropertyName}}
foreach (var property in inDto.Data.GetType().GetProperties())
{
var placeholder = "{{" + property.Name + "}}";
if (cellValue.Contains(placeholder))
{
var value = property.GetValue(inDto.Data);
var newValue = cellValue.Replace(placeholder, value?.ToString() ?? string.Empty);
cell.SetValue(newValue);
}
}
}
}
var memoryStream = new MemoryStream();
workbook.SaveAs(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = $"{(string.IsNullOrEmpty(inDto.ExportFileName) ? "" : inDto.ExportFileName + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
};
}
}