結合bootstrap fileinput插件和Bootstrap-table表格插件,實現文件上傳、預覽、提交的導入Excel數據操作流程

1、bootstrap-fileinpu的簡單介紹

在前面的隨筆,我介紹了Bootstrap-table表格插件的具體項目應用過程,本篇隨筆介紹另外一個Bootstrap FieInput插件的使用,整合兩者可以實現我們常規的Web數據導入操作,導入數據操作過程包括有上傳文件,預覽數據,選擇并提交記錄等一系列操作。
關于這個插件,我在早期隨筆《Bootstrap文件上傳插件File Input的使用》也做了一次介紹,這是一個增強的 HTML5 文件輸入控件,是一個 Bootstrap 3.x 的擴展,實現文件上傳預覽,多文件上傳等功能。
bootstrap-fileinput源碼:https://github.com/kartik-v/bootstrap-fileinput
bootstrap-fileinput在線API:http://plugins.krajee.com/file-input
bootstrap-fileinput Demo展示:http://plugins.krajee.com/file-basic-usage-demo
這個插件主要是介紹如何處理圖片上傳的處理操作,原先我的Excel導入操作使用的是Uploadify插件,可以參考我隨筆《附件上傳組件uploadify的使用》,不過這個需要Flash控件支持,在某些瀏覽器(如Chrome)就比較麻煩了,因此決定使用一種較為通用的上傳插件,這次首先對基于Bootstrap前端架構的框架系統進行升級,替代原來的Uploadify插件,這樣頁面上傳功能,在各個瀏覽器就可以無差異的實現了。
一般情況下,我們需要引入下面兩個文件,插件才能正常使用:

bootstrap-fileinput/css/fileinput.min.css
bootstrap-fileinput/js/fileinput.min.js

在File input 插件使用的時候,如果是基于Asp.NET MVC的,那么我們可以使用BundleConfig.cs進行添加對應的引用,加入到Bundles集合引用即可。

//添加對bootstrap-fileinput控件的支持
css_metronic.Include("~/Content/MyPlugins/bootstrap-fileinput/css/fileinput.min.css");
js_metronic.Include("~/Content/MyPlugins/bootstrap-fileinput/js/fileinput.min.js");
js_metronic.Include("~/Content/MyPlugins/bootstrap-fileinput/js/locales/zh.js");

在頁面中,我們使用以下HTML代碼實現界面展示,主要的bootstrap fileinput插件聲明,主要是基礎的界面代碼

<input id="excelFile" type="file"> 

Excel導入的的界面展示如下所示。



選擇指定文件后,我們可以看到Excel的文件列表,如下界面所示。



上傳文件后,數據直接展示在彈出層的列表里面,這里直接使用了 Bootstrap-table表格插件進行展示。

這樣我們就可以把Excel的記錄展示出來,實現了預覽的功能,勾選必要的記錄,然后保存即可提交到服務器進行保存,實現了Excel數據的真正導入數據庫處理。

2、Excel導出操作詳細介紹

我們在實際導入Excel的界面中,HTML代碼如下所示。

<!--導入數據操作層-->
<div id="import" class="modal fade bs-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-lg">
        <div class="modal-content">
            <div class="modal-header bg-primary">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true"></button>
                <h4 class="modal-title">文件導入</h4>
            </div>
            <div class="modal-body">
                <div style="text-align:right;padding:5px">
                    <a href="~/Content/Template/TestUser-模板.xls" onclick="javascript:Preview();">
                        ![](~/Content/images/ico_excel.png)
                        <span style="font-size:larger;font-weight:200;color:red">TestUser-模板.xls</span>
                    </a>
                </div>
                <hr/>
                <form id="ffImport" method="post">
                    <div title="Excel導入操作" style="padding: 5px">
                        <input type="hidden" id="AttachGUID" name="AttachGUID" /> 
                        <input id="excelFile" type="file"> 
                    </div>
                </form>

                <!--數據顯示表格-->
                <table id="gridImport" class="table table-striped table-bordered table-hover" cellpadding="0" cellspacing="0" border="0">
                </table>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">關閉</button>
                <button type="button" class="btn btn-primary" onclick="SaveImport()">保存</button>
            </div>
        </div>
    </div>
</div>

對于bootstrap fileinput的各種屬性,我們這里使用JS進行初始化,這樣方便統一管理和修改。

//初始化Excel導入的文件
function InitExcelFile() {
    //記錄GUID
    $("#AttachGUID").val(newGuid());

    $("#excelFile").fileinput({
        uploadUrl: "/FileUpload/Upload",//上傳的地址
        uploadAsync: true,              //異步上傳
        language: "zh",                 //設置語言
        showCaption: true,              //是否顯示標題
        showUpload: true,               //是否顯示上傳按鈕
        showRemove: true,               //是否顯示移除按鈕
        showPreview : true,             //是否顯示預覽按鈕
        browseClass: "btn btn-primary", //按鈕樣式 
        dropZoneEnabled: false,         //是否顯示拖拽區域
        allowedFileExtensions: ["xls", "xlsx"], //接收的文件后綴
        maxFileCount: 1,                        //最大上傳文件數限制
        previewFileIcon: '<i class="glyphicon glyphicon-file"></i>',
        allowedPreviewTypes: null,
        previewFileIconSettings: {
            'docx': '<i class="glyphicon glyphicon-file"></i>',
            'xlsx': '<i class="glyphicon glyphicon-file"></i>',
            'pptx': '<i class="glyphicon glyphicon-file"></i>',
            'jpg': '<i class="glyphicon glyphicon-picture"></i>',
            'pdf': '<i class="glyphicon glyphicon-file"></i>',
            'zip': '<i class="glyphicon glyphicon-file"></i>',
        },
        uploadExtraData: {  //上傳的時候,增加的附加參數
            folder: '數據導入文件', guid: $("#AttachGUID").val()
        }
    })  //文件上傳完成后的事件
   .on('fileuploaded', function (event, data, previewId, index) {
        var form = data.form, files = data.files, extra = data.extra,
            response = data.response, reader = data.reader;

        var res = data.response; //返回結果
        if (res.Success) {
            showTips('上傳成功');
            var guid = $("#AttachGUID").val();

            //提示用戶Excel格式是否正常,如果正常加載數據
            $.ajax({
                url: '/TestUser/CheckExcelColumns?guid=' + guid,
                type: 'get',
                dataType: 'json',
                success: function (data) {
                    if (data.Success) {
                        InitImport(guid); //重新刷新表格數據
                        showToast("文件已上傳,數據加載完畢!");

                        //重新刷新GUID,以及清空文件,方便下一次處理
                        RefreshExcel();
                    }
                    else {
                        showToast("上傳的Excel文件檢查不通過。請根據頁面右上角的Excel模板格式進行數據錄入。", "error");
                    }
                }
            });
        }
        else {
            showTips('上傳失敗');
        }
   });
}

上面的邏輯具體就是,設置上傳文件的后臺頁面為:/FileUpload/Upload,以及各種插件的配置參數,uploadExtraData里面設置的是提交的附加參數,也就是后臺控制器接收的參數,其中

.on('fileuploaded', function (event, data, previewId, index) {

的函數處理文件上傳后的處理函數,如果上傳文件返回的結果是成功的,那么我們再次調用ajax來檢查這個Excel的字段是否符合要求,如下地址:

url: '/TestUser/CheckExcelColumns?guid=' + guid,

如果這個檢查的后臺返回成功的記錄,那么再次需要把Excel記錄提取出來預覽,并清空bootstrap fileinput文件上傳插件,方便下次上傳文件。如下代碼所示。

    if (data.Success) {
        InitImport(guid); //重新刷新表格數據
        showToast("文件已上傳,數據加載完畢!");

        //重新刷新GUID,以及清空文件,方便下一次處理
        RefreshExcel();
    }
    else {
        showToast("上傳的Excel文件檢查不通過。請根據頁面右上角的Excel模板格式進行數據錄入。", "error");
    }

其中RefreshExcel就是重新更新上傳的附加參數值,方便下次上傳,否則附加參數的值一直不變化,就會導致我們設置的GUID沒有變化而出現問題。

//重新更新GUID的值,并清空文件
function RefreshExcel() {
    $("#AttachGUID").val(newGuid());
    $('#excelFile').fileinput('clear');//清空所有文件
    
    //附加參數初始化后一直不會變化,如果需要發生變化,則需要使用refresh進行更新
    $('#excelFile').fileinput('refresh', {
        uploadExtraData: { folder: '數據導入文件', guid: $("#AttachGUID").val() },
    });
}

而其中InitImport就是獲取預覽數據并展示在Bootstrap-table表格插件上的,關于這個插件的詳細使用,可以回顧下隨筆《基于Metronic的Bootstrap開發框架經驗總結(16)-- 使用插件bootstrap-table實現表格記錄的查詢、分頁、排序等處理》進行了解即可。

//根據條件查詢并綁定結果
var $import;
function InitImport(guid) {
    var url = "/TestUser/GetExcelData?guid=" + guid;
    $import = $('#gridImport').bootstrapTable({
        url: url,                           //請求后臺的URL(*)
        method: 'GET',                      //請求方式(*)
        striped: true,                      //是否顯示行間隔色
        cache: false,                       //是否使用緩存,默認為true,所以一般情況下需要設置一下這個屬性(*)
        pagination: false,                  //是否顯示分頁(*)
        sidePagination: "server",           //分頁方式:client客戶端分頁,server服務端分頁(*)
        pageNumber: 1,                      //初始化加載第一頁,默認第一頁,并記錄
        pageSize: 100,                     //每頁的記錄行數(*)
        pageList: [10, 25, 50, 100],        //可供選擇的每頁的行數(*)
        search: false,                      //是否顯示表格搜索
        strictSearch: true,
        showColumns: true,                  //是否顯示所有的列(選擇顯示的列)
        showRefresh: true,                  //是否顯示刷新按鈕
        minimumCountColumns: 2,             //最少允許的列數
        clickToSelect: true,               //是否啟用點擊選中行
        uniqueId: "ID",                     //每一行的唯一標識,一般為主鍵列
        queryParams: function (params) { },
        columns: [{
            checkbox: true,
            visible: true                  //是否顯示復選框  
        }, {
            field: 'Name',
            title: '姓名'
        }, {
            field: 'Mobile',
            title: '手機'
        }, {
            field: 'Email',
            title: '郵箱',
            formatter: emailFormatter
        }, {
            field: 'Homepage',
            title: '主頁',
            formatter: linkFormatter
        }, {
            field: 'Hobby',
            title: '興趣愛好'
        }, {
            field: 'Gender',
            title: '性別',
            formatter: sexFormatter
        }, {
            field: 'Age',
            title: '年齡'
        }, {
            field: 'BirthDate',
            title: '出生日期',
            formatter: dateFormatter
        }, {
            field: 'Height',
            title: '身高'
        }, {
            field: 'Note',
            title: '備注'
        }],
        onLoadSuccess: function () {
        },
        onLoadError: function () {
            showTips("數據加載失敗!");
        },
    });
}

最后就是確認提交后,會通過JS提交數據到后臺進行處理,如下代碼所示。

//保存導入的數據
function SaveImport() {
    
    var list = [];//構造集合對象
    var rows = $import.bootstrapTable('getSelections');
    for (var i = 0; i < rows.length; i++) {
        list.push({
            'Name': rows[i].Name, 'Mobile': rows[i].Mobile, 'Email': rows[i].Email, 'Homepage': rows[i].Homepage,
            'Hobby': rows[i].Hobby, 'Gender': rows[i].Gender, 'Age': rows[i].Age, 'BirthDate': rows[i].BirthDate,
            'Height': rows[i].Height, 'Note': rows[i].Note
        });
    }

    if (list.length == 0) {
        showToast("請選擇一條記錄", "warning");
        return;
    }

    var postData = { 'list': list };//可以增加其他參數,如{ 'list': list, 'Rucanghao': $("#Rucanghao").val() };
    postData = JSON.stringify(postData);

    $.ajax({
        url: '/TestUser/SaveExcelData',
        type: 'post',
        dataType: 'json',
        contentType: 'application/json;charset=utf-8',
        traditional: true,
        success: function (data) {
            if (data.Success) {
                //保存成功  1.關閉彈出層,2.清空記錄顯示 3.刷新主列表
                showToast("保存成功");

                $("#import").modal("hide");
                $(bodyTag).html("");
                Refresh();
            }
            else {
                showToast("保存失敗:" + data.ErrorMessage, "error");
            }
        },
        data: postData
    });
}

3、后臺控制器代碼分析

這里我們的JS代碼里面,涉及了幾個MVC后臺的方法處理:Upload、CheckExcelColumns、GetExcelData、SaveExcelData。這里分別進行介紹。

文件上傳的后臺控制器方法如下所示。

/// <summary>
/// 上傳附件到服務器上
/// </summary>
/// <param name="fileData">附件信息</param>
/// <param name="guid">附件組GUID</param>
/// <param name="folder">指定的上傳目錄</param>
/// <returns></returns>
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Upload(string guid, string folder)
{
    CommonResult result = new CommonResult();

    HttpFileCollectionBase files = HttpContext.Request.Files;
    if (files != null)
    {
        foreach (string key in files.Keys)
        {
            try
            {
                #region MyRegion
                HttpPostedFileBase fileData = files[key];
                if (fileData != null)
                {
                    HttpContext.Request.ContentEncoding = Encoding.GetEncoding("UTF-8");
                    HttpContext.Response.ContentEncoding = Encoding.GetEncoding("UTF-8");
                    HttpContext.Response.Charset = "UTF-8";

                    // 文件上傳后的保存路徑
                    string filePath = Server.MapPath("~/UploadFiles/");
                    DirectoryUtil.AssertDirExist(filePath);

                    string fileName = Path.GetFileName(fileData.FileName);      //原始文件名稱
                    string fileExtension = Path.GetExtension(fileName);         //文件擴展名
                    //string saveName = Guid.NewGuid().ToString() + fileExtension; //保存文件名稱

                    FileUploadInfo info = new FileUploadInfo();
                    info.FileData = ReadFileBytes(fileData);
                    if (info.FileData != null)
                    {
                        info.FileSize = info.FileData.Length;
                    }
                    info.Category = folder;
                    info.FileName = fileName;
                    info.FileExtend = fileExtension;
                    info.AttachmentGUID = guid;

                    info.AddTime = DateTime.Now;
                    info.Editor = CurrentUser.Name;//登錄人

                    result = BLLFactory<FileUpload>.Instance.Upload(info);
                    if (!result.Success)
                    {
                        LogTextHelper.Error("上傳文件失敗:" + result.ErrorMessage);
                    }
                } 
                #endregion
            }
            catch (Exception ex)
            {
                result.ErrorMessage = ex.Message;
                LogTextHelper.Error(ex);
            }
        }
    }
    else
    {
        result.ErrorMessage = "fileData對象為空";
    }

    return ToJsonContent(result);
}

文件上傳處理后,返回一個通用的CommonResult 的結果對象,也方便我們在JS客戶端進行判斷處理。

而其中檢查我們導入Excel的數據是否滿足列要求的處理,就是判斷它的數據列和我們預先設置好的列名是否一致即可。

//導入或導出的字段列表   
string columnString = "姓名,手機,郵箱,主頁,興趣愛好,性別,年齡,出生日期,身高,備注";

/// <summary>
/// 檢查Excel文件的字段是否包含了必須的字段
/// </summary>
/// <param name="guid">附件的GUID</param>
/// <returns></returns>
public ActionResult CheckExcelColumns(string guid)
{
    CommonResult result = new CommonResult();

    try
    {
        DataTable dt = ConvertExcelFileToTable(guid);
        if (dt != null)
        {
            //檢查列表是否包含必須的字段
            result.Success = DataTableHelper.ContainAllColumns(dt, columnString);
        }
    }
    catch (Exception ex)
    {
        LogTextHelper.Error(ex);
        result.ErrorMessage = ex.Message;
    }

    return ToJsonContent(result);
}

而GetExcelData則是格式化Excel數據到具體的List<TestUserInfo>集合里面,這樣我們方便在客戶端進行各種屬性的操作,它的代碼如下所示。

/// <summary>
/// 獲取服務器上的Excel文件,并把它轉換為實體列表返回給客戶端
/// </summary>
/// <param name="guid">附件的GUID</param>
/// <returns></returns>
public ActionResult GetExcelData(string guid)
{
    if (string.IsNullOrEmpty(guid))
    {
        return null;
    }

    List<TestUserInfo> list = new List<TestUserInfo>();

    DataTable table = ConvertExcelFileToTable(guid);
    if (table != null)
    {
        #region 數據轉換
        int i = 1;
        foreach (DataRow dr in table.Rows)
        {
            bool converted = false;
            DateTime dtDefault = Convert.ToDateTime("1900-01-01");
            DateTime dt;
            TestUserInfo info = new TestUserInfo();

            info.Name = dr["姓名"].ToString();
            info.Mobile = dr["手機"].ToString();
            info.Email = dr["郵箱"].ToString();
            info.Homepage = dr["主頁"].ToString();
            info.Hobby = dr["興趣愛好"].ToString();
            info.Gender = dr["性別"].ToString();
            info.Age = dr["年齡"].ToString().ToInt32();
            converted = DateTime.TryParse(dr["出生日期"].ToString(), out dt);
            if (converted && dt > dtDefault)
            {
                info.BirthDate = dt;
            }
            info.Height = dr["身高"].ToString().ToDecimal();
            info.Note = dr["備注"].ToString();

            info.Creator = CurrentUser.ID.ToString();
            info.CreateTime = DateTime.Now;
            info.Editor = CurrentUser.ID.ToString();
            info.EditTime = DateTime.Now;

            list.Add(info);
        }
        #endregion
    }

    var result = new { total = list.Count, rows = list };
    return ToJsonContent(result);
}

另一個SaveExcelData的函數就是處理數據導入的最終處理函數,主要就是把集合寫入到具體的數據庫里面即可,具體代碼如下所示。

/// <summary>
/// 保存客戶端上傳的相關數據列表
/// </summary>
/// <param name="list">數據列表</param>
/// <returns></returns>
public ActionResult SaveExcelData(List<TestUserInfo> list)
{
    CommonResult result = new CommonResult();
    if (list != null && list.Count > 0)
    {
        #region 采用事務進行數據提交

        DbTransaction trans = BLLFactory<TestUser>.Instance.CreateTransaction();
        if (trans != null)
        {
            try
            {
                //int seq = 1;
                foreach (TestUserInfo detail in list)
                {
                    //detail.Seq = seq++;//增加1
                    detail.CreateTime = DateTime.Now;
                    detail.Creator = CurrentUser.ID.ToString();
                    detail.Editor = CurrentUser.ID.ToString();
                    detail.EditTime = DateTime.Now;

                    BLLFactory<TestUser>.Instance.Insert(detail, trans);
                }
                trans.Commit();
                result.Success = true;
            }
            catch (Exception ex)
            {
                LogTextHelper.Error(ex);
                result.ErrorMessage = ex.Message;
                trans.Rollback();
            }
        }
        #endregion
    }
    else
    {
        result.ErrorMessage = "導入信息不能為空";
    }

    return ToJsonContent(result);
}

上面這幾個函數的代碼一般是比較有規律的,不需要一個個去編寫,一般通過代碼生成工具Database2Sharp批量生成即可。這樣可以有效提高Web的界面代碼和后臺代碼的開發效率,減少出錯的機會。

整個導入Excel數據的處理過程,所有代碼都貼出來了,基本上整個邏輯了解了就可以很好的了解這個過程的代碼了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容