989 lines
38 KiB
C#
989 lines
38 KiB
C#
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)
|
||
//{
|
||
// // 临床数据 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
|
||
{
|
||
/// <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 从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<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"
|
||
};
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
} |