最近在做一個內部系統,需要一個無刷新的上傳功能,找了許久,發現了一個好用的上傳工具-
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/
參考資料: