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 /// /// /// /// /// /// 文件名前缀 /// /// /// /// /// /// public static async Task DataExportAsync(string code, ExcelExportInfo data, string exportFileNamePrefix, IRepository _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(); var no = 1; foreach (var item in dic[key] as IList) { var itemDic = item.ConvertToDictionary(); ////处理集合里面时间类型,根据当前语言将时间转变为字符串 //foreach (var itemValuePair in itemDic) //{ // // 临床数据 1,1 会变成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 { /// /// 增加动态列开始索引 npoi从0开始 现在ClosedXML 从1开始 /// public int AutoColumnStartIndex { get; set; } /// /// 动态列开始的行index npoi从0开始 现在ClosedXML 从1开始 /// public int AutoColumnTitleRowIndex { get; set; } /// /// 模板列最后的索引 npoi从0开始 现在ClosedXML 从1开始 /// public int TempalteLastColumnIndex { get; set; } public bool IsCDISCExport { get; set; } = false; //public List CDISCList { get; set; } = new List(); /// /// 动态的列名 如果Id 重复,那么就按照名称填充,否则就按照Id 填充列数据 /// public List ColumnIdNameList { get; set; } /// /// 动态翻译的字典名 /// public List TranslateDicNameList { get; set; } = new List(); /// /// 动态取数据的集合名 /// public string DynamicListName { get; set; } /// /// 动态数据翻译的取字典的属性名 /// public string DynamicItemDicName { get; set; } /// /// 取值的属性名 /// public string DynamicItemValueName { get; set; } /// /// Excel Title Name /// public string DynamicItemTitleName { get; set; } public string DynamicItemTitleId { get; set; } public List RemoveColunmIndexList { get; set; } = new List(); 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 ColumnIdList => ColumnIdNameList == null ? new List() : ColumnIdNameList.Select(t => t.Id.ToString()).ToList(); public List ColumnNameList => ColumnIdNameList == null ? new List() : ColumnIdNameList.Select(t => t.Name).ToList(); } public static async Task<(MemoryStream, string)> DataExport_ClosedXMLAsync(string code, ExcelExportInfo data, IRepository _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 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>(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(); var no = 1; foreach (var item in dic[key] as IList) //foreach (var item in dic[key] as JArray) { //var itemDic = JsonConvert.DeserializeObject>(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 从0,ClosedXML 从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(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 MutiSheetDataExportAsync(string code, object data, string exportFileNamePrefix, IRepository _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" }; } /// /// 导出文件模板 /// /// /// public static async Task 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" }; } }