el-upload源碼修改跳坑


之前給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}就行了。


​ 最后測試結果十分成功!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM