最近不小心更新了element-ui的版本,已經到了2.1.0,以前修改的源碼都失效了。 於是重新嘗試下麵的指令重新修改: 這時候會發現,不僅npm run dist的eslint日常報錯,連npm install都報錯了,不過是普通的operation not permitted,用管理員許可權運行 ...
最近不小心更新了element-ui的版本,已經到了2.1.0,以前修改的源碼都失效了。
於是重新嘗試下麵的指令重新修改:
git clone https://github.com/ElemeFE/element.git cd element npm install npm run dist
這時候會發現,不僅npm run dist的eslint日常報錯,連npm install都報錯了,不過是普通的operation not permitted,用管理員許可權運行下cmd就OK了。
問題在於這個dist出來的lib文件夾,我去掉了eslint的掃描指令後,可以成功打包,生成的文件夾覆蓋後,有一個組件報錯了:
這真的是搞毛,我只是修改了upload的組件,分頁組件居然報錯了。
我以為兩者是關聯的,但是我什麼都不改,直接dist後,仍然會報這個錯。
這時只有兩個選擇,去修改pagination組件源碼,嘗試修複錯誤。或者不改源碼,嘗試從給的方法解決問題。
因為如果改源碼要鎖定版本,還是非常壞事的,所以這次我決定從暴露出的API來解決我的問題。
問題還是那個老的,upload組件點擊刪除圖片時必須彈一個確認框,點擊確認後才能刪除,如圖:
這裡我直接用了element-ui的另一個指令this.$message,之前是在源碼里刪除圖片前直接加了一個判斷:
// 根據變數控制流程 handleRemove(file, raw) { // 添加的確認環節 if (this.jimmyRemoveTip) { this.$confirm('此操作將永久刪除圖片, 是否繼續?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(() => { // ...刪除圖片 }).catch(() => { this.$message({ type: 'info', message: '已取消刪除' }); }); }else { // 正常流程 } }
但是現在這條路行不通了,於是我嘗試看看新版本的element-ui有沒有加輔助代碼。
直接進入了element-ui的packages/upload/src/index.vue中,翻到handleRemove方法,驚喜的發現多了一個判斷:
handleRemove(file, raw) { if (raw) { file = this.getFile(raw); } let doRemove = () => { this.abort(file); let fileList = this.uploadFiles; fileList.splice(fileList.indexOf(file), 1); this.onRemove(file, fileList); }; if (!this.beforeRemove) { doRemove(); } else if (typeof this.beforeRemove === 'function') { const before = this.beforeRemove(file, this.uploadFiles); if (before && before.then) { before.then(() => { doRemove(); }, noop); } else if (before !== false) { doRemove(); } } }
這裡有一個新的判斷beforeRemove,跑去官網看,是這樣解釋的:
這就很爽了,根據源碼的情況和官網解釋,只要返回一個false或者reject就能讓刪除操作停止。
返回false是很簡單,但是這裡我只能用$message指令,這玩意返回的是一個Promise,於是我要想辦法在點擊取消的時候reject掉,一開始我是這樣寫的:
return this.$confirm('是否確定刪除該圖片 ?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(null, e => { throw new Error('reject'); });
相當暴力,這個當然解決了問題。但是拋出個錯誤總覺得很不雅,最後改成了這樣:
return this.$confirm('是否確定刪除該圖片 ?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(null).catch(e=>{ e(); });
解決了問題一,還剩另外一個。當上傳的圖片到上限時,需要把上傳按鈕隱藏。
目前upload組件有一個關於上限的參數,是limit,但是這個參數很局限,在上傳圖片達到上限時,會中止上傳,源碼如下:
uploadFiles(files) { // 達到上限調用on-exceed鉤子函數並返回 if (this.limit && this.fileList.length + files.length > this.limit) { this.onExceed && this.onExceed(files, this.fileList); return; } let postFiles = Array.prototype.slice.call(files); if (!this.multiple) { postFiles = postFiles.slice(0, 1); } if (postFiles.length === 0) { return; } postFiles.forEach(rawFile => { this.onStart(rawFile); if (this.autoUpload) this.upload(rawFile); }); }
雖然可以在on-exceed鉤子函數中提示用戶圖片上傳達到上限,但是產品要上傳按鈕消失,這就很頭疼了……
目前還未找到完美的解決辦法,先在鉤子函數中做上限提示……
在不久之前,後臺也提了一個需求,說多張上傳不能太多,最多一次10張,不然圖片伺服器處理不過來。
如果能改源碼,這個需求分分鐘就搞定。但是現在行不通,只能從現存的API來想辦法。
整個組件結構很簡單:
// 根據類型擺放圖片展示list的位置 // uploadComponent就是那個上傳按鈕 // $slots.default是白框框中間自定義的圖標 // $slots.tip是下麵的文字 <div> { this.listType === 'picture-card' ? uploadList : ''} { this.$slots.trigger ? [uploadComponent, this.$slots.default] : uploadComponent } {this.$slots.tip} { this.listType !== 'picture-card' ? uploadList : ''} </div>
示例代碼如下圖所示:
<div class="upload-content"> <el-upload /*...*/> <!-- slot預設為default --> <p>default</p> <p slot='tip'>tips</p> </el-upload> </div>
效果圖:
而uploadList組件就是已上傳的圖片展示組件,這個組件沒有暴露API,純展示,所以我的第二個問題暫時不好處理。
接下來看上傳流程,整個upload組件的入口代碼如下:
<input class="el-upload__input" type="file" ref="input" name={name} on-change={handleChange} multiple={multiple} accept={accept}></input>
核心上傳按鈕,屬性直接看就懂了,點擊上傳按鈕後,監聽原生on-change事件,觸發handleChange:
handleChange(ev) { // 獲取事件對應的files const files = ev.target.files; if (!files) return; this.uploadFiles(files); }
方法獲取對應事件的文件或文件列表,調用uploadFiles方法:
uploadFiles(files) { // 文件超過限制觸發鉤子函數並返回 if (this.limit && this.fileList.length + files.length > this.limit) { this.onExceed && this.onExceed(files, this.fileList); return; } // 轉換為純數組 let postFiles = Array.prototype.slice.call(files); // 單文件上傳模式只取第一個 if (!this.multiple) { postFiles = postFiles.slice(0, 1); } if (postFiles.length === 0) { return; } // 調用forEach依次上傳 postFiles.forEach(rawFile => { this.onStart(rawFile); // 根據參數進行自動上傳 if (this.autoUpload) this.upload(rawFile); }); }
這裡後臺跟我吐槽為什麼要一個一個上傳,弄成一個請求多好,同時上傳100張就要發100次請求,所以伺服器處理不過來。
因為上傳這部分之前瞭解較少,所以也不明白這裡為什麼這樣處理。
先不看onStart方法,過一下自動上傳流程:
upload(rawFile, file) { // 清空上傳按鈕內容 this.$refs.input.value = null; // 判斷是否存在before-upload鉤子函數 if (!this.beforeUpload) { return this.post(rawFile); } // 鉤子函數處理 const before = this.beforeUpload(rawFile); if (before && before.then) { before.then(processedFile => { const fileType = Object.prototype.toString.call(processedFile); if (fileType === '[object File]' || fileType === '[object Blob]') { this.post(processedFile); } else { this.post(rawFile); } }, () => { this.onRemove(null, rawFile); }); } else if (before !== false) { this.post(rawFile); } else { this.onRemove(null, rawFile); } }
這個地方是判斷文件上傳數量的絕佳位置,但是很遺憾,源碼里沒有給出機會做處理,所以無法在該鉤子函數中判斷上傳文件的數量。
然後調用了post方法做文件上傳準備:
post(rawFile) { const { uid } = rawFile; // 上傳文件的參數與回調函數 const options = { headers: this.headers, withCredentials: this.withCredentials, file: rawFile, data: this.data, filename: this.name, action: this.action, onProgress: e => { this.onProgress(e, rawFile); }, onSuccess: res => { this.onSuccess(res, rawFile); delete this.reqs[uid]; }, onError: err => { this.onError(err, rawFile); delete this.reqs[uid]; } }; // 上傳 const req = this.httpRequest(options); this.reqs[uid] = req; // 返回結果 if (req && req.then) { req.then(options.onSuccess, options.onError); } }
這裡的上傳方法httpRequest可以自定義,預設是內置的ajax方法。
ajax就沒什麼營養了,所以正常人使用的上傳流程大概就是這樣。
但是我不行啊,需要不修改源碼的情況下完成特殊需求,還是有點……
過了一遍流程,發現可操作的地方只有一個地方,那就是auto-upload那裡,將自動上傳置false,然後進行二次處理,所以之前的流程會斷:
postFiles.forEach(rawFile => { // 進入onStart this.onStart(rawFile); // 不進行上傳 if (this.autoUpload) this.upload(rawFile); })
這裡看一下onStart的內容:
// 'on-start': this.handleStart handleStart(rawFile) { // 隨機數 rawFile.uid = Date.now() + this.tempIndex++; // 包裝文件對象 let file = { status: 'ready', name: rawFile.name, size: rawFile.size, percentage: 0, uid: rawFile.uid, raw: rawFile }; // 轉換URL以便展示 try { file.url = URL.createObjectURL(rawFile); } catch (err) { console.error(err); return; } // 將文件彈入圖片展示數組 this.uploadFiles.push(file); // 觸發onChange事件 this.onChange(file, this.uploadFiles); }
這個函數主要是封裝了file對象,展示上傳的圖片但不做上傳操作。
這樣的話就有操作空間了,於是我監聽了onChange事件並看看返回的參數內容:
<el-upload /*...*/ :on-change='uploadFile'> </el-upload>
列印內容如下:
其中第一個參數是封裝的file對象,url是轉換的本地路徑以便展示。
第二個參數是uploadList的組件參數,會將之前已有的圖片也加進來。
如果同時上傳10張照片,會觸發10次onChange事件,由於不會做上傳操作,所以可以在前端處理掉。
當時我想著不如在這裡把所有上傳的請求整合成一個,但是由於每個文件上傳都會觸發該事件,我又無法獲取上傳文件數量,所以想著還是限制吧。
假想代碼如下:
uploadFile(f, fl) { let localFileList = this.list, len = localFileList.length; // 獲取上傳的圖片數量 let uploadFileList = fl.slice(len); if (uploadFileList.length > 10) { // 不進行上傳 } else { // 正常上傳 } }
然而這是不現實的,問題還是每個上傳都會觸發該事件,所以會重覆上傳之前的圖片。
不搞了……不想自己封裝組件,木有UI天賦。
沒辦法,已經提了Feature Request,不知道有沒有用。
啊……年後深圳求職,一年萌新求帶走。