【无刷新上传】
要实现文件上传,form必须设置几个属性:
1.action:设为要处理数据的页面地址;
2.method:设为"post";
3.enctype/encoding:必须设为"multipart/form-data",这里要注意的是在ie中用js修改form的enctype属性是没有效果的,只能修改encoding;后面两个属性程序初始化时都有设置:
this.Form.method = "post";
this.Form.encoding = "multipart/form-data";要注意这里的无刷新不是ajax哦,而是利用“古老”的iframe。
由于ajax提交数据必须先获取数据,而js(一般情况下)是不能操作客户端文件,要获取文件数据就更不用说了,所以只能用iframe来做。
先说说iframe实现无刷新上传的原理:利用form的target属性,把数据提交到页面中一个(通常为隐藏的)iframe上。直观点说就是把“刷新”留给iframe。
其实原理跟一般用iframe实现无刷新提交表单是一样的,只是这里换成是文件。
这里关键就是把form的target设为iframe的name:this.Form.target = this._FrameName;
【iframe】
如果没有自定义iframe,程序在初始化时会自动创建无刷新所需的iframe的。
首先必须选择一个iframe名,这在无刷新时是必须的,为了每个实例能创建各自的iframe,这里用了一个随机数:this._FrameName = "uploadFrame_" + Math.floor(Math.random() * 1000);也可以用一个递增的计算器来代替随机数。接着创建iframe,本以为用document.createElement("iframe")创建再设置它的name属性就行了。
却发现这样设置的name在ie居然不认(有说name是只读属性),还好在网上找到一个方法:“IE 创建元素,还有一个特点,就是可以连同属性一同创建”。
例如我想给动态创建的iframe设置name,可以这样:document.createElement("<iframe name=\"" + this._FrameName + "\">")不过这个方式在ff会报错:
uncaught exception: String contains an invalid character (NS_ERROR_DOM_INVALID_CHARACTER_ERR)
估计是用createElement时不能带name,标准应该也是这样,所以兼容的方式这样写:var oFrame = isIE ? document.createElement("<iframe name=\"" + this._FrameName + "\">") : document.createElement("iframe");
//为ff设置name
oFrame.name = this._FrameName;
oFrame.style.display = "none";
关于这方面更详细的内容请看这里。创建完还需要插入到body中,一般的做法是使用document.body.appendChild,但在ie中会有“已终止操作”错误,可以用下面这段代码测试:<body>
<div>
<script>
document.body.appendChild(document.createElement("div"));
</script>
</div>
</body>
网上找到一个解析:“原来FF下的实现机制是当页面还没有完全读取完时body元素就已经存在了,而IE只有页面完全读取结束body元素才会存在,所以在页面中插入上面这条语句在IE下就会出现错误”。
我在web开发未解之谜中也提到了这个现象,我这里使用了insertBefore代替:document.body.insertBefore(oFrame, document.body.childNodes[0]);
在服务器端文件传送完(或失败)之后,怎么通知客户端呢?
这里说说我的方法,首先我在客户端定义一个函数:function Finish(msg){ alert(msg); location.href = location.href; }很简单,就是显示提示并重新加载页面(如果使用reload会导致ff中iframe重复加载数据)。
那服务器端如何通知客户端的问题,就是iframe如何跟主页面交互。
答案是通过window.parent或window.top,在iframe中parent和top属性“分别返回立即父窗口和最上层的祖先窗口”。
例如我在服务器端处理完数据之后会输出:
context.Response.Write("<script>window.parent.Finish('" + _msg + "');</script>");
就会执行主页面的Finish函数了。

解决方案 »

  1.   

    【多文件上传】
    对于多文件上传,这里的目的是如何做到163网盘那样,只用一个file控件就实现多文件上传。
    这里参考了163网盘的思路,下面说说如何实现:
    首先必须有一个文件空间(我自己定的名字),例如程序中的"idFile"对象,这个空间不需要内容甚至一个div就可以,主要是用来存放file控件,程序中Folder属性就是这个文件空间对象。
    ps:这里的要求是把file控件都控制在文件空间里,即使不是单file控件的情况。
    再说说Files属性,这个属性放的是file控件集合,方便获取file控件,在下面“文件列表”就会用到。处理这些file控件的程序主要在Ini函数中:
    首先是处理文件空间中的file控件:this.Files = [];
    //整理文件空间,把有值的file放入文件集合
    Each(this.Folder.getElementsByTagName("input"), Bind(this, function(o){
        if(o.type == "file"){ o.value && this.Files.push(o); this.onIniFile(o); }
    }))可以看到这里主要是把file控件放入到Files中,并执行附加函数onIniFile,我是这样定义这个函数的:onIniFile: function(file){ file.value ? file.style.display = "none" : this.Folder.removeChild(file); }这里为了实现单file控件,把原来有值的file都隐藏了,还有那个“单file控件”呢?
    别急,接着就在文件空间插入一个新的file控件:var file = document.createElement("input");
    file.name = this.FileName; file.type = "file"; file.onchange = Bind(this, function(){ this.Check(file); this.Ini(); });
    this.Folder.appendChild(file);可以看到file控件的name是FileName属性的值,默认是空的,如果服务器端需要这个name的话就可以设置。
    这里可以看到每个file控件都有onchange来执行检测函数Check,这样每次选择文件后都会用Check检测一次,这里说说这个Check函数://检测变量
    var bCheck = true;
    //进行空值、文件数、后缀名、同值检测
    if(!file.value){
        bCheck = false; this.onEmpty();
    } else if(this.Limit && this.Files.length >= this.Limit){
        bCheck = false; this.onLimite();
    } else if(!!this.ExtIn.length && !RegExp("\.(" + this.ExtIn.join("|") + ")$", "i").test(file.value)){
        //检测是否允许后缀名
        bCheck = false; this.onNotExtIn();
    } else if(!!this.ExtOut.length && RegExp("\.(" + this.ExtOut.join("|") + ")$", "i").test(file.value)) {
        //检测是否禁止后缀名
        bCheck = false; this.onExtOut();
    } else if(!!this.Distinct) {
        Each(this.Files, function(o){ if(o.value == file.value){ bCheck = false; } })
        if(!bCheck){ this.onSame(); }
    }里面有一个检测变量bCheck,然后进行空值、文件数限制、后缀名、相同文件的检测,当其中一个步骤不通过bCheck就会设为false,一个常用的检测结构。
    这里说说检测后缀名,由于js不能像后台那样获取文件的文件类型,所以只能根据后缀名来判断,例如用正则判断:/\.(jpg|gif)$/i.test(file.value)这样判断显然是不够的,所以如果要做文件类型判断的话一定要在后台用ContentType再判断一次。
    最后如果没有通过检测就会执行onFail函数:
    !bCheck && this.onFail(file);我在onFail函数中设定了移除没有通过检测的file控件:
    onFail: function(file){ this.Folder.removeChild(file); }
    这样就基本实现(正确的说是模拟)了单file控件上传多个文件的效果了。
    【文件列表】
    在上面的Ini函数中,最后执行了一个附加函数onIni,这个函数是用户自己定义的,我就在这个函数中添加文件列表。在之前先说说添加文件列表的函数AddList,这个函数是用来把file控件的值列在一个table里面。
    函数的参数是一个二维数组,其中第一维是行(tr),第二维是列(td)。
    首先获取列表对象FileList,再定义一个文档碎片oFragment来操作dom:var FileList = $("idFileList"), oFragment = document.createDocumentFragment();
    然后用两个Each把二维数组插入到文档碎片中:Each(rows, function(cells){
        var row = document.createElement("tr");
        Each(cells, function(o){
            var cell = document.createElement("td");
            if(typeof o == "string"){ cell.innerHTML = o; }else{ cell.appendChild(o); }
            row.appendChild(cell);
        });
        oFragment.appendChild(row);
    })
    其中用了一个判断if(typeof o == "string"),如果是文本就直接用innerHTML插入td,如果不是文本(这里不是文本就是一个对象)就用appendChild插入到td。
    当数据都插入到文档碎片,就准备把文档碎片插入到FileList中,不过还有一个步骤就是清空FileList中原有的数据。
    本来把innerHTML设为空来清空FileList会更有效率,但ie的table中只有td支持innerHTML,所以只好用removeChild来清空:while(FileList.hasChildNodes()){ FileList.removeChild(FileList.firstChild); }之后就可以把文档碎片插入了:
    FileList.appendChild(oFragment);继续看onIni函数,现在只需要把要显示的数据组成一个二维数组,再用AddList就能显示文件列表了,这时存放file控件集合的Files属性就大有用处了。
    首先定义一个放显示数据的数组:
    var arrRows = [];
    然后根据Files对这个数组赋值:
    if(this.Files.length){
        var oThis = this;
        Each(this.Files, function(o){
            var a = document.createElement("a"); a.innerHTML = "取消"; a.href = "javascript:void(0);";
            a.onclick = function(){ oThis.Delete(o); return false; };
            arrRows.push([o.value, a]);
        });
    } else { arrRows.push(["<font color='gray'>没有添加文件</font>", "&nbsp;"]); }
    AddRow(arrRows);
    当Files没有控件时只是输出“没有添加文件”,有控件时就会把每个file控件的要显示数据放到一个数组中,可以看到这个数组其实就是td内容的集合,接着把这个数组加入到arrRows中形成二维数组,最后把得到的arrRows给AddRow函数显示数据就行了。
    为了能取消指定的file控件,这里插入了一个a来触发删除函数Delete,这里也有一个技巧,这里把href设为"javascript:void(0);",并在onclick中返回false,这样能最大程度的实现仅仅执行js而不去跳转。在表单提交时也要重新显示文件列表,表单提交后就不允许删除文件了,只显示文件路径就行了:$("idBtnupload").onclick = function(){
        //显示文件列表
        var arrRows = [];
        Each(fu.Files, function(o){ arrRows.push([o.value, "&nbsp;"]); });
        AddList(arrRows);
        
        fu.Folder.style.display = "none";
        $("idProcess").style.display = "";
        $("idMsg").innerHTML = "正在添加文件到您的网盘中,请稍候……<br />有可能因为网络问题,出现程序长时间无响应,请点击“<a href='?'><font color='red'>取消</font></a>”重新上传文件";
        
        fu.Form.submit();
    }
    说到表单提交要注意一个问题,就是表单是不能嵌套的,最好是把表单放到服务器表单之外,没有办法才使用服务器表单作为提交表单(由于程序会修改提交表单的属性,所以尽量不要这样使用)。这样文件列表就完成了,有兴趣的话也可以自己封装一下这个功能。
      

  2.   

    【file样式】
    到此,程序的功能都已经实现了,但在163网盘中还有一个特别的地方,就是file控件的样式。
    如果有用过163网盘上传文件,就知道那个file控件就像一个按钮,但功能确实是一个file控件。
    但当自己尝试修改file控件的样式时,发现单单设置file控件的样式并不能实现想要的效果。
    于是我想了另一个办法,用一个button来模拟,结果发现也不行,用js根本操作不了file控件,应该是考虑到安全问题吧。
    最后是参考了163网盘和muxrwc模拟126附件添加的效果,总结了这个方法:
    1.指定用一个容器(例如程序中的idFile)。
    容器最好指定高和宽,并且overflow为hidden,不是块级元素的最好设display为block(为了高和宽的正确呈现);
    2.在容器里放一个file控件,并设置样式,使能触发弹出选择文件框的部分覆盖整个容器,并设置成全透明。
    容器指定准确的高和宽就是为了能通过file控件中不多的能设置的样式来覆盖整个容器;
    3.现在已经把容器模拟成file控件了,可以直接设置容器的样式来模拟设置file控件的样式了。在程序中主要用file控件的margin-left和font-size来实现覆盖整个容器:
    a.files input {
        margin-left:-350px;
        font-size:30px;
        cursor:pointer;
        filter:alpha(opacity=0);
        opacity:0;
    }至于容器,我使用了有伪类hover的a元素(虽然CSS2中hover可以应用于任何对象,但ie6不支持)。
    这里用了一个常用的小技巧,就是用一张图片作为背景通过在hover时修改background-position来实现两张图片的效果:a.files {
        width:90px;
        height:30px;
        overflow:hidden;
        display:block;
        border:1px solid #BEBEBE;
        background:url(img/fu_btn.gif) left top no-repeat;
        text-decoration:none;
    }
    a.files:hover {
        background-color:#FFFFEE;
        background-position:0 -30px;
    }
    在点击这个a时后会出现一个虚线框,在这里显然不太美观,可以把outline设为none来去掉,可是ie又不支持,在网上找到一个方法ie可以把hideFocus设为true来隐藏聚焦(即不显示这个虚线框,hideFocus可以在js或html中设置,也可以通过expression放到css中:a.files, a.files input {
        outline:none;/*ff* /
        hide-focus:expression(this.hideFocus=true);/*ie* /
    }这样完全模拟了163网盘的效果了。
    【后台】前台基本完成了,就到后台啦。后台的功能很简单,就是处理传递过来的文件数据。
    这里像js + .Net 图片切割系统那样使用ashx文件处理IHttpHandler发送过来的数据。
    程序很简单,就直接贴代码了:int iTotal = context.Request.Files.Count;if (iTotal == 0)
    {
        _msg = "没有数据";
    }
    else
    {
        int iCount = 0;    for (int i = 0; i < iTotal; i++)
        {
            HttpPostedFile file = context.Request.Files[i];        if (file.ContentLength > 0 || !string.IsNullOrEmpty(file.FileName))
            {
                //保存文件
                file.SaveAs(System.Web.HttpContext.Current.Server.MapPath("./file/" + Path.GetFileName(file.FileName)));            //这里可以根据实际设置其他限制
                if (++iCount > UploadFileLimit)
                {
                    _msg = "超过上传限制:" + UploadFileLimit;
                    break;
                }
            }
        }
    }
    这里只检测了有无文件和文件数限制,其他检测如文件大小等可以自己扩展,应该不难。
    处理完数据之后就通知客户端:context.Response.Write("<script>window.parent.Finish('" + _msg + "');</script>");
    这个在上面iframe的内容中已经说明了。
    使用说明基本使用很简单,实例化一个file对象,其中参数分别是form对象,文件空间对象:new FileUpload("uploadForm", "idFile")
    这样就实现了一个简单的无刷新上传文件表单。还可以使用这几个属性:
    Form//表单
    Folder//文件控件存放空间
    Files//文件集合更多的功能可以选择设置这些属性:
    属性名:默认值//说明
    FileName:"",//文件上传控件的name,配合后台使用
    FrameName:"",//iframe的name,要自定义iframe的话这里设置name
    onIniFile:function(){},//整理文件时执行(其中参数是file对象)
    onEmpty:function(){},//文件空值时执行
    Limit:0,//文件数限制,0为不限制
    onLimite:function(){},//超过文件数限制时执行
    Distinct:true,//是否不允许相同文件
    onSame:function(){},//有相同文件时执行
    ExtIn:[],//允许后缀名
    onNotExtIn:function(){},//不是允许后缀名时执行
    ExtOut:[],//禁止后缀名,当设置了ExtIn则ExtOut无效
    onExtOut:function(){},//是禁止后缀名时执行
    onFail:function(){},//文件不通过检测时执行(其中参数是file对象)
    onIni:function(){}//重置时执行使用方法可以参考实例。程序中提供了下面几个方法:
    Ini 整理空间
    Check 检测file对象
    Delete 删除指定file
    Clear 删除全部file
      

  3.   

    程序代码
    var isIE = (document.all) ? true : false;var $ = function (id) {
        return "string" == typeof id ? document.getElementById(id) : id;
    };var Class = {
      create: function() {
        return function() {
          this.initialize.apply(this, arguments);
        }
      }
    }var Extend = function(destination, source) {
    for (var property in source) {
    destination[property] = source[property];
    }
    }var Bind = function(object, fun) {
    return function() {
    return fun.apply(object, arguments);
    }
    }var Each = function(list, fun){
    for (var i = 0, len = list.length; i < len; i++) { fun(list[i], i); }
    };//文件上传类
    var FileUpload = Class.create();
    FileUpload.prototype = {
      //表单对象,文件控件存放空间
      initialize: function(form, folder, options) {

    this.Form = $(form);//表单
    this.Folder = $(folder);//文件控件存放空间
    this.Files = [];//文件集合

    this.SetOptions(options);

    this.FileName = this.options.FileName;
    this._FrameName = this.options.FrameName;
    this.Limit = this.options.Limit;
    this.Distinct = !!this.options.Distinct;
    this.ExtIn = this.options.ExtIn;
    this.ExtOut = this.options.ExtOut;

    this.onIniFile = this.options.onIniFile;
    this.onEmpty = this.options.onEmpty;
    this.onNotExtIn = this.options.onNotExtIn;
    this.onExtOut = this.options.onExtOut;
    this.onLimite = this.options.onLimite;
    this.onSame = this.options.onSame;
    this.onFail = this.options.onFail;
    this.onIni = this.options.onIni;

    if(!this._FrameName){
    //为每个实例创建不同的iframe
    this._FrameName = "uploadFrame_" + Math.floor(Math.random() * 1000);
    //ie不能修改iframe的name
    var oFrame = isIE ? document.createElement("<iframe name=\"" + this._FrameName + "\">") : document.createElement("iframe");
    //为ff设置name
    oFrame.name = this._FrameName;
    oFrame.style.display = "none";
    //在ie文档未加载完用appendChild会报错
    document.body.insertBefore(oFrame, document.body.childNodes[0]);
    }

    //设置form属性,关键是target要指向iframe
    this.Form.target = this._FrameName;
    this.Form.method = "post";
    //注意ie的form没有enctype属性,要用encoding
    this.Form.encoding = "multipart/form-data"; //整理一次
    this.Ini();
      },
      //设置默认属性
      SetOptions: function(options) {
        this.options = {//默认值
    FileName: "",//文件上传控件的name,配合后台使用
    FrameName: "",//iframe的name,要自定义iframe的话这里设置name
    onIniFile: function(){},//整理文件时执行(其中参数是file对象)
    onEmpty: function(){},//文件空值时执行
    Limit: 0,//文件数限制,0为不限制
    onLimite: function(){},//超过文件数限制时执行
    Distinct: true,//是否不允许相同文件
    onSame: function(){},//有相同文件时执行
    ExtIn: [],//允许后缀名
    onNotExtIn: function(){},//不是允许后缀名时执行
    ExtOut: [],//禁止后缀名,当设置了ExtIn则ExtOut无效
    onExtOut: function(){},//是禁止后缀名时执行
    onFail: function(){},//文件不通过检测时执行(其中参数是file对象)
    onIni: function(){}//重置时执行
        };
        Extend(this.options, options || {});
      },
      //整理空间
      Ini: function() {
    //整理文件集合
    this.Files = [];
    //整理文件空间,把有值的file放入文件集合
    Each(this.Folder.getElementsByTagName("input"), Bind(this, function(o){
    if(o.type == "file"){ o.value && this.Files.push(o); this.onIniFile(o); }
    }))
    //插入一个新的file
    var file = document.createElement("input");
    file.name = this.FileName; file.type = "file"; file.onchange = Bind(this, function(){ this.Check(file); this.Ini(); });
    this.Folder.appendChild(file);
    //执行附加程序
    this.onIni();
      },
      //检测file对象
      Check: function(file) {
    //检测变量
    var bCheck = true;
    //空值、文件数限制、后缀名、相同文件检测
    if(!file.value){
    bCheck = false; this.onEmpty();
    } else if(this.Limit && this.Files.length >= this.Limit){
    bCheck = false; this.onLimite();
    } else if(!!this.ExtIn.length && !RegExp("\.(" + this.ExtIn.join("|") + ")$", "i").test(file.value)){
    //检测是否允许后缀名
    bCheck = false; this.onNotExtIn();
    } else if(!!this.ExtOut.length && RegExp("\.(" + this.ExtOut.join("|") + ")$", "i").test(file.value)) {
    //检测是否禁止后缀名
    bCheck = false; this.onExtOut();
    } else if(!!this.Distinct) {
    Each(this.Files, function(o){ if(o.value == file.value){ bCheck = false; } })
    if(!bCheck){ this.onSame(); }
    }
    //没有通过检测
    !bCheck && this.onFail(file);
      },
      //删除指定file
      Delete: function(file) {
    //移除指定file
    this.Folder.removeChild(file); this.Ini();
      },
      //删除全部file
      Clear: function() {
    //清空文件空间
    Each(this.Files, Bind(this, function(o){ this.Folder.removeChild(o); })); this.Ini();
      }
    }测试代码
    var fu = new FileUpload("uploadForm", "idFile", { Limit: 3, ExtIn: ["jpg", "gif"],
    onIniFile: function(file){ file.value ? file.style.display = "none" : this.Folder.removeChild(file); },
    onEmpty: function(){ alert("请选择一个文件"); },
    onLimite: function(){ alert("超过上传限制"); },
    onSame: function(){ alert("已经有相同文件"); },
    onNotExtIn: function(){ alert("只允许上传" + this.ExtIn.join(",") + "文件"); },
    onFail: function(file){ this.Folder.removeChild(file); },
    onIni: function(){
    //显示文件列表
    var arrRows = [];
    if(this.Files.length){
    var oThis = this;
    Each(this.Files, function(o){
    var a = document.createElement("a"); a.innerHTML = "取消"; a.href = "javascript:void(0);";
    a.onclick = function(){ oThis.Delete(o); return false; };
    arrRows.push([o.value, a]);
    });
    } else { arrRows.push(["<font color='gray'>没有添加文件</font>", "&nbsp;"]); }
    AddList(arrRows);
    //设置按钮
    $("idBtnupload").disabled = $("idBtndel").disabled = this.Files.length <= 0;
    }
    });$("idBtnupload").onclick = function(){
    //显示文件列表
    var arrRows = [];
    Each(fu.Files, function(o){ arrRows.push([o.value, "&nbsp;"]); });
    AddList(arrRows);

    fu.Folder.style.display = "none";
    $("idProcess").style.display = "";
    $("idMsg").innerHTML = "正在添加文件到您的网盘中,请稍候……<br />有可能因为网络问题,出现程序长时间无响应,请点击“<a href='?'><font color='red'>取消</font></a>”重新上传文件";

    fu.Form.submit();
    }//用来添加文件列表的函数
    function AddList(rows){
    //根据数组来添加列表
    var FileList = $("idFileList"), oFragment = document.createDocumentFragment();
    //用文档碎片保存列表
    Each(rows, function(cells){
    var row = document.createElement("tr");
    Each(cells, function(o){
    var cell = document.createElement("td");
    if(typeof o == "string"){ cell.innerHTML = o; }else{ cell.appendChild(o); }
    row.appendChild(cell);
    });
    oFragment.appendChild(row);
    })
    //ie的table不支持innerHTML所以这样清空table
    while(FileList.hasChildNodes()){ FileList.removeChild(FileList.firstChild); }
    FileList.appendChild(oFragment);
    }
    $("idLimit").innerHTML = fu.Limit;$("idExt").innerHTML = fu.ExtIn.join(",");$("idBtndel").onclick = function(){ fu.Clear(); }//在后台通过window.parent来访问主页面的函数
    function Finish(msg){ alert(msg); location.href = location.href; }
      

  4.   

    哇塞,太帅了,帮你顶~  Ajax
      

  5.   

    学习,不久前在cnblogs看到同样的文章,原来就是你啊:)
      

  6.   

    cloudgamer 久仰了.
    谢谢分享.
      

  7.   

    学习一下, 你的cnblogs空间有问题, 访问不了.
      

  8.   

    新加了asp版本【asp版本补充】由于很多人问我asp版本的后台该如何写,所以决定写一个给大家。
    这里我用了化境HTTP上传程序2.1版(应该是最新版了)的无组件上传类,但用的时候发现几个问题(不知是我不会用还是asp本身的问题):
    1,当file控件的name是空时,后台会找不到文件;
    2,文件名比较短时(例如我用"f"),后台也找不到文件;
    3,当有多个file控件,如果使用相同的name,后台只会保存一个文件;
    4,我在上传文件后输出的中文是乱码(有时又正常)。针对前3条,我加了一个RanName属性,设为true的话会自动生成随机的file控件名,对于第4条,我发现如果字是直接写在文档上就不会乱码,所以我这里把输出的文字都直接写在文档上没有用变量。如果有兄弟知道怎么解决这些问题记得告诉我哦。
      

  9.   

    不错,好的很~~·very very good 
      

  10.   

    感谢楼主分享,昨天看了你得.net源码很激动今天改成了php,0积分供各位下载由于刚上传没有地址只能给个总地址又需要的可以去下载在我的资源里http://download.csdn.net/mengshan1986
      

  11.   


    不过我不懂php
    能放到我的blog里么
      

  12.   

    当然可以不过我没有上传你blog的权限你得自己下载添加了、
      

  13.   

    你把文件发给我吧
    cloudgamer[at]163.com
      

  14.   

    感谢mengshan1986提供的php版:下载完整测试代码(php)
      

  15.   

    有个问题向要请教,看了下您的程序,在上传之前文件是要一个一个手动添加的.有没有方法让程序自动读取并加载到file域里呢? 好像file域不能赋值.仙人有什么好方法么.困惑大半年了