jQuery File Upload 单页面多实例的实现
Posted Meadows of Heaven
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jQuery File Upload 单页面多实例的实现相关的知识,希望对你有一定的参考价值。
jQuery File Upload 的 GitHub 地址:https://github.com/blueimp/jQuery-File-Upload
插件描述:jQuery File Upload 是一个 jQuery 图片上传组件,支持多文件上传、取消、删除,上传前缩略图预览、列表显示图片大小,支持上传进度条显示。插件基于开放的标准,如 html5 和 javascript ,不需要额外的浏览器插件(例如使用Adobe 的 Flash ),在旧版浏览器中使用 XMLHttpRequest 上传文件。(参见:http://www.jq22.com/jquery-info230)。
需求:在一个页面中包含多个插件实例。例如在生鲜电商网站后台的食谱添加/编辑页面,需要上传/编辑一道菜的食材和配料,就需要在页面中同时包含两个上传实例。插件的文档中有关于单页面多实例的介绍:Multiple File Upload Widgets on the same page,里面说明很简单,把 demo 中 index.html 的 form 表单的 id 改成 class,再简单修改 js/main.js 就可以了。
在很天真的试过之后,发现根本不是这么回事,index.html 中文件域的 name 是写死的,根本无法满足单页面多实例的需求,只能对插件进行修改...而且在改完之后发现修改的工作量很大。
代码是在 demo 的基础上,后端使用 php,删除了不需要的文件,比如其他后端语言处理程序。没有做数据验证和入库等处理,展示页面直接遍历文件夹读取文件。
最终效果图:
初始界面
上传图片及刷新页面
文件结构及主要修改文件
js
server
/index.html
<!DOCTYPE HTML> <!-- /* * jQuery File Upload Plugin Demo 9.1.0 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan * https://blueimp.net * * Licensed under the MIT license: * http://www.opensource.org/licenses/MIT */ --> <html lang="en"> <head> <!-- Force latest IE rendering engine or ChromeFrame if installed --> <!--[if IE]> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <![endif]--> <meta charset="utf-8"> <title>jQuery File Upload Demo</title> <meta name="description" content="File Upload widget with multiple file selection, drag&drop support, progress bars, validation and preview images, audio and video for jQuery. Supports cross-domain, chunked and resumable file uploads and client-side image resizing. Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads."> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Bootstrap styles --> <link rel="stylesheet" href="css/bootstrap.min.css"> <!-- Generic page styles --> <link rel="stylesheet" href="css/style.css"> <!-- blueimp Gallery styles --> <link rel="stylesheet" href="css/blueimp-gallery.min.css"> <!-- CSS to style the file input field as button and adjust the Bootstrap progress bars --> <link rel="stylesheet" href="css/jquery.fileupload.css"> <!-- <link rel="stylesheet" href="css/jquery.fileupload-ui.css"> --> <!-- CSS adjustments for browsers with JavaScript disabled --> <noscript><link rel="stylesheet" href="css/jquery.fileupload-noscript.css"></noscript> <noscript><link rel="stylesheet" href="css/jquery.fileupload-ui-noscript.css"></noscript> </head> <body> <div class="container"> <!-- The file upload form used as target for the file upload widget --> <form class="fileupload" action="server/php" method="POST" enctype="multipart/form-data"> <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload --> <div class="row fileupload-buttonbar"> <div class="col-lg-7"> <!-- The fileinput-button span is used to style the file input field as button --> <span class="btn btn-primary">食材</span> <span class="btn btn-success fileinput-button"> <i class="glyphicon glyphicon-plus"></i> <span>Add files...</span> <input type="file" name="food[]" multiple> </span> <button type="submit" class="btn btn-primary start"> <i class="glyphicon glyphicon-upload"></i> <span>Start upload</span> </button> <button type="reset" class="btn btn-warning cancel"> <i class="glyphicon glyphicon-ban-circle"></i> <span>Cancel upload</span> </button> <button type="button" class="btn btn-danger delete"> <i class="glyphicon glyphicon-trash"></i> <span>Delete</span> </button> <input type="checkbox" class="toggle"> <!-- The global file processing state --> <span class="fileupload-process"></span> </div> <!-- The global progress state --> <div class="col-lg-5 fileupload-progress fade"> <!-- The global progress bar --> <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100"> <div class="progress-bar progress-bar-success" style="width:0%;"></div> </div> <!-- The extended global progress state --> <div class="progress-extended"> </div> </div> </div> <!-- The table listing the files available for upload/download --> <table role="presentation" class="table table-striped"><tbody class="files"></tbody></table> </form> <!-- Multiple File Upload Widgets on the same page --> <form class="fileupload" action="server/php" method="POST" enctype="multipart/form-data"> <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload --> <div class="row fileupload-buttonbar"> <div class="col-lg-7"> <!-- The fileinput-button span is used to style the file input field as button --> <span class="btn btn-primary">配料</span> <span class="btn btn-success fileinput-button"> <i class="glyphicon glyphicon-plus"></i> <span>Add files...</span> <input type="file" name="batching[]" multiple> </span> <button type="submit" class="btn btn-primary start"> <i class="glyphicon glyphicon-upload"></i> <span>Start upload</span> </button> <button type="reset" class="btn btn-warning cancel"> <i class="glyphicon glyphicon-ban-circle"></i> <span>Cancel upload</span> </button> <button type="button" class="btn btn-danger delete"> <i class="glyphicon glyphicon-trash"></i> <span>Delete</span> </button> <input type="checkbox" class="toggle"> <!-- The global file processing state --> <span class="fileupload-process"></span> </div> <!-- The global progress state --> <div class="col-lg-5 fileupload-progress fade"> <!-- The global progress bar --> <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100"> <div class="progress-bar progress-bar-success" style="width:0%;"></div> </div> <!-- The extended global progress state --> <div class="progress-extended"> </div> </div> </div> <!-- The table listing the files available for upload/download --> <table role="presentation" class="table table-striped"><tbody class="files"></tbody></table> </form> </div> <!-- The blueimp Gallery widget --> <div id="blueimp-gallery" class="blueimp-gallery blueimp-gallery-controls" data-filter=":even"> <div class="slides"></div> <h3 class="title"></h3> <a class="prev">‹</a> <a class="next">›</a> <a class="close">×</a> <a class="play-pause"></a> <ol class="indicator"></ol> </div> <!-- The template to display files available for upload --> <script id="template-upload" type="text/x-tmpl"> {% for (var i=0, file; file=o.files[i]; i++) { %} <tr class="template-upload fade"> <td> <span class="preview"></span> </td> <td> <p class="name">{%=file.name%}</p> <strong class="error text-danger"></strong> </td> <td> <p class="size">Processing...</p> <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="progress-bar progress-bar-success" style="width:0%;"></div></div> </td> <td> {% if (!i && !o.options.autoUpload) { %} <button class="btn btn-primary start" disabled> <i class="glyphicon glyphicon-upload"></i> <span>Start</span> </button> {% } %} {% if (!i) { %} <button class="btn btn-warning cancel"> <i class="glyphicon glyphicon-ban-circle"></i> <span>Cancel</span> </button> {% } %} </td> </tr> {% } %} </script> <!-- The template to display files available for download --> <script id="template-download" type="text/x-tmpl"> {% for (var i=0, file; file=o.files[i]; i++) { %} <tr class="template-download fade"> <td> <span class="preview"> {% if (file.thumbnailUrl) { %} <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" data-gallery><img src="{%=file.thumbnailUrl%}"></a> {% } %} </span> </td> <td> <p class="name"> {% if (file.url) { %} <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?\'data-gallery\':\'\'%}>{%=file.name%}</a> {% } else { %} <span>{%=file.name%}</span> {% } %} </p> {% if (file.error) { %} <div><span class="label label-danger">Error</span> {%=file.error%}</div> {% } %} </td> <td> <span class="size">{%=o.formatFileSize(file.size)%}</span> </td> <td> {% if (file.deleteUrl) { %} <button class="btn btn-danger delete" data-type="{%=file.deleteType%}" data-url="{%=file.deleteUrl%}"{% if (file.deleteWithCredentials) { %} data-xhr-fields=\'{"withCredentials":true}\'{% } %}> <i class="glyphicon glyphicon-trash"></i> <span>Delete</span> </button> <input type="checkbox" name="delete" value="1" class="toggle"> {% } else { %} <button class="btn btn-warning cancel"> <i class="glyphicon glyphicon-ban-circle"></i> <span>Cancel</span> </button> {% } %} </td> </tr> {% } %} </script> <script src="js/jquery/1.10.2/jquery.min.js"></script> <!-- The jQuery UI widget factory, can be omitted if jQuery UI is already included --> <script src="js/vendor/jquery.ui.widget.js"></script> <!-- The Templates plugin is included to render the upload/download listings --> <script src="js/tmpl.min.js"></script> <!-- The Load Image plugin is included for the preview images and image resizing functionality --> <script src="js/load-image.all.min.js"></script> <!-- The Canvas to Blob plugin is included for image resizing functionality --> <script src="js/canvas-to-blob.min.js"></script> <!-- Bootstrap JS is not required, but included for the responsive demo navigation --> <script src="js/bootstrap/3.0.0/js/bootstrap.min.js"></script> <!-- blueimp Gallery script --> <script src="js/jquery.blueimp-gallery.min.js"></script> <!-- The Iframe Transport is required for browsers without support for XHR file uploads --> <script src="js/jquery.iframe-transport.js"></script> <!-- The basic File Upload plugin --> <script src="js/jquery.fileupload.js"></script> <!-- The File Upload processing plugin --> <script src="js/jquery.fileupload-process.js"></script> <!-- The File Upload image preview & resize plugin --> <script src="js/jquery.fileupload-image.js"></script> <!-- The File Upload audio preview plugin --> <script src="js/jquery.fileupload-audio.js"></script> <!-- The File Upload video preview plugin --> <script src="js/jquery.fileupload-video.js"></script> <!-- The File Upload validation plugin --> <script src="js/jquery.fileupload-validate.js"></script> <!-- The File Upload user interface plugin --> <script src="js/jquery.fileupload-ui.js"></script> <!-- The main application script --> <script src="js/main.js"></script> <!-- The XDomainRequest Transport is included for cross-domain file deletion for IE 8 and IE 9 --> <!--[if (gte IE 8)&(lt IE 10)]> <script src="js/cors/jquery.xdr-transport.js"></script> <![endif]--> </body> <script> // $(\'.fileupload\').fileupload({ // // Uncomment the following to send cross-domain cookies: // //xhrFields: {withCredentials: true}, // url: \'server/php/?fields=shirt,sweater\', // }); var fields = \'food,batching\'; $(\'.fileupload\').fileupload({ url: \'server/php/?fields=\' + fields, add: function (e, data) { // 不用点击直接上传 var jqXHR = data.submit() .success(function (result, textStatus, jqXHR) {}) .error(function (jqXHR, textStatus, errorThrown) {}) .complete(function (result, textStatus, jqXHR) {}); } }); $.ajax({ url: \'server/php/?fields=\' + fields, success(data) { var dataObj = JSON.parse(data); $.each(dataObj, function(name, value) { $.each(value, function(k, v){ var item = \'<tr class="template-download fade in">\'; item += \'<td><span class="preview"><a data-gallery="" download="\' + v.name + \'" title="\' + v.name + \'" href="\' + v.url + \'"><img src="\' + v.thumbnailUrl + \'"></a></span></td><td><p class="name"><a data-gallery="" download="\' + v.name + \'" title="\' + v.url + \'" href="\' + v.url + \'">\' + v.name + \'</a></p></td><td><span class="size">\' + (v.size / 1000) + \' KB</span></td><td>\'; item += \'<button data-url="\' + v.deleteUrl + \'" data-type="\' + v.deleteType + \'" class="btn btn-danger delete" ><i class="glyphicon glyphicon-trash"></i><span>Delete</span></button>\'; item += \' <input type="checkbox" class="toggle" value="1" name="delete"></td></tr>\'; var $item = $(item); $input_file = eval($("input[type=\'file\'][name=\'" + name + "[]\']")); $item.appendTo($input_file.parents(\'.fileupload-buttonbar\').siblings(\'.table-striped\').children(\'.files\')); }); }); } }); </script> </html>
说明:
通过 fields 来配置文件中不同的文件域,多个文件域的 name 用逗号 , 隔开。这是修改后 demo 中唯一需要根据页面文件域 name 的不同要做配置地方
var fields = \'food,batching\';
/js/jquery.fileupload-ui.js
修改 $.widget(\'blueimp.fileupload\', $.blueimp.fileupload, {}) 中 的 getFilesFromResponse
getFilesFromResponse: function (data) { var paramName = JSON.stringify(data.paramName); if(paramName) { var files = paramName.slice(2,-4); } // console.log(files,eval("data.result."+files)); if (data.result && $.isArray(eval("data.result."+files))) { return eval("data.result."+files); } return []; },
把写死的 files 改成页面中实际的 name
完整文件:
/* * jQuery File Upload User Interface Plugin 9.6.0 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan * https://blueimp.net * * Licensed under the MIT license: * http://www.opensource.org/licenses/MIT */ /* jshint nomen:false */ /* global define, window */ (function (factory) { \'use strict\'; if (typeof define === \'function\' && define.amd) { // Register as an anonymous AMD module: define([ \'jquery\', \'tmpl\', \'./jquery.fileupload-image\', \'./jquery.fileupload-audio\', \'./jquery.fileupload-video\', \'./jquery.fileupload-validate\' ], factory); } else { // Browser globals: factory( window.jQuery, window.tmpl ); } }(function ($, tmpl) { \'use strict\'; $.blueimp.fileupload.prototype._specialOptions.push( \'filesContainer\', \'uploadTemplateId\', \'downloadTemplateId\' ); // The UI version extends the file upload widget // and adds complete user interface interaction: $.widget(\'blueimp.fileupload\', $.blueimp.fileupload, { options: { // By default, files added to the widget are uploaded as soon // as the user clicks on the start buttons. To enable automatic // uploads, set the following option to true: autoUpload: false, // The ID of the upload template: uploadTemplateId: \'template-upload\', // The ID of the download template: downloadTemplateId: \'template-download\', // The container for the list of files. If undefined, it is set to // an element with class "files" inside of the widget element: filesContainer: undefined, // By default, files are appended to the files container. // Set the following option to true, to prepend files instead: prependFiles: false, // The expected data type of the upload response, sets the dataType // option of the $.ajax upload requests: dataType: \'json\', // Error and info messages: messages: { unknownError: \'Unknown error\' }, // Function returning the current number of files, // used by the maxNumberOfFiles validation: getNumberOfFiles: function () { return this.filesContainer.children() .not(\'.processing\').length; }, // Callback to retrieve the list of files from the server response: getFilesFromResponse: function (data) { var paramName = JSON.stringify(data.paramName); if(paramName) { var files = paramName.slice(2,-4); } // console.log(files,eval("data.result."+files)); if (data.result && $.isArray(eval("data.result."+files))) { return eval("data.result."+files); } return []; }, // The add callback is invoked as soon as files are added to the fileupload // widget (via file input selection, drag & drop or add API call). // See the basic file upload widget for more information: add: function (e, data) { if (e.isDefaultPrevented()) { return false; } var $this = $(this), that = $this.data(\'blueimp-fileupload\') || $this.data(\'fileupload\'), options = that.options; data.context = that._renderUpload(data.files) .data(\'data\', data) .addClass(\'processing\'); options.filesContainer[ options.prependFiles ? \'prepend\' : \'append\' ](data.context); that._forceReflow(data.context); that._transition(data.context); data.process(function () { return $this.fileupload(\'process\', data); }).always(function () { data.context.each(function (index) { $(this).find(\'.size\').text( that._formatFileSize(data.files[index].size) ); }).removeClass(\'processing\'); that._renderPreviews(data); }).done(function () { data.context.find(\'.start\').prop(\'disabled\', false); if ((that._trigger(\'added\', e, data) !== false) && (options.autoUpload || data.autoUpload) && data.autoUpload !== false) { data.submit(); } }).fail(function () { if (data.files.error) { data.context.each(function (index) { var error = data.files[index].error; if (error) { $(this).find(\'.error\').text(error); } }); } }); }, // Callback for the start of each file upload request: send: function (e, data) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data(\'blueimp-fileupload\') || $(this).data(\'fileupload\'); if (data.context && data.dataType && data.dataType.substr(0, 6) === \'iframe\') { // Iframe Transport does not support progress events. // In lack of an indeterminate progress bar, we set // the progress to 100%, showing the full animated bar: data.context .find(\'.progress\').addClass( !$.support.transition && \'progress-animated\' ) .attr(\'aria-valuenow\', 100) .children().first().css( \'width\', \'100%\' ); } return that._trigger(\'sent\', e, data); }, // Callback for successful uploads: done: function (e, data) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data(\'blueimp-fileupload\') || $(this).data(\'fileupload\'), getFilesFromResponse = data.getFilesFromResponse || that.options.getFilesFromResponse, files = getFilesFromResponse(data), template, deferred; if (data.context) { data.context.each(function (index) { var file = files[index] || {error: \'Empty file upload result\'}; deferred = that._addFinishedDeferreds(); that._transition($(this)).done( function () { var node = $(this); template = that._renderDownload([file]) .replaceAll(node); that._forceReflow(template); that._transition(template).done( function () { data.context = $(this); that._trigger(\'completed\', e, data); that._trigger(\'finished\', e, data); deferred.resolve(); } ); } ); }); } else { template = that._renderDownload(files)[ that.options.prependFiles ? \'prependTo\' : \'appendTo\' ](that.options.filesContainer); that._forceReflow(template); deferred = that._addFinishedDeferreds(); that._transition(template).done( function () { data.context = $(this); that._trigger(\'completed\', e, data); that._trigger(\'finished\', e, data); deferred.resolve(); } ); } }, // Callback for failed (abort or error) uploads: fail: function (e, data) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data(\'blueimp-fileupload\') || $(this).data(\'fileupload\'), template, deferred; if (data.context) { data.context.each(function (index) { if (data.errorThrown !== \'abort\') { var file = data.files[index]; file.error = file.error || data.errorThrown || data.i18n(\'unknownError\'); deferred = that._addFinishedDeferreds(); that._transition($(this)).done( function () { var node = $(this); template = that._renderDownload([file]) .replaceAll(node); that._forceReflow(template); that._transition(template).done( function () { data.context = $(this); that._trigger(\'failed\', e, data); that._trigger(\'finished\', e, data); deferred.resolve(); } ); } ); } else { deferred = that._addFinishedDeferreds(); that._transition($(this)).done( function () { $(this).remove(); that._trigger(\'failed\', e, data); that._trigger(\'finished\', e, data); deferred.resolve(); } ); } }); } else if (data.errorThrown !== \'abort\') { data.context = that._renderUpload(data.files)[ that.options.prependFiles ? \'prependTo\' : \'appendTo\' ](that.options.filesContainer) .data(\'data\', data); that._forceReflow(data.context); deferred = that._addFinishedDeferreds(); that._transition(data.context).done( function () { data.context = $(this); that._trigger(\'failed\', e, data); that._trigger(\'finished\', e, data); deferred.resolve(); } ); } else { that._trigger(\'failed\', e, data); that._trigger(\'finished\', e, data); that._addFinishedDeferreds().resolve(); } }, // Callback for upload progress events: progress: function (e, data) { if (e.isDefaultPrevented()) { return false; } var progress = Math.floor(data.loaded / data.total * 100); if (data.context) { data.context.each(function () { $(this).find(\'.progress\') .attr(\'aria-valuenow\', progress) .children().first().css( \'width\', progress + \'%\' ); }); } }, // Callback for global upload progress events: progressall: function (e, data) { if (e.isDefaultPrevented()) { return false; } var $this = $(this), progress = Math.floor(data.loaded / data.total * 100), globalProgressNode = $this.find(\'.fileupload-progress\'), extendedProgressNode = globalProgressNode .find(\'.progress-extended\'); if (extendedProgressNode.length) { extendedProgressNode.html( ($this.data(\'blueimp-fileupload\') || $this.data(\'fileupload\')) ._renderExtendedProgress(data) ); } globalProgressNode .find(\'.progress\') .attr(\'aria-valuenow\', progress) .children().first().css( \'width\', progress + \'%\' ); }, // Callback for uploads start, equivalent to the global ajaxStart event: start: function (e) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data(\'blueimp-fileupload\') || $(this).data(\'fileupload\'); that._resetFinishedDeferreds(); that._transition($(this).find(\'.fileupload-progress\')).done( function () { that._trigger(\'started\', e); } ); }, // Callback for uploads stop, equivalent to the global ajaxStop event: stop: function (e) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data(\'blueimp-fileupload\') || $(this).data(\'fileupload\'), deferred = that._addFinishedDeferreds(); $.when.apply($, that._getFinishedDeferreds()) .done(function () { that._trigger(\'stopped\', e); }); that._transition($(this).find(\'.fileupload-progress\')).done( function () { $(this).find(\'.progress\') .attr(\'aria-valuenow\', \'0\') .children().first().css(\'width\', \'0%\'); $(this).find(\'.progress-extended\').html(\' \'); deferred.resolve(); } ); }, processstart: function (e) { if (e.isDefaultPrevented()) { return false; } $(this).addClass(\'fileupload-processing\'); }, processstop: function (e) { if (e.isDefaultPrevented()) { return false; } $(this).removeClass(\'fileupload-processing\'); }, // Callback for file deletion: destroy: function (e, data) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data(\'blueimp-fileupload\') || $(this).data(\'fileupload\'), removeNode = function () { that._transition(data.context).done( function () { $(this).remove(); that._trigger(\'destroyed\', e, data); } ); }; if (data.url) { data.dataType = data.dataType || that.options.dataType; $.ajax(data).done(removeNode).fail(function () { that._trigger(\'destroyfailed\', e, data); }); } else { removeNode(); } } }, _resetFinishedDeferreds: function () { this._finishedUploads = []; }, _addFinishedDeferreds: function (deferred) { if (!deferred) { deferred = $.Deferred(); } this._finishedUploads.push(deferred); return deferred; }, _getFinishedDeferreds: function () { return this._finishedUploads; }, // Link handler, that allows to download files // by drag & drop of the links to the desktop: _enableDragToDesktop: function () { var link = $(this), url = link.prop(\'href\'), name = link.prop(\'download\'), type = \'application/octet-stream\'; link.bind(\'dragstart\', function (e) { try { e.originalEvent.dataTransfer.setData( \'DownloadURL\', [type, name, url].join(\':\') ); } catch (ignore) {} }); }, _formatFileSize: function (bytes) { if (typeof bytes !== \'number\') { return \'\'; } if (bytes >= 1000000000) { return (bytes / 1000000000).toFixed(2) + \' GB\'; } if (bytes >= 1000000) { return (bytes / 1000000).toFixed(2) + \' MB\'; } return (bytes / 1000).toFixed(2) + \' KB\'; }, _formatBitrate: function (bits) { if (typeof bits !== \'number\') { return \'\'; } if (bits >= 1000000000) { return (bits / 1000000000).toFixed(2) + \' Gbit/s\'; } if (bits >= 1000000) { return (bits / 1000000).toFixed(2) + \' Mbit/s\'; } if (bits >= 1000) { return (bits / 1000).toFixed(2) + \' kbit/s\'; } return bits.toFixed(2) + \' bit/s\'; }, _formatTime: function (seconds) { var date = new Date(seconds * 1000), days = Math.floor(seconds / 86400); days = days ? days + \'d \' : \'\'; return days + (\'0\' + date.getUTCHours()).slice(-2) + \':\' + (\'0\' + date.getUTCMinutes()).slice(-2) + \':\' + (\'0\' + date.getUTCSeconds()).slice(-2); }, _formatPercentage: function (floatValue) { return (floatValue * 100).toFixed(2) + \' %\'; }, _renderExtendedProgress: function (data) { return this._formatBitrate(data.bitrate) + \' | \' + this._formatTime( (data.total - data.loaded) * 8 / data.bitrate ) + \' | \' + this._formatPercentage( data.loaded / data.total ) + \' | \' + this._formatFileSize(data.loaded) + \' / \' + this._formatFileSize(data.total); }, _renderTemplate: function (func, files) { if (!func) { return $(); } var result = func({ files: files, formatFileSize: this._formatFileSize, options: this.options }); if (result instanceof $) { return result; } return $(this.options.templatesContainer).html(result).children(); }, _renderPreviews: function (data) { data.context.find(\'.preview\').each(function (index, elm) { $(elm).append(data.files[index].preview); }); }, _renderUpload: function (files) { return this._renderTemplate( this.options.uploadTemplate, files ); }, _renderDownload: function (files) { return this._renderTemplate( this.options.downloadTemplate, files ).find(\'a[download]\').each(this._enableDragToDesktop).end(); }, _startHandler: function (e) { e.preventDefault(); var button = $(e.currentTarget), template = button.closest(\'.template-upload\'), data = template.data(\'data\'); button.prop(\'disabled\', true); if (data && data.submit) { data.submit(); } }, _cancelHandler: function (e) { e.preventDefault(); var template = $(e.currentTarget) .closest(\'.template-upload,.template-download\'), data = template.data(\'data\') || {}; data.context = data.context || template; if (data.abort) { data.abort(); } else { data.errorThrown = \'abort\'; this._trigger(\'fail\', e, data); } }, _deleteHandler: function (e) { e.preventDefault(); var button = $(e.currentTarget); this._trigger(\'destroy\', e, $.extend({ context: button.closest(\'.template-download\'), type: \'DELETE\' }, button.data())); }, _forceReflow: function (node) { return $.support.transition && node.length && node[0].offsetWidth; }, _transition: function (node) { var dfd = $.Deferred(); if ($.support.transition && node.hasClass(\'fade\') && node.is(\':visible\')) { node.bind( $.support.transition.end, function (e) { // Make sure we don\'t respond to other transitions events // in the container element, e.g. from button elements: if (e.target === node[0]) { node.unbind($.support.transition.end); dfd.resolveWith(node); } } ).toggleClass(\'in\'); } else { node.toggleClass(\'in\'); dfd.resolveWith(node); } return dfd; }, _initButtonBarEventHandlers: function () { var fileUploadButtonBar = this.element.find(\'.fileupload-buttonbar\'), filesList = this.options.filesContainer; this._on(fileUploadButtonBar.find(\'.start\'), { click: function (e) { e.preventDefault(); filesList.find(\'.start\').click(); } }); this._on(fileUploadButtonBar.find(\'.cancel\'), { click: function (e) { e.preventDefault(); filesList.find(\'.cancel\').click(); } }); this._on(fileUploadButtonBar.find(\'.delete\'), { click: function (e) { e.preventDefault(); filesList.find(\'.toggle:checked\') .closest(\'.template-download\') .find(\'.delete\').click(); fileUploadButtonBar.find(\'.toggle\') .prop(\'checked\', false); } }); this._on(fileUploadButtonBar.find(\'.toggle\'), { change: function (e) { filesList.find(\'.toggle\').prop( \'checked\', $(e.currentTarget).is(\':checked\') ); } }); }, _destroyButtonBarEventHandlers: function () { this._off( this.element.find(\'.fileupload-buttonbar\') .find(\'.start, .cancel, .delete\'), \'click\' ); this._off( this.element.find(\'.fileupload-buttonbar .toggle\'), \'change.\' ); }, _initEventHandlers: function () { this._super(); this._on(this.options.filesContainer, { \'click .start\': this._startHandler, \'click .cancel\': this._cancelHandler, \'click .delete\': this._deleteHandler }); this._initButtonBarEventHandlers(); }, _destroyEventHandlers: function () { this._destroyButtonBarEventHandlers(); this._off(this.options.filesContainer, \'click\'); this._super(); }, _enableFileInputButton: function () { this.element.find(\'.fileinput-button input\') .prop(\'disabled\', false) .parent().removeClass(\'disabled\'); }, _disableFileInputButton: function () { this.element.find(\'.fileinput-button input\') .prop(\'disabled\', true) .parent().addClass(\'disabled\'); }, _initTemplates: function () { var options = this.options; options.templatesContainer = this.document[0].createElement( options.filesContainer.prop(\'nodeName\') ); if (tmpl) { if (options.uploadTemplateId) { options.uploadTemplate = tmpl(options.uploadTemplateId); } if (options.downloadTemplateId) { options.downloadTemplate = tmpl(options.downloadTemplateId); } } }, _initFilesContainer: function () { var options = this.options; if (options.filesContainer === undefined) { options.filesContainer = this.element.find(\'.files\'); } else if (!(options.filesContainer instanceof $)) { options.filesContainer = $(options.filesContainer); } }, _initSpecialOptions: function () { this._super(); this._initFilesContainer(); this._initTemplates(); }, _create: function () { this._super(); this._resetFinishedDeferreds(); if (!$.support.fileInput) { this._disableFileInputButton(); } }, enable: function () { var wasDisabled = false; if (this.options.disabled) { wasDisabled = true; } this._super(); if (wasDisabled) { this.element.find(\'input, button\').prop(\'disabled\', false); this._enableFileInputButton(); } }, disable: function () { if (!this.options.disabled) { this.element.find(\'input, button\').prop(\'disabled\', true); this._disableFileInputButton(); } this._super(); } }); }));
./js/main.js
注释以下代码
$(\'.fileupload\').fileupload({ // Uncomment the following to send cross-domain cookies: //xhrFields: {withCredentials: true}, url: \'server/php/\' });
移至 ./index.html 中
./server/php/index.php
<?php /* * jQuery File Upload Plugin PHP Example 5.14 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan * https://blueimp.net * * Licensed under the MIT license: * http://www.opensource.org/licenses/MIT */ error_reporting(E_ALL | E_STRICT); require(\'UploadHandler.php\'); // 添加图片 if(! empty($_FILES)) { $keys = array_keys($_FILES); if(! empty($keys)) { $key = $keys[0]; } else { $key = null; } } else { // 删除图片时的参数 $pathinfo = pathinfo($_SERVER[\'REQUEST_URI\']); if(! empty($pathinfo[\'filename\'])) { $dirname = preg_match(\'/^\\?(.*)=.*$/\', $pathinfo[\'filename\'], $match); if(! empty($match[1]) && $match[1] != \'fields\') { $key = $match[1]; } else { $key = null; } } else { $key = null; } } $upload_handler = new UploadHandler($key);
根据不同的添加/删除的不同情况,传递不同的参数,同时在页面加载时通过 ajax 获取不同文件夹的文件用于展示(并没有做多层目录或者文件存储目录与文件域的 name 不同的情况的考虑,同时实际项目的 url 模式也有可能与程序中的正则表达式不匹配,可在实际项目中根据实际情况修改)
./server/php/UploadHandler.php 上传类
修改了 $this->options 中关于上传路径的参数,原 demo 中是写死的
function __construct($dir = null, $options = null, $initialize = true, $error_messages = null) { if($dir === null) { $dir = \'files\'; } $this->options = array( \'script_url\' => $this->get_full_url().\'/\', \'upload_dir\' => dirname($this->get_server_var(\'SCRIPT_FILENAME\')).\'/\'.$dir.\'/\', \'upload_url\' => $this->get_full_url().\'/\'.$dir.\'/\', \'user_dirs\' => false, \'mkdir_mode\' => 0755, \'param_name\' => $dir, ..........
修改了 get_singular_param_name 方法
protected function get_singular_param_name() { // return substr($this->options[\'param_name\'], 0, -1); return $this->options[\'param_name\']; }
修改了 get 方法
public function get($print_response = true) { if ($print_response && isset($_GET[\'download\'])) { return $this->download(); } if(isset($_GET[\'fields\']) && $_GET[\'fields\'] != \'\') { $fields = $_GET[\'fields\']; if(strpos($fields, \',\') === false) { // 只有一个实例 $this->options[\'upload_dir\'] = dirname($this->get_server_var(\'SCRIPT_FILENAME\')).\'/\'.$fields.\'/\'; $this->options[\'upload_url\'] = $this->get_full_url().\'/\'.$fields.\'/\'; $this->options[\'param_name\'] = $fields; $response = array( $val => $this->get_file_objects() ); $return = $this->generate_response($response, $print_response); } else { // 多个实例 $fields = explode(\',\', $fields); $return = array(); foreach($fields as $key => $val) { $this->options[\'upload_dir\'] = dirname($this->get_server_var(\'SCRIPT_FILENAME\')).\'/\'.$val.\'/\'; $this->options[\'upload_url\'] = $this->get_full_url().\'/\'.$val.\'/\'; $this->options[\'param_name\'] = $val; $response = $this->get_file_objects(); if(! empty($response)) { $return[$val] = $response; } } } echo json_encode($return); } }
get 方法主要用于刷新页面后展示之前的上传列表,这个只是最简单的实现,实际项目中应该是通过数据库查找
完整文件:
<?php /* * jQuery File Upload Plugin PHP Class 8.1.0 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan * https://blueimp.net * * Licensed under the MIT license: * http://www.opensource.org/licenses/MIT */ class UploadHandler { protected $options; // PHP File Upload error message codes: // http://php.net/manual/en/features.file-upload.errors.php protected $error_messages = array( 1 => \'The uploaded file exceeds the upload_max_filesize directive in php.ini\', 2 => \'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form\', 3 => \'The uploaded file was only partially uploaded\', 4 => \'No file was uploaded\', 6 => \'Missing a temporary folder\', 7 => \'Failed to write file to disk\', 8 => \'A PHP extension stopped the file upload\', \'post_max_size\' => \'The uploaded file exceeds the post_max_size directive in php.ini\', \'max_file_size\' => \'File is too big\', \'min_file_size\' => \'File is too small\', \'accept_file_types\' => \'Filetype not allowed\', \'max_number_of_files\' => \'Maximum number of files exceeded\', \'max_width\' => \'Image exceeds maximum width\', \'min_width\' => \'Image requires a minimum width\', \'max_height\' => \'Image exceeds maximum height\', \'min_height\' => \'Image requires a minimum height\', \'abort\' => \'File upload aborted\', \'image_resize\' => \'Failed to resize image\' ); protected $image_objects = array(); function __construct($dir = null, $options = null, $initialize = true, $error_messages = null) { if($dir === null) { $dir = \'files\'; } $this->options = array( \'script_url\' => $this->get_full_url().\'/\', \'upload_dir\' => dirname($this->get_server_var(\'SCRIPT_FILENAME\')).\'/\'.$dir.\'/\', \'upload_url\' => $this->get_full_url().\'/\'.$dir.\'/\', \'user_dirs\' => false, \'mkdir_mode\' => 0755, \'param_name\' => $dir, // Set the following option to \'POST\', if your server does not support // DELETE requests. This is a parameter sent to the client: \'delete_type\' => \'DELETE\', \'access_control_allow_origin\' => \'*\', \'access_control_allow_credentials\' => false, \'access_control_allow_methods\' => array( \'OPTIONS\', \'HEAD\', \'GET\', \'POST\', \'PUT\', \'PATCH\', \'DELETE\' ), \'access_control_allow_headers\' => array( \'Content-Type\', \'Content-Range\', \'Content-Disposition\' ), // Enable to provide file downloads via GET requests to the PHP script: // 1. Set to 1 to download files via readfile method through PHP // 2. Set to 2 to send a X-Sendfile header for lighttpd/Apache // 3. Set to 3 to send a X-Accel-Redirect header for nginx // If set to 2 or 3, adjust the upload_url option to the base path of // the redirect parameter, e.g. \'/files/\'. \'download_via_php\' => false, // Read files in chunks to avoid memory limits when download_via_php // is enabled, set to 0 to disable chunked reading of files: \'readfile_chunk_size\' => 10 * 1024 * 1024, // 10 MiB // Defines which files can be displayed inline when downloaded: \'inline_file_types\' => \'/\\.(gif|jpe?g|png)$/i\', // Defines which files (based on their names) are accepted for upload: \'accept_file_types\' => \'/.+$/i\', // The php.ini settings upload_max_filesize and post_max_size // take precedence over the following max_file_size setting: \'max_file_size\' => null, \'min_file_size\' => 1, // The maximum number of files for the upload directory: \'max_number_of_files\' => null, // Defines which files are handled as image files: \'image_file_types\' => \'/\\.(gif|jpe?g|png)$/i\', // Use exif_imagetype on all files to correct file extensions: \'correct_image_extensions\' => false, // Image resolution restrictions: \'max_width\' => null, \'max_height\' => null, \'min_width\' => 1, \'min_height\' => 1, // Set the following option to false to enable resumable uploads: \'discard_aborted_uploads\' => true, // Set to 0 to use the GD library to scale and orient images, // set to 1 to use imagick (if installed, falls back to GD), // set to 2 to use the ImageMagick convert binary directly: \'image_library\' => 1, // Uncomment the following to define an array of resource limits // for imagick: /* \'imagick_resource_limits\' => array( imagick::RESOURCETYPE_MAP => 32, imagick::RESOURCETYPE_MEMORY => 32 ), */ // Command or path for to the ImageMagick convert binary: \'convert_bin\' => \'convert\', // Uncomment the following to add parameters in front of each // ImageMagick convert call (the limit constraints seem only // to have an effect if put in front): /* \'convert_params\' => \'-limit memory 32MiB -limit map 32MiB\', */ // Command or path for to the ImageMagick identify binary: \'identify_bin\' => \'identify\', \'image_versions\' => array( // The empty image version key defines options for the original image: \'\' => array( // Automatically rotate images based on EXIF meta data: \'auto_orient\' => true ), // Uncomment the following to create medium sized images: /* \'medium\' => array( \'max_width\' => 800, \'max_height\' => 600 ), */ \'thumbnail\' => array( // Uncomment the following to use a defined directory for the thumbnails // instead of a subdirectory based on the version identifier. // Make sure that this directory doesn\'t allow execution of files if you // don\'t pose any restrictions on the type of uploaded files, e.g. by // copying the .htaccess file from the files directory for Apache: //\'upload_dir\' => dirname($this->get_server_var(\'SCRIPT_FILENAME\')).\'/thumb/\', //\'upload_url\' => $this->get_full_url().\'/thumb/\', // Uncomment the following to force the max // dimensions and e.g. create square thumbnails: //\'crop\' => true, \'max_width\' => 80, \'max_height\' => 80 ) ) ); if ($options) { $this->options = $options + $this->options; } if ($error_messages) { $this->error_messages = $error_messages + $this->error_messages; } if ($initialize) { $this->initialize(); } } protected function initialize() { switch ($this->get_server_var(\'REQUEST_METHOD\')) { case \'OPTIONS\': case \'HEAD\': $this->head(); break; case \'GET\': $this->get(); break; case \'PATCH\': case \'PUT\': case \'POST\': $this->post(); break; case \'DELETE\': $this->delete(); break; default: $this->header(\'HTTP/1.1 405 Method Not Allowed\'); } } protected function get_full_url() { $https = !empty($_SERVER[\'HTTPS\']) && strcasecmp($_SERVER[\'HTTPS\'], \'on\') === 0 || !empty($_SERVER[\'HTTP_X_FORWARDED_PROTO\']) && strcasecmp($_SERVER[\'HTTP_X_FORWARDED_PROTO\'], \'https\') === 0; return ($https ? \'https://\' : \'http://\'). (!empty($_SERVER[\'REMOTE_USER\']) ? $_SERVER[\'REMOTE_USER\'].\'@\' : \'\'). (isset($_SERVER[\'HTTP_HOST\']) ? $_SERVER[\'HTTP_HOST\'] : ($_SERVER[\'SERVER_NAME\']. ($https && $_SERVER[\'SERVER_PORT\'] === 443 || $_SERVER[\'SERVER_PORT\'] === 80 ? \'\' : \':\'.$_SERVER[\'SERVER_PORT\']))). substr($_SERVER[\'SCRIPT_NAME\'],0, strrpos($_SERVER[\'SCRIPT_NAME\'], \'/\')); } protected function get_user_id() { @session_start(); return session_id(); } protected function get_user_path() { if ($this->options[\'user_dirs\']) { return $this->get_user_id().\'/\'; } return \'\'; } protected function get_upload_path($file_name = null, $version = null) { $file_name = $file_name ? $file_name : \'\'; if (empty($version)) { $version_path = \'\'; } else { $version_dir = @$this->options[\'image_versions\'][$version][\'upload_dir\']; if ($version_dir) { return $version_dir.$this->get_user_path().$file_name; } $version_path = $version.\'/\'; } return $this->options[\'upload_dir\'].$this->get_user_path() .$version_path.$file_name; } protected function get_query_separator($url) { return strpos($url, \'?\') === false ? \'?\' : \'&\'; } protected function get_download_url($file_name, $version = null, $direct = false) { if (!$direct && $this->options[\'download_via_php\']) { $url = $this->options[\'script_url\'] .$this->get_query_separator($this->options[\'script_url\']) .$this->get_singular_param_name() .\'=\'.rawurlencode($file_name); if ($version) { $url .= \'&version=\'.rawurlencode($version); } return $url.\'&download=1\'; } if (empty($version)) { $version_path = \'\'; } else { $version_url = @$this->options[\'image_versions\'][$version][\'upload_url\']; if ($version_url) { return $version_url.$this->get_user_path().rawurlencode($file_name); } $version_path = rawurlencode($version).\'/\'; } return $this->options[\'upload_url\'].$this->get_user_path() .$version_path.rawurlencode($file_name); } protected function set_additional_file_properties($file) { $file->deleteUrl = $this->options[\'script_url\'] .$this->get_query_separator($this->options[\'script_url\']) .$this->get_singular_param_name() .\'=\'.rawurlencode($file->name); $file->deleteType = $this->options[\'delete_type\']; if ($file->deleteType !== \'DELETE\') { $file->deleteUrl .= \'&_method=DELETE\'; } if ($this->options[\'access_control_allow_credentials\']) { $file->deleteWithCredentials = true; } } // Fix for overflowing signed 32 bit integers, // works for sizes up to 2^32-1 bytes (4 GiB - 1): protected function fix_integer_overflow($size) { if ($size < 0) { $size += 2.0 * (PHP_INT_MAX + 1); } return $size; } protected function get_file_size($file_path, $clear_stat_cache = false) { if ($clear_stat_cache) { if (version_compare(PHP_VERSION, \'5.3.0\') >= 0) { clearstatcache(true, $file_path); } else { clearstatcache(); } } return $this->fix_integer_overflow(filesize($file_path)); } protected function is_valid_file_object($file_name) { $file_path = $this->get_upload_path($file_name); if (is_file($file_path) && $file_name[0] !== \'.\') { return true; } return false; } protected function get_file_object($file_name) { if ($this->is_valid_file_object($file_name)) { $file = new \\stdClass(); $file->name = $file_name; $file->size = $this->get_file_size(以上是关于jQuery File Upload 单页面多实例的实现的主要内容,如果未能解决你的问题,请参考以下文章
jQuery File Upload 图片上传解决方案兼容IE6+
我用jQuery-File-Upload 做上传的时候,点击按钮的时候,就刷新一下页面,不弹出窗口,怎么做呀?