前言
最近遇到了一個需求,需要導出各種訂單數據,可能是因為懶吧,不想重新寫查詢然后導出數據;就有了下邊的這些操作了。
具體實現方式
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會自動回收;