标签:
最近接了一个任务,就是做一个列表的Excel导出功能。并且有很多页面都会使用这个功能。
导出的Excel大体格式如图
很简单的列表,标题加背景色,然后不同类型,显示方式不一样。对齐方式不一样。不同页面除了内容以外,大体形式都差不多。
当时本来是想直接用NPOI,IRow ICell。这样进行拼接页面,最简单也最方便。
但是很多页面,都进行这种类似的设计。我实在是懒得做这种重复功能。所以花了一点时间,整理了一下帮助类。
做好这个帮助类以后只要进行两点调用
1.制作导出Excel的数据模型。这个没办法,毕竟要告诉系统,你要导出的内容都是什么东西。省略不了。
2.调用帮助类的导出方法。初始化一下,ExportExcel(导出的数据集合); 然后Excel就直接下载完毕了。
ExcelDownload downLoad = new ExcelDownload("员工信息", "年度员工汇总"); downLoad.ExportExcel(testList);
接着,说一下我的思路和具体写法。做的不到,想的不周全的地方。还希望各位告知。源码地址在文章结尾处。
因为我想做成使用最简单的类,所以我只暴露一个公共方法。其余一切都进行隐藏。
但是因为可能需要导出的其他格式的Excel,那么我也可以接受一个自定义的Excel填充方法。
这里我定义一个ExportExcel方法接受数据集合。并且添加一个重载方法,接受自定义Excel填充方法。
/// <summary> /// 导出Excel /// </summary> /// <param name="list">导出模型数据</param> public void ExportExcel<T>(IEnumerable<T> list) { GenerateExcel(list); DownLoadExcel(); } /// <summary> /// 导出Excel /// </summary> /// <param name="excelSetMethod">操作Excel方法</param> public void ExportExcel(Action<IWorkbook, ISheet> excelSetMethod) { excelSetMethod.Invoke(hssfWork, hssfSheet); DownLoadExcel(); }
方法里面的私有方法,稍后会详细说明。这里调用这一个方法,普通导出和自定义导出,均可实现。
首先要考虑的问题就是单元格的样式问题和列头名称问题。
如果不是公共方法,就非常简单了,直接在方法里面ICellStyle。但是既然是偷懒,肯定不能这样,而且又要考虑到可以自定义。
那么,我采用的方法就是给模型实体类使用Attribute。可以在实体类上面针对不同字段,设定不同的显示样式。
缺点也很显而易见,使用反射执行效率会稍微慢一点(各位有其他方法可以说一下)。
实体类的定义就如同下面
public class UserManagerTest { [ExcelInfo("名称")] public string Name { get; set; } [ExcelInfo("年龄", ExcelStyle = ExcelStyle.left)] public int Old { get; set; } [ExcelInfo("金额", ExcelStyle = ExcelStyle.money)] public double Money { get; set; } [ExcelInfo("时间", ExcelStyle = ExcelStyle.date | ExcelStyle.right)] public DateTime CreateDate { get; set; } }
可以定义中文名称显示标题、设置单元格样式、设置单元格宽度。
目前允许自定义的就这三个。
public class ExcelInfoAttribute : Attribute { /// <summary> /// 显示中文名 /// </summary> public string Name { get; set; } /// <summary> /// 列宽 /// </summary> public int Width { get; set; } /// <summary> /// 列样式 /// </summary> public ExcelStyle ExcelStyle { get; set; } /// <summary> /// 默认左对齐,宽度2800 /// </summary> public ExcelInfoAttribute(string name) { Name = name; Width = 2800; ExcelStyle = ExcelStyle.left; } }
ExcelStyle,是列样式的一个枚举,针对不同的值,会进行不同的单元格设置。
因为有可能一个单元格进行多种设置,例如时间格式并且右对齐。所以使用Flags,让其可以形成组。
[Flags] public enum ExcelStyle { /// <summary> /// 标题灰色背景 /// </summary> title = 0x001, /// <summary> /// 左对齐 /// </summary> left = 0x002, /// <summary> /// 右对齐 /// </summary> right = 0x004, /// <summary> /// 时间格式,右对齐 /// </summary> date = 0x008, /// <summary> /// 金钱格式,右对齐 /// </summary> money = 0x016 }
样式有了接下来就要把他对应成ICellStyle单元格样式,这样才能具体的进行操作。
这个时候,我们就需要一个针对样式枚举,返回单元格样式的方法。
这样方法会进行以下几点操作
单元格设定,因为样式会很多。所以这里使用了多态代替判断条件。
并且使用了一个键值对,来实现享元设计模式的思路。
internal static class ExcelStyleMessage { /// <summary> /// 样式集合 /// </summary> private static Dictionary<string, ICellStyle> styleList { get; set; } static ExcelStyleMessage() { styleList = new Dictionary<string, ICellStyle>(); } /// <summary> /// 获取枚举对应CellStyle /// </summary> /// <param name="excelStyle">Excel样式枚举</param> /// <returns></returns> internal static ICellStyle GetCellStyle<T>(T workbook, ExcelStyle excelStyle) where T : IWorkbook { if (styleList.ContainsKey(excelStyle.ToString())) { return styleList[excelStyle.ToString()]; } ICellStyle _cellStyle = workbook.CreateCellStyle(); _cellStyle.BorderTop = BorderStyle.Thin; _cellStyle.BorderRight = BorderStyle.Thin; _cellStyle.BorderLeft = BorderStyle.Thin; _cellStyle.BorderBottom = BorderStyle.Thin; CellStyleMethod styleMethod; if (excelStyle.ToString().IndexOf(‘,‘) > -1) { foreach (var styleItem in excelStyle.ToString().Replace(" ", "").Split(‘,‘)) { if (Enum.IsDefined(typeof(ExcelStyle), styleItem)) { ExcelStyle styleModel = (ExcelStyle)Enum.Parse(typeof(ExcelStyle), styleItem, true); styleMethod = GetStyleMethod(styleModel); styleMethod.SetCell(_cellStyle); } } return _cellStyle; } styleMethod = GetStyleMethod(excelStyle); styleMethod.SetCell(_cellStyle); styleList.Add(excelStyle.ToString(), _cellStyle); return _cellStyle; } /// <summary> /// 根据枚举加载对应操作类 /// </summary> /// <param name="excelStyle">样式枚举</param> /// <returns>操作类</returns> private static CellStyleMethod GetStyleMethod(ExcelStyle excelStyle) { switch (excelStyle) { case ExcelStyle.title: return new TitleBackgroundMethod(); case ExcelStyle.left: return new LeftAligmentMethod(); case ExcelStyle.right: return new RightAligmentMethod(); case ExcelStyle.date: return new DateFormatMethod(); case ExcelStyle.money: return new MoneyFormatMethod(); default: throw new ArgumentException("参数无效"); } } }
具体样式的多态,我就不粘贴了。就是一个很简单的抽象类,然后集成实现SetCell。
抽象类定义了workbook字段,用于创建IDataFormat。具体可以看最底下的源码地址。
首先考虑一下需要全局的字段和属性。Excel名称,页签名称,IWorkbook,ISheet。
那么,我们定义这四个,并且在构造函数里给他们赋值。
private readonly string _excelName; private readonly string _excelSheetName; private IWorkbook hssfWork { get; set; } private ISheet hssfSheet { get; set; } /// <summary> /// 初始化Excel相关信息 /// </summary> /// <param name="ExcelName">Excel名称</param> /// <param name="ExcelSheetName">初始页签名称</param> public ExcelDownload(string ExcelName, string ExcelSheetName) { _excelName = ExcelName; _excelSheetName = ExcelSheetName; hssfWork = new HSSFWorkbook(); hssfSheet = hssfWork.CreateSheet(_excelSheetName); }
自定义的Excel填充,就是一个执行然后下载。我们忽略,说一下通用的方法。
public void ExportExcel<T>(IEnumerable<T> list) { GenerateExcel(list); DownLoadExcel(); }
执行两个方法,1.生成Excel,2.下载Excel
在生成方法里面,我们给样式的workbook赋值,然后设置Excel的列表和单元格内容。
private void GenerateExcel<T>(IEnumerable<T> list) { if (list == null) { return; } CellStyleMethod.workbook = hssfWork; Dictionary<PropertyInfo, ExcelInfoAttribute> _excelInfos = GetPropInfo<T>(); SetExcelTitle(_excelInfos); SetExcelContent(list, _excelInfos); }
GetPropInfo 是一个反射的方法,反射实体类中的特性,并返回属性和样式特性的键值对。这个在设置里面会有用途。
private Dictionary<PropertyInfo, ExcelInfoAttribute> GetPropInfo<T>() { Dictionary<PropertyInfo, ExcelInfoAttribute> _infos = new Dictionary<PropertyInfo, ExcelInfoAttribute>(); Type _type = typeof(T); PropertyInfo[] _propInfos = _type.GetProperties(); foreach (var propInfo in _propInfos) { object[] objAttrs = propInfo.GetCustomAttributes(typeof(ExcelInfoAttribute), true); if (objAttrs.Length > 0) { ExcelInfoAttribute attr = objAttrs[0] as ExcelInfoAttribute; if (attr != null) { _infos.Add(propInfo, attr); } } } return _infos; }
SetExcelTitle 是设置Excel的标题。
private void SetExcelTitle(Dictionary<PropertyInfo, ExcelInfoAttribute> excelInfos) { IRow rowTitle = hssfSheet.CreateRow(0); int _cellIndex = 0; foreach (var item in excelInfos) { ICell celltitle = rowTitle.CreateCell(_cellIndex); celltitle.CellStyle = ExcelStyleMessage.GetCellStyle(hssfWork, ExcelStyle.title); celltitle.SetCellValue(item.Value.Name); hssfSheet.SetColumnWidth(_cellIndex, item.Value.Width); _cellIndex++; } }
SetExcelContent 是设置Excel的内容
private void SetExcelContent<T>(IEnumerable<T> list, Dictionary<PropertyInfo, ExcelInfoAttribute> _excelInfos) { int _rowNum = 1; Dictionary<string, ICellStyle> cellStyleList = new Dictionary<string, ICellStyle>(); foreach (T rowItem in list) { int _rowCell = 0; IRow _rowValue = hssfSheet.CreateRow(_rowNum); foreach (var cellItem in _excelInfos) { object _cellItemValue = cellItem.Key.GetValue(rowItem); ICell _cell = _rowValue.CreateCell(_rowCell); if (!cellStyleList.ContainsKey(cellItem.Value.ExcelStyle.ToString())) { ICellStyle _cellStyle = ExcelStyleMessage.GetCellStyle(hssfWork, cellItem.Value.ExcelStyle); cellStyleList.Add(cellItem.Value.ExcelStyle.ToString(), _cellStyle); } SetCellValue(cellItem, _cellItemValue, _cell); _cell.CellStyle = cellStyleList[cellItem.Value.ExcelStyle.ToString()]; _rowCell++; } _rowNum++; } }
这里就是使用键值对的属性,来获取Value值的。然后因为返回的object,所以我们进行一下类型转换。
SetCellValue 就是根据Type,来进行强制转换。
下载就更简单了,我们使用FileStream,进行一个临时存储。将NPOI写入这个文件中。然后将其转换成字节流,输出出来。
/// <summary> /// 导出Excel操作 /// </summary> private void DownLoadExcel() { string _path = TemporarySave(); FileStream fileStream = new FileStream(_path, FileMode.Open); int fileContent = (int)fileStream.Length; byte[] byData = new byte[fileContent]; fileStream.Read(byData, 0, fileContent); fileStream.Close(); File.Delete(_path); DownLoadExcel(byData); } /// <summary> /// 临时保存 /// </summary> /// <returns></returns> private string TemporarySave() { string _path = AppDomain.CurrentDomain.BaseDirectory; _path += string.Format(@"\TemporarySave{0}.xlsx", DateTime.Now.ToString("hhmmss")); using (FileStream file = new FileStream(_path, FileMode.Create)) { hssfWork.Write(file); file.Close(); } return _path; } /// <summary> /// 下载 /// </summary> private void DownLoadExcel(byte[] byData) { HttpContext.Current.Response.AddHeader("Content-Disposition", string.Format("attachment;filename={0}.xls", _excelName)); HttpContext.Current.Response.BinaryWrite(byData); HttpContext.Current.Response.Flush(); HttpContext.Current.Response.Close(); }
至此,这个帮助类的雏形,就算完成了。说着挺简单的,但是连想带写,也还是用了一下午。
因为,平时很少写这种博客,都是知识点记录。所以可能措辞方面不太妥当,请各位见谅。
源码地址:https://github.com/chenxygx/ExcelExportHelper
写的不好,多多见谅。
标签:
原文地址:http://www.cnblogs.com/chenxygx/p/5954870.html