之前給element-ui提了一個問題,結果沒有鳥我,沒辦法,只能修改源碼來滿足需求了
(備注:element-ui2依然沒有修改,為了迎合產品還是要改源碼)
本文討論的組件屬性僅限於list-type='picture-card'
現在的問題是這樣的:
element-ui中有一個upload組件,可以上傳圖片或文件。該組件有很豐富的鈎子函數與配置,但是沒有一個限制上傳圖片數量(即使是按鈕禁用)的方法,於是我就自己封裝了一下:
<el-upload :disabled='is_max' @s='...' @r='....' ...其余配置></el-upload>
export default = {
props:['num'], // 限制上傳數量
methods:{
on-success:function(r,f,fl){
this.is_max = true;
if(this.num && this.num === fl.length){
this.is_max = false;
}
this.$emit('s',f,fl);
},
on-remove:function(f,fl){
this.is_max = false;
this.$emit('r',f);
},
on-progress:function(){
this.is_max = true;
}
}
}
封裝添加了一個num參數,限制上傳數量,並在上傳過程中禁用上傳按鈕。
最初的目的是達到上傳數量限制,就禁止上傳,但是可以刪除圖片,刪除后解除限制。
這個需求在1.3.7版本可以實現,但是在后面的某一次(可見我上一篇文章的結尾)commit中被修改了,刪除與上傳同時受disabled影響,如果設置disabled='true',那么這個組件變成了一個純展示作用,無法刪除,無法添加。
后來決定暫時用1.3.7版本打包,先出功能。
原以為事情可以緩一緩,沒想到產品對那個上傳按鈕十分不滿,認為如果達到了上限,這個按鈕會產生困惑,應該消失。禁用不行,里面弄個叉也不行,必須要消失!
好吧,只能來一發硬的,改源碼。
上傳按鈕控制的標簽如下所示,type為picture,text,picture-card之一。
<div class='el-upload el-upload--(type)'></div>
當時有兩個想法:
1、修改disabled的默認行為,讓上傳按鈕消失,刪除按鈕不作用
2、引入新的變量,單獨控制上傳按鈕
由於不知道源碼的內部行為,所以先想着。
一開始想法十分簡單,找到了目錄node_modules\element-ui\packages\upload\src下的upload.vue文件,為了看效果,直接添加了一行代碼:
// line-163
const data = {
class: {
'el-upload': true,
'abc': true, // 這里是我自己加的
},
on: {
click: handleClick
}
};
保存-打包-打開網頁-F12,發現並沒有abc這個類被加上去,查看打包后的JS文件,依然只有一個el-upload類。
這就十分尷尬了,於是換了個地方,找到了node_modules\element-ui\lib下的upload.js文件,添加了兩行代碼:
// line-934
var data = {
class: {
'el-upload': true,
'abc': true //同上
},
on: {
click: handleClick
}
};
// line-1312
var oClass = { 'el-upload': true, 'abc':true};
oClass['el-upload--' + listType] = true;
這里直接修改了打包后的render函數,我想着這次應該穩了,結果打包后一看,還是不行。
這就十分尷尬了,只能上網搜搜。一開始找到教程,教你修改哪個文件,然后實現功能,然而跟我之前的步驟一樣,不可信。繼續搜索,終於找到了一位小哥:https://segmentfault.com/a/1190000010932321
懶得看網頁的可以找一個安靜的文件夾,依次運行以下指令:
git clone https://github.com/ElemeFE/element.git
cd element
npm install
此時,element的元身會被下載下來。接下來修改package文件夾中的源碼,運行npm run dist指令,該指令會在當前目錄生成一個lib文件夾,用這個文件夾替換node_modules中的lib文件夾,然后打包就可以了!
事不宜遲,試試!
找到了element\packages\upload\src下的upload.vue,再次進行修改:
class: {
'el-upload': true,
'abc':true,
},
又是熟悉的操作,然而報了2個error,一臉懵逼的看錯誤信息:
1、Missing space before value for key 'abc'
2、Unexpected trailing comma
憑着我過硬的英文功底修正了錯誤:
1、冒號后面要加空格
2、最后一個屬性的逗號要省略
修改后再次運行,眼前閃過一片花花綠綠,成了。
覆蓋了lib文件夾后再次嘗試,可以驚喜的看到:
<div class='el-upload abc el-upload--picture-card'></div>
感覺人生都陽光起來了,接下來是實現目標的時候了。
簡略的看了下upload組件內部,有包含5個組件index、iframe-upload、upload-dragger、upload-list、upload。
其中iframe-table是造一個表單,然后表單提交圖片信息,略過。
upload-dragger是拖拽功能,略過。
upload-list實現已圖片上傳預覽,略過。
需要關注的只有整合所有組件的index與上傳按鈕相關的upload組件了。
index.vue的整個大概是這樣:
<div>
<upload-list></upload-list> <!-- 用於展示圖片 僅當picture-card類型時在前面 -->
<uploadComponent> <!-- 上傳 內部的DOM會被當成上傳按鈕 通過FormDataAPI決定調用表單或者ajax -->
<upload></upload> <!-- drag屬性決定是否支持拖拽 -->
</uploadComponent>
</div>
仔細看了一遍內部實現,再回頭看一眼之前的2個想法,決定用第二個,修改默認有點麻煩。
方式比較簡單粗暴,在所有定義diabled的地方加上了一條新語句,比如:
// line-94
{
disabled: Boolean
Jimmy_input_btn_disabled: Boolean
}
這樣就自定義了一個新的數據,第二步,通過這個來阻止上傳按鈕生成。
找來找去,只有一個地方可以控制:
// line-254
// 這里是上傳按鈕的渲染點
const uploadComponent =
this.Jimmy_input_btn_disabled ? '' : // 我加的
(typeof FormData !== 'undefined' || this.$isServer)
? <upload {...uploadData}>{trigger}</upload>
: <iframeUpload {...uploadData}>{trigger}</iframeUpload>;
加上后,打包測試:
<el-upload :Jimmy_input_btn_disabled='is_max' ...其余配置></el-upload>
在圖片達到上限后,上傳按鈕驚喜的消失了,舒服!
然而,在下一秒,我刪除圖片的時候,就報了一個錯,abort無法執行。
順着報錯信息,找到了這里:
// 刪除時候會調用的函數
handleRemove(file, raw) {
if (raw) {
file = this.getFile(raw);
}
this.abort(file);
// ...
},
//...
abort(file) {
this.$refs['upload-inner'].abort(file);
},
這個upload-inner呢,在index.vue也有定義:
const uploadData = {
props: {
// ...一堆參數
},
ref: 'upload-inner'
};
再看看上面uploadComponent的定義,我瞬間明白了,這個ref被作為參數傳給了上傳按鈕,刪除圖片需要執行綁定在該按鈕上面的abort函數。問題是,這個按鈕被我弄沒了,而且由於vue的'析構',DOM上的事件也沒了。
結果就是,不可行,粗暴是不對的,兩個方案同時否決。
想了好久,既然不能讓DOM消失,那么弄成display:none不就OK了么。
兩個方案:
1、根據某個條件動態渲染一個自定義的class,該class定義為diaplay:none
2、根據某個條件動態渲染行內樣式display:none
由於render函數不太記得用法,所以第二個看起來實現有點難度,決定用第一個。當然,這個條件不能是默認的diabled,所以,我的Jimmy_input_btn_disabled又可以出場了。
修改的時候遇到了瓶頸,里面的代碼居然是JSX,我修改了upload.vue:
class: {
'el-upload': true,
'abc' : !!this.Jimmy_input_btn_disabled
},
很明顯,雖然數據傳進來了,但是並沒有做到動態渲染,這里只是初始化,所以abc沒有出現過,並且在變動的時候,根元素出現了這樣的情況:
<div Jimmy_input_btn_disabled = 'true'></div>
當時真是笑尿我了。
一度陷入了僵局,JSX不知道怎么寫。如果是vue中的render函數,我還可以寫個demo然后模仿。
最后我甚至跑去看react的教程,但是人家變動數據用的是setState……
在不斷的嘗試中的,我是找到了辦法,簡單的吐血。
其實早就發現了,只是陷入了一個誤區。
首先這個Jimmy_input_btn_disabled需要更改為String類型,作為一個動態類傳入:
disabled: Boolean,
Jimmy_inputbtn_disabled: String
第二步,在JSX中直接寫class:
<div {...data} class={Jimmy_input_btn_disabled}>
...
</div>
源碼修改完成,打包。
html中如下:
<div :Jimmy_input_btn_disabled = 'xxx'></div>
JS文件如下:
new Vue({
data:{
xxx:''
},
methods:{
s:function(){
// 達到上限
this.xxx = 'xxx';
},
r:function(){
// 解除
this.xxx = '';
}
}
});
CSS很簡單,直接設置xxx{display:none}
就行了。
最后測試結果十分成功!