根据数据源自定义字段实现动态导出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会自动回收;

 

posted @ 2019-08-12 17:39  代码驿站  阅读(2214)  评论(1编辑  收藏  举报