zoukankan      html  css  js  c++  java
  • 根据数据源自定义字段实现动态导出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会自动回收;

  • 相关阅读:
    MySql安装方法和配置、解决中文乱码
    医疗器械软件安全性级别判定
    工业镜头视场、倍率、焦距之间的关系
    C#一个进程监控另一进程的用户界面是否响应系统
    TextEdit验证
    "tsc.exe"已退出,代码1
    截取上传视频的第一帧作为封面的方法
    汉堡也会结对子
    第二次作业——全国中小学生数学(四则运算)竞赛
    第二次作业——全国中小学生数学(四则运算)竞赛(更改版)
  • 原文地址:https://www.cnblogs.com/Jinfeng1213/p/11341529.html
Copyright © 2011-2022 走看看