根據數據源自定義字段實現動態導出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會自動回收;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM