码迷,mamicode.com
首页 > 其他好文 > 详细

偷懒小工具 - Excel导出公共类

时间:2016-10-13 02:11:59      阅读:351      评论:0      收藏:0      [点我收藏+]

标签:

说明

最近接了一个任务,就是做一个列表的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单元格样式,这样才能具体的进行操作。

这个时候,我们就需要一个针对样式枚举,返回单元格样式的方法。

这样方法会进行以下几点操作

  1. 设置单元格通用的属性。我这里设置的是单元格边框。
  2. 针对Flags组,分别进行对应的单元格设定,并且最终返回一个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("参数无效");
            }
        }
    }
Excel样式操作

具体样式的多态,我就不粘贴了。就是一个很简单的抽象类,然后集成实现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 

生成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;
        }
GetPropInfo

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++;
            }
        }
SetTitle

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++;
            }
        }
SetContent

这里就是使用键值对的属性,来获取Value值的。然后因为返回的object,所以我们进行一下类型转换。

SetCellValue 就是根据Type,来进行强制转换。

下载Excel

下载就更简单了,我们使用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

写的不好,多多见谅。

偷懒小工具 - Excel导出公共类

标签:

原文地址:http://www.cnblogs.com/chenxygx/p/5954870.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!