根据数据源自定义字段实现动态导出Excel
前言
最近遇到了一个需求,需要导出各种订单数据,可能是因为懒吧,不想重新写查询然后导出数据;就有了下边的这些操作了。
具体实现方式
1),定义一个泛型类来接收我们要导出的数据源,(需要EPPlus包)代码如下:
1 /// <summary> 2 /// 传入数据,返回Excel流文件 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 /// <param name="TSources">数据源</param> 6 /// <param name="sheetName">sheet名字</param> 7 /// <returns></returns> 8 private async Task<MemoryStream> GetExcelMemoryStreams<T>(List<T> TSources, string sheetName = "sheet1") 9 { 10 return await Task.Run(() => 11 { 12 MemoryStream ms = new MemoryStream(); 13 if (TSources.Any()) 14 { 15 using (ExcelPackage package = new ExcelPackage()) 16 { 17 ExcelWorksheet sheet = package.Workbook.Worksheets.Add(sheetName); 18 19 20 //获取传进来的类型 21 Type type = typeof(T); 22 var propertiesList = type.GetProperties(); 23 24 for (int row = 1; row <= TSources.Count() + 1; row++) 25 { 26 var index = 0; 27 for (int cl = 1; cl <= propertiesList.Length; cl++) 28 { 29 //获取备注名字 30 var displayName = propertiesList[cl - 1].GetExportExcelDisplayName(); 31 //判断字段是否有自定义属性(ExportExcelColumnAttribute) 32 if (displayName != "false") 33 { 34 index++; 35 if (row == 1) //设置表头 36 sheet.Cells[row, index].Value = displayName; 37 else 38 { 39 //获取字段名字 40 var Name = propertiesList[cl - 1].Name; 41 //获取对应的值 42 var value = TSources[row - 2].GetType().GetProperty(Name)?.GetValue(TSources[row - 2])?.ToString(); 43 sheet.Cells[row, index].Value = value; 44 } 45 } 46 } 47 } 48 49 //设置Excel列宽 50 sheet.Cells.AutoFitColumns(1.5); 51 52 package.SaveAs(ms); 53 ms.Position = 0; 54 } 55 } 56 else 57 throw new UserFriendlyException($"{sheetName}暂无数据!"); 58 59 return ms; 60 }); 61 }
2),定义实体属性标识导出列,代码如下:
1 /// <summary> 2 /// Excel导出用 3 /// </summary> 4 [AttributeUsage(AttributeTargets.Property)]//标记只能用在属性上面 5 public class ExportExcelColumnAttribute : Attribute 6 { 7 private string _Name = null; 8 9 /// <summary> 10 /// 构造涵数传入值 11 /// </summary> 12 /// <param name="Name"></param> 13 public ExportExcelColumnAttribute(string Name) 14 { 15 this._Name = Name; 16 } 17 18 public string GetDisplayName() 19 { 20 return _Name; 21 } 22 }
3),定义获取该属性的备注名(Excel导出列名)方法,代码如下:
1 /// <summary> 2 /// 获取属性设置的导出备注 3 /// </summary> 4 /// <param name="prop"></param> 5 /// <returns></returns> 6 public static string GetExportExcelDisplayName(this PropertyInfo prop) 7 { 8 if (prop.IsDefined(typeof(ExportExcelColumnAttribute), true)) 9 { 10 ExportExcelColumnAttribute attribute = (ExportExcelColumnAttribute)prop.GetCustomAttribute(typeof(ExportExcelColumnAttribute), true); 11 return attribute.GetDisplayName(); 12 } 13 else 14 { 15 return "false"; 16 } 17 }
4),在实体上标注要导出的字段
1 public class GetServerOrderListDto : NullableIdDto<long> 2 { 3 /// <summary> 4 /// 订单编号 5 /// </summary> 6 [ExportExcelColumn("订单编号")] 7 public string OrdernNumber { get; set; } 8 9 /// <summary> 10 /// 赠购单id 11 /// </summary> 12 public long? OrderID { get; set; } 13 14 /// <summary> 15 /// 赠送单号 16 /// </summary> 17 [ExportExcelColumn("赠送单号")] 18 public string ComplimentaryOrderNumber { get; set; } 19 /// <summary> 20 /// 收费单号 21 /// </summary> 22 [ExportExcelColumn("收费单号")] 23 public string AdditionalOrdernNumber { get; set; } 24 25 /// <summary> 26 /// 客户名称 27 /// </summary> 28 [ExportExcelColumn("客户名称")] 29 public string CompanyName { get; set; } 30 }
5),接口服务调用:
6).控制器需要将返回的内存流转化为文件流(FileStreamResult)返回到前端响应。
7).前端js代码
//点击导出按钮 $("#export_excel").click(function () { var input = { .... }; const req = new XMLHttpRequest(); req.open('Post', '你的请求地址', true); req.responseType = 'blob'; req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); req.onload = function () { const data = req.response; const blob = new Blob([data]); let blobUrl = window.URL.createObjectURL(blob); download(blobUrl, "你要导出的文件名"); }; var str = ""; var keys = Object.keys(input); $.each(keys, function (idx, val) { str += `&${val}=${input[val]}`; }); console.log(str.substring(1, str.length)) req.send(str); }); function download(blobUrl,fileName) { const a = document.createElement('a'); a.style.display = 'none'; a.download = fileName; a.href = blobUrl; a.click(); document.body.removeChild(a); }
温馨提示
a),上述第34行 index 作用主要是防止出现空白列,(实体列中不标识某列导出时(如上述实体 赠送订单ID),会显示空白列的问题:如下图情况:);
b),导出列会比较窄,我们需要设置自适应宽度: sheet.Cells.AutoFitColumns(1.5); 调整列宽;
c),返回的内存流 MemoryStream 返回的时候需要将位置至为0(代码中:ms.Position = 0;);因为Read()方法是从当前流的Position属性的位置开始读,这就是为什么很多人测试的时候,刚刚写入内存的数据,Read()方法无法读取到内容的原因,因为刚刚写入内存之后,位置恰好是在最后一位了。Read()方法当然读不到。此方法强大之处在于,你可以从一个内存流中读出你想要的一个片段。
d),using块的 ExcelPackage对象不需要手动释放,因为他继承了IDisposable,在使用完之后会自动释放;如下图:
e),MemoryStream 是一个特例,MemoryStream中没有任何非托管资源,所以它的Dispose不调用也没关系;托管资源.Net会自动回收;