ClosedXML、DocumentFormat.OpenXml導出DataTable到Excel (支持多行合並列標題 )


在CLosedXML提供了很簡單的DataTable導出。

請參考:C# CLosedXML四句代碼搞定DataTable數據導出到Excel https://www.cnblogs.com/MorganMa/p/13139418.html

public void Export(DataTable data)
{
// 這種方法導出方便,但 首行自動啟用了篩選,自動用了 套用表格格式
XLWorkbook wb = new XLWorkbook(); wb.Worksheets.Add(data); wb.SaveAs("Export.xlsx"); }

 


 

在很多系統中都用到導出,使用過多種導出方式,覺得ClosedXML插件的導出簡單又方便。

並且ClosedXML、DocumentFormat.OpenXml都是MIT開源。

首先通過 Nuget 安裝 ClosedXML 插件,同時會自動安裝 DocumentFormat.OpenXml 插件。

以下就是導出相關的代碼:

1、MVC控制器中的導出

// MVC 控制器中
/// <summary>
/// 導出 (無論是否需要返回值,都有 Response)
/// </summary>
public void Export1() // 傳入搜索條件
{
    // 1、根據條件查詢到想要的數據
    DataTable data = new DataTable();
    // 2、設置表名(對應sheet名)、列名(對應列名)
    data.TableName = "XX統計";
    // 3、列寬設置(拖動Excel列寬度時,會顯示{寬度:10.00 (75像素)|寬度:20.00 (145像素)}推算寬度1為7像素,每列自加5像素;默認大小的漢字,一個字寬約15.3px)
    int[] colsWidth = new int[] { 160, 200, 300, 160 };
    // 4、生成導出文件
    byte[] filedata = ExportHelper.ExportExcel(data);
    // 5、輸出文件流
    HttpContext.Output(filedata, "XX統計_導出" + DateTime.Today.ShowDate() + ".xlsx");
}

2、生成導出文件

方法一:遍歷表格,逐個單元格設置。

using ClosedXML.Excel;
using System.Data;
using System.IO;

/// <summary>
/// 導出Excel文件
/// </summary>
/// <param name="data">導出的數據</param>
/// <param name="colsWidth">列寬</param>
/// <returns></returns>
public static byte[] ExportExcel(DataTable data, int[] colsWidth = null)
{
    using (XLWorkbook workbook = new XLWorkbook())
    {
        IXLWorksheet worksheet = workbook.AddWorksheet(data.TableName);

        // 處理列
        for (int i = 0; i < data.Columns.Count; i++)
        {
            worksheet.Cell(1, i + 1).Value = data.Columns[i].ColumnName;
            worksheet.Cell(1, i + 1).Style.Font.Bold = true;
        }

        // 處理列寬
        if (colsWidth != null)
        {
            for (int j = 1; j <= colsWidth.Length; j++)
            {
                worksheet.Columns(j, j).Width = colsWidth[j - 1];
            }
        }

        // 處理數據
        int r = 2;// 第二行開始
        foreach (DataRow dr in data.Rows)
        {
            // 第一列開始
            for (int c = 1; c <= data.Columns.Count; c++)
            {
worksheet.Cell(r, c).SetValue<string>(dr[c-1].ToString()); // worksheet.Cell(r, c).Value = dr[c-1].ToString(); // 存在數據類型隱患,自動轉換數字、日期、時間格式的數據,顯示出來的可能不是想要的
// worksheet.Cell(r, c).DataType = XLDataType.Text; // 1、先設置格式,賦值后,會自動根據數據重新改變格式,2、先賦值,后設置格式,存在日期變為數字情況;
} r++; } // 緩存到內存流,然后返回 using (MemoryStream stream = new MemoryStream()) { workbook.SaveAs(stream); return stream.ToArray(); } } }

方案二:整個DataTable直接導出(但格式調整是個問題,我還沒找到怎么改,如果有知道的請留言)

public static byte[] ExportExcel(DataTable data, int[] colsWidth = null)
{
    using (XLWorkbook workbook = new XLWorkbook())
    {
        // 這種方法導出方便,但 首行自動啟用了篩選,自動用了 套用表格格式
        IXLWorksheet worksheet = workbook.AddWorksheet(data);

        // 處理列寬(拖動Excel列寬度時,會顯示{寬度:10.00 (75像素)|寬度:20.00 (145像素)}推算寬度1為7像素,每列自加5像素)
        if (colsWidth != null)
        {

            for (int j = 1; j <= colsWidth.Length; j++)
            {
                // 默認 8.43=64px
                worksheet.Columns(j, j).Width = colsWidth[j - 1];
            }
        }

        // 這種快捷的導出數據,自動開啟了篩選功能(Excel 數據-->篩選)
        // 首行的篩選功能去除(以下已試過,都對 workbook.AddWorksheet(data); 無效。)
        //worksheet.AutoFilter.Clear();
        //worksheet.AutoFilter.IsEnabled = false;
        //worksheet.RangeUsed().SetAutoFilter(false);
        //worksheet.SetAutoFilter(false);

        // 這種快捷的導出數據,使用了套用表格格式(Excel 開始-->套用表格格式)
        // 更改/取消 套用表格格式(以下已試過,都對 workbook.AddWorksheet(data); 無效。)
        //worksheet.Clear(XLClearOptions.AllFormats);
        //worksheet.RangeUsed().Clear(XLClearOptions.AllFormats);
        //worksheet.Style = XLWorkbook.DefaultStyle;
        //worksheet.RangeUsed().Style = XLWorkbook.DefaultStyle;

        // 用XLColor.NoColor 無顏色無法去除顏色,需要用白色才能覆蓋 套用表格格式 的顏色
        worksheet.RangeUsed().Style.Fill.SetBackgroundColor(XLColor.White);

        // 自定義樣式部分
        worksheet.RangeUsed().Style.Alignment.WrapText = true;
        worksheet.RangeUsed().Style.Alignment.Vertical = XLAlignmentVerticalValues.Top;
        worksheet.RangeUsed().Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
        worksheet.RangeUsed().Style.Border.BottomBorder = XLBorderStyleValues.Thin;
        worksheet.RangeUsed().Style.Border.RightBorder = XLBorderStyleValues.Thin;
                
        //// 設置首航行高(推算高度1為4/3px≈1.333px)
        ////worksheet.Row(1).Height = 15; // 默認15=20px;

        // 根據內容調整所有行的高度(不加這個,自動換行還能撐高行高)
        //worksheet.Rows().AdjustToContents();

        // 緩存到內存流,然后返回
        using (MemoryStream stream = new MemoryStream())
        {
            workbook.SaveAs(stream);
            return stream.ToArray();
        }
    }
}

 

3、輸出文件流

using System.Text;
using System.Text.RegularExpressions;
using System.Web;

/// <summary>
/// 輸出文件流
/// </summary>
/// <param name="httpContext">Http上下文</param>
/// <param name="filedata">文件數據</param>
/// <param name="fileName">文件名(要后綴名)</param>
public static void Output(this HttpContextBase httpContext, byte[] filedata, string fileName)
{
    //文件名稱效驗 文件名不能包含\/:*?<>| 其中\需要兩次轉義
    if (Regex.IsMatch(fileName, @"[\\/:*?<>|]"))
    {
        fileName = Regex.Replace(fileName, @"[\\/:*?<>|]", "_");
    }

    //判斷是否為火狐瀏覽器(下載時,有些瀏覽器中文名稱亂碼,有些瀏覽器中文名正常,但不能編碼,不會自動解碼,如火狐)
    if (httpContext.Request.Browser.Browser != "Firefox")
    {
        fileName = HttpUtility.UrlEncode(fileName, Encoding.UTF8);
    }

    // 沒有定義類型,用二進制流類型的MIME
    string MIME = "application/octet-stream";
    httpContext.Response.Clear();
    httpContext.Response.Buffer = true; //該值指示是否緩沖輸出,並在完成處理整個響應之后將其發送
    httpContext.Response.ContentType = MIME;
    httpContext.Response.ContentEncoding = Encoding.UTF8;
    httpContext.Response.Charset = Encoding.UTF8.HeaderName;
    httpContext.Response.AddHeader("Content-Disposition", "attachment;filename=" + fileName);
    httpContext.Response.AddHeader("Accept-Language", "zh-cn");
    httpContext.Response.AddHeader("Content-Length", filedata.LongLength.ToString());
    httpContext.Response.BinaryWrite(filedata);
    httpContext.Response.Flush();
    httpContext.Response.End();
}

通過ClosedXML導出操作方便。


對以上導出Excel方式一進行行優化,使其支持多行標題和合並標題。

/// <summary>
/// 導出Excel文件
/// <para>
/// 【注意】當使用多行合並表頭時;第一:注意表頭和數據列要對應,避免數據錯位;第二:有多行合並時,對於之后的行,要跳過多行合並占用的列;
/// </para>
/// </summary>
/// <param name="data">導出的數據</param>
/// <param name="colsWidth">列寬</param>
/// <param name="ehrs">支持多行有合並表頭</param>
/// <returns></returns>
public static byte[] ExportExcel2(DataTable data, int[] colsWidth = null, params ExcelHeaderRow[] ehrs)
{
    if (data == null)
    {
        return null;
    }

    using (XLWorkbook workbook = new XLWorkbook())
    {
        // 工作表名稱不能包含以下字符:\:/?*[]
        IXLWorksheet worksheet = workbook.AddWorksheet(data.TableName);
        // 行計數(Excel都是從1開始)
        int rIndex = 1;

        // 處理表頭
        if (ehrs != null)
        {
            foreach (ExcelHeaderRow row in ehrs)
            {
                int cIndex = 1; // 列計數(Excel都是從1開始)
                foreach (ExcelHeaderColumn col in row.cols)
                {
                    if (string.IsNullOrEmpty(col.colname))
                    {
                        // 跳過列
                        cIndex += col.colspan;
                        continue;
                    }
                    worksheet.Cell(rIndex, cIndex).Value = col.colname;
                    worksheet.Cell(rIndex, cIndex).Style.Font.Bold = true;
                    worksheet.Cell(rIndex, cIndex).Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
                    worksheet.Cell(rIndex, cIndex).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
                    worksheet.Cell(rIndex, cIndex).Style.Alignment.WrapText = true;

                    if (col.rowspan > 1 || col.colspan > 1)
                    {
                        worksheet.Range(rIndex, cIndex, rIndex + col.rowspan - 1, cIndex + col.colspan - 1).Merge();
                    }
                    cIndex += col.colspan;
                }
                if (row.RowHeight > 0)
                {
                    worksheet.Row(rIndex).Height = row.RowHeight;
                }
                rIndex++; // 行加1
            }
        }
        else
        {
            // 采用DataTable列名做表頭
            for (int i = 0; i < data.Columns.Count; i++)
            {
                worksheet.Cell(rIndex, i + 1).Value = data.Columns[i].ColumnName;
                worksheet.Cell(rIndex, i + 1).Style.Font.Bold = true;
                worksheet.Cell(rIndex, i + 1).Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
                worksheet.Cell(rIndex, i + 1).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
                worksheet.Cell(rIndex, i + 1).Style.Alignment.WrapText = true;
            }
            // 設置首航行高(推算高度1為4/3px≈1.333px)
            //worksheet.Row(1).Height = 15; // 默認15=20px;
            rIndex++; // 行加1
        }

        // 處理列寬(拖動Excel列寬度時,會顯示{寬度:10.00 (75像素)|寬度:20.00 (145像素)}推算寬度1為7像素,每列自加5像素)
        if (colsWidth != null)
        {
            for (int j = 1; j <= colsWidth.Length; j++)
            {
                // 默認 8.43=64px
                worksheet.Columns(j, j).Width = colsWidth[j - 1];
            }
        }

        // 處理數據
        foreach (DataRow dr in data.Rows)
        {
            // 第一列開始
            for (int cIndex = 1; cIndex <= data.Columns.Count; cIndex++)
            {
                // 明確指定值類型,避免格式問題
                worksheet.Cell(rIndex, cIndex).SetValue(dr[cIndex - 1].ToString());
                worksheet.Cell(rIndex, cIndex).Style.Alignment.Vertical = XLAlignmentVerticalValues.Top;
                worksheet.Cell(rIndex, cIndex).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
                worksheet.Cell(rIndex, cIndex).Style.Alignment.WrapText = true;
            }
            rIndex++;
        }
        // 小數格式(未測試)
        //worksheet.Cell(r, c).Style.NumberFormat.Format = "$ #,##0.00";
        // 日期格式(未測試)
        //worksheet.Cell(r, c).Style.DateFormat.Format = "yyyy/MM/dd";

        // 根據內容調整所有行的高度(不加這個,自動換行還能撐高行高)
        //worksheet.Rows().AdjustToContents();
        // 緩存到內存流,然后返回
        using (MemoryStream stream = new MemoryStream())
        {
            workbook.SaveAs(stream);
            return stream.ToArray();
        }
    }
}

支持以上方法需要的支撐類:

/// <summary>
/// Excel Header Row
/// </summary>
public class ExcelHeaderRow
{
    /// <summary>
    /// 行高 (默認0自動;推算高度1為4/3px≈1.333px;默認15=20px;)
    /// </summary>
    public int RowHeight { get; set; }
    public List<ExcelHeaderColumn> cols { get; set; }
}

/// <summary>
/// Excel Header Column
/// </summary>
public class ExcelHeaderColumn
{
    /// <summary>
    /// 列名(為null是表示調過,特別在多行合並表頭的情況,注意第二行調過多少colspan一定不要遺漏)
    /// </summary>
    public string colname { get; set; }
    public int rowspan { get; set; } = 1;
    public int colspan { get; set; } = 1;

    public ExcelHeaderColumn() { }
    public ExcelHeaderColumn(string cname, int rs = 1, int cs = 1)
    {
        colname = cname;
        rowspan = rs;
        colspan = cs;
    }
}

多行多列合並的使用方式:

// 列標題
ExcelHeaderRow[] headers = new ExcelHeaderRow[]
{
    new ExcelHeaderRow // 第一行
    {
        cols = new List<ExcelHeaderColumn>
        {
            new ExcelHeaderColumn("多行1", 2),
            new ExcelHeaderColumn("多行2", 2),
            new ExcelHeaderColumn("多列1", 1,2),
            new ExcelHeaderColumn("多列2", 1,2),
            new ExcelHeaderColumn("多列3", 1,2),
            new ExcelHeaderColumn("多行3", 2),
        }
    },
    new ExcelHeaderRow // 第二行
    {
        cols = new List<ExcelHeaderColumn>
        {
            new ExcelHeaderColumn(null, 2), // 跳過被多行合並占用的列
            new ExcelHeaderColumn("子列1"),
            new ExcelHeaderColumn("子列2"),
            new ExcelHeaderColumn("子列3"),
            new ExcelHeaderColumn("子列4"),
            new ExcelHeaderColumn("子列5"),
            new ExcelHeaderColumn("子列6"),
            // 最后的多行合並可以不管
        }
    }
};

 調用方式:

ExcelHelper.ExportExcel2(dt, colsWidth, headers);

 標題效果如下:

 


免責聲明!

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



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