结合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阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容