基于MVC+EasyUI的Web开发框架经验总结(10)--在Web界面上实现数据的导入和导出

数据的导入导出,在很多系统里面都比较常见,这个导入导出的操作,在Winform里面比较容易实现,我曾经在之前的一篇文章《Winform开发框架之通用数据导入导出操作》介绍了在Winform里面的通用导入导出模块的设计和开发过程,但在Web上我们应该如何实现呢?本文主要介绍利用MVC4+EasyUI的特点,并结合文件上传控件Uploadify 的使用,实现文件上传后马上进行处理并显示,然后确认后把数据写入数据库的过程。
我们知道,Web上对Excel的处理和Winform的有所差异,如果是在Web上处理,我们需要把Excel文档上传到服务器上,然后读取文件进行显示,所以第一步是实现文件的上传操作,关于文件上传控件,具体可以参考我的文章《基于MVC4+EasyUI的Web开发框架形成之旅--附件上传组件uploadify的使用》。

1、导入数据的界面效果展示

在Winform里面,我们处理Excel数据导入的界面如下所示。



在Web上的主界面如下所示。



导入界面如下所示。

2、Web数据导入的处理逻辑和代码

为了实现Web上的数据导入导出操作,我们需要增加两个按钮,一个是导入按钮,一个是导出按钮。

 <a href="javascript:void(0)" class="easyui-linkbutton" id="btnImport" iconcls="icon-excel" onclick="ShowImport()">导入</a>
 <a href="javascript:void(0)" class="easyui-linkbutton" id="btnExport" iconcls="icon-excel" onclick="ShowExport()">导出</a>

导入的JS处理代码如下所示。

//显示导入界面
function ShowImport() {
    $.showWindow({
        title: '客户联系人-Excel数据导入',
        useiframe: true,
        width: 1024,
        height: 700,
        content: 'url:/Contact/Import',
        buttons: [{
            text: '取消',
            iconCls: 'icon-cancel',
            handler: function (win) {
                win.close();
            }
        }]
    });
}

上面主要就是弹出一个窗口(上面的导入数据窗口),用来方便客户选择Excel文件并保存数据或者下载导入模板等操作的。

然后在Import.cshtml的视图代码里面,我们需要初始化Datagrid和相关的界面元素,初始化DataGrid的代码如下所示。

//实现对DataGird控件的绑定操作
function InitGrid() {
    var guid = $("#AttachGUID").val();
    $('#grid').datagrid({   //定位到Table标签,Table标签的ID是grid
        url: '/Contact/GetExcelData?guid=' + guid,   //指向后台的Action来获取当前用户的信息的Json格式的数据
        title: '客户联系人-Excel数据导入',
        iconCls: 'icon-view',
        height: 400,
        width: function () { return document.body.clientWidth * 0.9 },//自动宽度

        ..................

上面红色部分的内容,就是我们在文件顺利上传到服务器上的时候,根据一个guid的参数初始化DataGrid的列表数据。

下面是附件上传控件uploadify的初始化脚本代码,其中红色部分注意一下,我们需要上传的是一个文件,并且不允许多选,限定上传文件的类型为xls。

文件上传完成后,首先调用CheckExcelColumns控制器函数来检查是否匹配导入模板的字段,如果匹配通过,加载Excel并展示数据到Datagrid里面,否则提示用户按模板格式录入数据。

<script type="text/javascript">
$(function () {
    //添加界面的附件管理
    $('#file_upload').uploadify({
        'swf': '/Content/JQueryTools/uploadify/uploadify.swf',  //FLash文件路径
        'buttonText': '浏  览',                                 //按钮文本
        'uploader': '/FileUpload/Upload',                       //处理ASHX页面
        'queueID': 'fileQueue',                        //队列的ID
        'queueSizeLimit': 1,                          //队列最多可上传文件数量,默认为999
        'auto': false,                                 //选择文件后是否自动上传,默认为true
        'multi': false,                                 //是否为多选,默认为true
        'removeCompleted': true,                       //是否完成后移除序列,默认为true
        'fileSizeLimit': '10MB',                       //单个文件大小,0为无限制,可接受KB,MB,GB等单位的字符串值
        'fileTypeDesc': 'Excel Files',                 //文件描述
        'fileTypeExts': '*.xls',  //上传的文件后缀过滤器
        'onQueueComplete': function (event, data) {    //所有队列完成后事件
            var guid = $("#AttachGUID").val();
            ViewUpFiles(guid, "div_files");

            //提示用户Excel格式是否正常,如果正常加载数据
            $.ajax({
                url: '/Contact/CheckExcelColumns?guid=' + guid,
                type: 'get',
                dataType:'json',
                success: function (data) {
                    if (data.Success) {                                
                        InitGrid(); //重新刷新表格数据
                        $.messager.alert("提示", "文件已上传,数据加载完毕!");
                    }
                    else {
                        $.messager.alert("提示", "上传的Excel文件检查不通过。请根据页面右上角的Excel模板格式进行数据录入。");
                    }
                }
            });                    
        },
        'onUploadStart': function (file) {
            InitUpFile();//上传文件前 ,重置GUID,每次不同
            $("#file_upload").uploadify("settings", 'formData', { 'folder': '数据导入文件', 'guid': $("#AttachGUID").val() }); //动态传参数
        },
        'onUploadError': function (event, queueId, fileObj, errorObj) {
            //alert(errorObj.type + ":" + errorObj.info);
        }
    });
});

为了有效处理数据的导入,我们需要严格保证导入的数据是和模板的字段是匹配的,否则处理容易出错,也没有任何意义。为了实现这个目的,框架里面提供方法对字段进行检查,主要是确保Excel里面包含了完整的字段即可。

/// <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);
}

而在InitGrid的初始化中的这个GetExcelData的控制器方法如下所示。主要的逻辑就是获取到Excel,并把Excel里面的数据转换为DataTable,最后初始化为实体类列表,并返回给调用页面就可以了。

/// <summary>
/// 获取服务器上的Excel文件,并把它转换为实体列表返回给客户端
/// </summary>
/// <param name="guid">附件的GUID</param>
/// <returns></returns>
public ActionResult GetExcelData(string guid)
{
    if (string.IsNullOrEmpty(guid))
    {
        return null;
    }

    List<ContactInfo> list = new List<ContactInfo>();
    DataTable table = ConvertExcelFileToTable(guid);
    if (table != null)
    {
        #region 数据转换
        int i = 1;
        foreach (DataRow dr in table.Rows)
        {
            string customerName = dr["客户名称"].ToString();
            if (string.IsNullOrEmpty(customerName))
            {
                continue;//客户名称为空,记录跳过
            }

            CustomerInfo customerInfo = BLLFactory<Customer>.Instance.FindByName(customerName);
            if (customerInfo == null)
            {
                continue;//客户名称不存在,记录跳过
            }

            ContactInfo info = new ContactInfo();
            info.Customer_ID = customerInfo.ID;//客户ID
            info.HandNo = dr["编号"].ToString();
            info.Name = dr["姓名"].ToString();
             ..............................//增加一个特殊字段的转义
            info.Data1 = BLLFactory<Customer>.Instance.GetCustomerName(info.Customer_ID);

            list.Add(info);
        }
        #endregion
    }
    var result = new { total = list.Count, rows = list };
    return JsonDate(result);
}

3、Web上数据的导出操作

刚才介绍了数据的导入操作,数据的导出操作相对简单一些,它的JS函数操作如下所示。

//导出Excel数据
var exportCondition;
function ShowExport() {
    var url = "/Contact/Export";
    $.ajax({
        type: "POST",
        url: url,
        data: exportCondition,
        success: function (filePath) {
            var downUrl = '/FileUpload/DownloadFile?file=' + filePath;
            window.location = downUrl;
        }
    });
}

虽然数据的导出比较简单一点,但是由于我们需要使用POST方式对数据条件进行提交,因此不像普通的方式下载文件Window.Open(url)就可以实现文件下载了。如果POST方式提交了参数,那么返回的数据即使是文件流,也无法进行有效的下载。

从上面的脚本我们可以看到,里面的exportCondition就是我们需要提交到服务器的条件,服务器根据这个条件进行检索数据,并返回一个Excel文件就可以了。

由于使用ajax这种POST方式无法直接下载文件流,因此,我们需要先根据条件,在服务器上生成文件,返回一个文件路径,再次通过DownloadFile方法进行文件的下载才可以。

因此这个传递的条件也是很重要的,在查询操作的时候,我们可以把对应的条件传递给它。

//绑定搜索按钮的的点击事件
function BindSearchEvent() {
    //按条件进行查询数据,首先我们得到数据的值
    $("#btnSearch").click(function () {
        //得到用户输入的参数
        //取值有几种方式:$("#id").combobox('getValue'), $("#id").datebox('getValue'), $("#id").val(),combotree('getValue')
        //字段增加WHC_前缀字符,避免传递如URL这样的Request关键字冲突
        var queryData = {
             WHC_Name: $("#txtName").val(),
             WHC_OfficePhone: $("#txtOfficePhone").val(),
             WHC_Mobile: $("#txtMobile").val(),
             WHC_Address: $("#txtAddress").val(),
             WHC_Email: $("#txtEmail").val(),
             WHC_Note: $("#txtNote").val()
          }
        //将值传递给DataGrid
        InitGrid(queryData);

        //传递给导出操作
        exportCondition = queryData;

        return false;
    });
}

在我们选定某个树的节点的时候,我们也可以传递自定义的条件给它。

//根据消息分组加载指定列表
function loadByGroupTree(node) {
    //赋值给特殊字段,公司和部门查询的时候选择其中一个
    var queryParams = $('#grid').datagrid('options').queryParams;
    var condition = "{ id: \"" + node.id +"\", groupname:\"" + node.text +"\", userid:\"" + @Session["UserId"] + "\" }";
    queryParams.CustomedCondition = condition;//提供给datagrid的条件

    exportCondition = { CustomedCondition: condition };//提供给导出的条件

    $("#grid").datagrid("reload");
    $('#grid').datagrid('uncheckAll');
}

后台的Export控制器方法主要的逻辑如下所示。



最终是返回一个生成好的文件地址。
最后给一个方法直接下载文件就可以了。

/// <summary>
/// 根据路径下载文件,主要用于生成的文件的下载
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns></returns>
public ActionResult DownloadFile(string file)
{
    string realPath = Server.MapPath(file);
    string saveFileName = FileUtil.GetFileName(realPath);

    Response.WriteFile(realPath);
    Response.Charset = "GB2312";
    Response.ContentEncoding = Encoding.GetEncoding("GB2312");
    Response.ContentType = "application/ms-excel/msword";
    Response.AppendHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(saveFileName));
    Response.Flush();
    Response.End();

    return new FileStreamResult(Response.OutputStream, "application/ms-excel/msword");
}

导出的Excel界面效果如下所示。



由于篇幅的原因,这个导入导出的操作就介绍到这里,希望有问题大家共同探讨。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容