最近在做一个内部系统,需要一个无刷新的上传功能,找了许久,发现了一个好用的上传工具-Fine Uploader,网上也有不少关于它的介绍,对我有不少的启发,结合我的使用场景简单的介绍一下它与thinkjs完美配合。
首先就是使用thinkjs快速搭建一个web应用,可以参考之前写的一个thinkjs初试。
访问127.0.0.1:8360查看应用是否正常启动,如果一切正常就可以开始创建前端页面和服务端处理上传的逻辑的页面了。
修改/App/Lib/Controller/Home/IndexController.js内容为:
/** * controller * @return */ module.exports = Controller("Home/BaseController", function(){ "use strict"; return { indexAction: function(){ this.display(); }, uploadFileAction: function() { var self = this; var fileInfo = this.file('qqfile'); //http://www.thinkjs.org/doc/http.html#上传的文件 /* //fileInfo的值 { fieldName: 'qqfile', originalFilename: '1.jpg', path: '/home/maoshuai/htdocs/mtyras/App/Runtime/Temp/23886-1c2xozp.jpg', headers: { 'content-disposition': 'form-data; name="qqfile"; filename="1.jpg"', 'content-type': 'image/jpeg' }, ws: { _writableState: { highWaterMark: 16384, objectMode: false, needDrain: false, ending: true, ended: true, finished: true, decodeStrings: true, defaultEncoding: 'utf8', length: 0, writing: false, sync: false, bufferProcessing: false, onwrite: [Function], writecb: null, writelen: 0, buffer: [], errorEmitted: false }, writable: true, domain: null, _events: { error: [Object], close: [Object] }, _maxListeners: 10, path: '/home/maoshuai/htdocs/mtyras/App/Runtime/Temp/23886-1c2xozp.jpg', fd: null, flags: 'w', mode: 438, start: undefined, pos: undefined, bytesWritten: 28618, closed: true }, size: 28618 }*/ if(fileInfo) { self.json({ error: 0, errmsg: 'ok', success: true //只有success返回true才认为上传成功 }); }else { self.error(); } } }; });
修改/App/View/Home/index_index.html内容为:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="http://s7.qhimg.com/!f33a50ea/fine-uploader.css"> <title>Fine Uploader与thinkjs的邂逅</title> </head> <body> <div id="fine-uploader-wrapper"> <div class="qq-uploader-selector qq-uploader"> <div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container"> <div class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div> </div> <div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone> <span>拖拽上传区域</span> </div> <div class="qq-upload-button-selector qq-upload-button"> <div>选择文件</div> </div> <span class="qq-drop-processing-selector qq-drop-processing"> <span>上传进度</span> <span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span> </span> <ul class="qq-upload-list-selector qq-upload-list"> <li> <div class="qq-progress-bar-container-selector"> <div class="qq-progress-bar-selector qq-progress-bar"></div> </div> <span class="qq-upload-spinner-selector qq-upload-spinner"></span> <span class="qq-edit-filename-icon-selector qq-edit-filename-icon"></span> <span class="qq-upload-file-selector qq-upload-file"></span> <input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text"> <span class="qq-upload-size-selector qq-upload-size"></span> <a class="qq-upload-cancel-selector qq-upload-cancel" href="#">取消</a> <a class="qq-upload-retry-selector qq-upload-retry" href="#">重试</a> <a class="qq-upload-delete-selector qq-upload-delete" href="#">删除</a> <span class="qq-upload-status-text-selector qq-upload-status-text"></span> </li> </ul> </div> </div> <button id="upload-btn">上传按钮</button> <script type="text/javascript" src="http://s8.qhimg.com/!240a7702/fine-uploader.js"></script> <script type="text/javascript"> //具体参数参考源码qq.FineUploaderBasic中的_options查看 var uploader = new qq.FineUploader({ element: document.getElementById("fine-uploader-wrapper"), //上传按钮 request: { endpoint: 'test/uploadFile' //上传接口地址 }, multiple: false, //是否多个文件 autoUpload: false, //是否支持上传 validation: { allowedExtensions: ['jpeg', 'jpg', 'png'], //上传文件约束条件 sizeLimit: 2048000 //bytes 2000KB }, callbacks: { onSubmit: function(id, fileName) { //文件开始提交 console.log(fileName,'文件开始提交'); }, onUpload: function(id, fileName) { //文件开始上传 console.log(fileName,'文件开始提交'); }, onProgress: function(id, fileName, loaded, total) { //文件正在上传 console.log(fileName,'已上传'+(loaded/total)*100+'%'); }, onComplete: function(id, fileName, responseJSON) { //文件上传成功 console.log(fileName,'上传成功,返回信息为:',responseJSON); }, onCancel: function(id, fileName) { //取消文件上传 console.log('取消',fileName,'上传'); } }, messages: { noFilesError: '没有选中文件' }, text: { formatProgress: "{percent}% of {total_size}", failUpload: "上传失败", waitingForResponse: "上传中...", paused: "暂停" }, template: 'fine-uploader-wrapper', //ID debug: true }); document.getElementById('upload-btn').onclick = function() { uploader.uploadStoredFiles(); } </script> </body> </html>
下面对服务端代码和前端页面进行详细的说明:
服务端IndexController.js
indexAction对应的页面是index_index.html,uploadFileAction对应的是前端页面的上传图片的接口,其中注释部分是通过thinkjs获取的文件信息,例子是直接返回了,使用的时候可以根据自己的情况,对上传的数据进行条件判断,然后做出相应的处理。这里需要注意的是:Fine Uploader返回的信息中必须包含success字段,并且只有在success=true的时候,才认为是上传成功,才会改变前端页面的展示。而thinkjs的this.success()函数不能传success参数,所以说使用了this.json()来实现。
前端index_index.html
页面没有要说的,主要介绍一下Fine Uploader的使用吧。
它主要两种主要形式,一种是原生JS实现,一种是jquery插件形式,两种的使用方式分别如下:
$(dom).fineUploader(conf); new qq.FineUploader(conf);
接下来说一下conf可以配置什么信息,查看源码可以发现这个默认配置:
this._options = { debug: false, button: null, multiple: true, maxConnections: 3, disableCancelForFormUploads: false, autoUpload: true, request: { endpoint: "/server/upload", params: {}, paramsInBody: true, customHeaders: {}, forceMultipart: true, inputName: "qqfile", uuidName: "qquuid", totalFileSizeName: "qqtotalfilesize", filenameParam: "qqfilename" }, validation: { allowedExtensions: [], sizeLimit: 0, minSizeLimit: 0, itemLimit: 0, stopOnFirstInvalidFile: true, acceptFiles: null, image: { maxHeight: 0, maxWidth: 0, minHeight: 0, minWidth: 0 } }, callbacks: { onSubmit: function(id, name) {}, onSubmitted: function(id, name) {}, onComplete: function(id, name, responseJSON, maybeXhr) {}, onAllComplete: function(successful, failed) {}, onCancel: function(id, name) {}, onUpload: function(id, name) {}, onUploadChunk: function(id, name, chunkData) {}, onUploadChunkSuccess: function(id, chunkData, responseJSON, xhr) {}, onResume: function(id, fileName, chunkData) {}, onProgress: function(id, name, loaded, total) {}, onTotalProgress: function(loaded, total) {}, onError: function(id, name, reason, maybeXhrOrXdr) {}, onAutoRetry: function(id, name, attemptNumber) {}, onManualRetry: function(id, name) {}, onValidateBatch: function(fileOrBlobData) {}, onValidate: function(fileOrBlobData) {}, onSubmitDelete: function(id) {}, onDelete: function(id) {}, onDeleteComplete: function(id, xhrOrXdr, isError) {}, onPasteReceived: function(blob) {}, onStatusChange: function(id, oldStatus, newStatus) {}, onSessionRequestComplete: function(response, success, xhrOrXdr) {} }, messages: { typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.", sizeError: "{file} is too large, maximum file size is {sizeLimit}.", minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.", emptyError: "{file} is empty, please select files again without it.", noFilesError: "No files to upload.", tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.", maxHeightImageError: "Image is too tall.", maxWidthImageError: "Image is too wide.", minHeightImageError: "Image is not tall enough.", minWidthImageError: "Image is not wide enough.", retryFailTooManyItems: "Retry failed - you have reached your file limit.", onLeave: "The files are being uploaded, if you leave now the upload will be canceled.", unsupportedBrowserIos8Safari: "Unrecoverable error - this browser does not permit file uploading of any kind due to serious bugs in iOS8 Safari. Please use iOS8 Chrome until Apple fixes these issues." }, retry: { enableAuto: false, maxAutoAttempts: 3, autoAttemptDelay: 5, preventRetryResponseProperty: "preventRetry" }, classes: { buttonHover: "qq-upload-button-hover", buttonFocus: "qq-upload-button-focus" }, chunking: { enabled: false, concurrent: { enabled: false }, mandatory: false, paramNames: { partIndex: "qqpartindex", partByteOffset: "qqpartbyteoffset", chunkSize: "qqchunksize", totalFileSize: "qqtotalfilesize", totalParts: "qqtotalparts" }, partSize: 2000000, // only relevant for traditional endpoints, only required when concurrent.enabled === true success: { endpoint: null } }, resume: { enabled: false, recordsExpireIn: 7, //days paramNames: { resuming: "qqresume" } }, formatFileName: function(fileOrBlobName) { if (fileOrBlobName !== undefined && fileOrBlobName.length > 33) { fileOrBlobName = fileOrBlobName.slice(0, 19) + "..." + fileOrBlobName.slice(-14); } return fileOrBlobName; }, text: { defaultResponseError: "Upload failure reason unknown", sizeSymbols: ["kB", "MB", "GB", "TB", "PB", "EB"] }, deleteFile: { enabled: false, method: "DELETE", endpoint: "/server/upload", customHeaders: {}, params: {} }, cors: { expected: false, sendCredentials: false, allowXdr: false }, blobs: { defaultName: "misc_data" }, paste: { targetElement: null, defaultName: "pasted_image" }, camera: { ios: false, // if ios is true: button is null means target the default button, otherwise target the button specified button: null }, // This refers to additional upload buttons to be handled by Fine Uploader. // Each element is an object, containing `element` as the only required // property. The `element` must be a container that will ultimately // contain an invisible `<input type="file">` created by Fine Uploader. // Optional properties of each object include `multiple`, `validation`, // and `folders`. extraButtons: [], // Depends on the session module. Used to query the server for an initial file list // during initialization and optionally after a `reset`. session: { endpoint: null, params: {}, customHeaders: {}, refreshOnReset: true }, // Send parameters associated with an existing form along with the files form: { // Element ID, HTMLElement, or null element: "qq-form", // Overrides the base `autoUpload`, unless `element` is null. autoUpload: false, // true = upload files on form submission (and squelch submit event) interceptSubmit: true }, // scale images client side, upload a new file for each scaled version scaling: { // send the original file as well sendOriginal: true, // fox orientation for scaled images orient: true, // If null, scaled image type will match reference image type. This value will be referred to // for any size record that does not specific a type. defaultType: null, defaultQuality: 80, failureText: "Failed to scale", includeExif: false, // metadata about each requested scaled version sizes: [] }, workarounds: { iosEmptyVideos: true, ios8SafariUploads: true, ios8BrowserCrash: true } };
我们可以看到有很多很多的配置,大部分我也没有仔细的研究,只是看了一下常用的一些配置,并且在index_index.html也做了备注。我们关注的比较多的就是:multiple、autoUpload、validation、callbacks、messages、template,基本上这些就能满足我们的需求了。下面详解一下这几个参数的作用:
- multiple是否支持多文件上传
- autoUpload是否自动上传,就是说选中文件后,是否还需要手动点击上传按钮触发开始上传事件
- validation文件约束条件,包含文件格式、文件最大值、文件最小值
- callbacks各种回调函数,包含开始提交、开始上传、正在上传、上传成功、取消上传
- messages一些默认提示信息,可以将源文件的错误提示汉化调整,例子中只是i调整了noFilesError,业务可以根据自己需求进行配置
- template可以使用script模版或者dom模版,只要传模版的ID字符串或者dom对象
- debug这个就不用说了,为了开发调试使用,会记录详细的上传过程,便于查找问题出现的位置
除了配置之外,在说明一下uploadStoredFiles这个方法,是为了方便为手动上传绑定时候使用。
至此,一个简单的无刷新上传就完成了。这里对thinkjs和Fine Uploader的讲解都是一些皮毛,简单的说明一下怎么使两者很好的配合起来,如果想要详细的学习thinkjs和Fine Uploader还是建议直接去官网学习。
thinkjs官网:http://thinkjs.org/
Fine Uploader官网:http://fineuploader.com/
参考资料: