淺探element-ui2組件源碼之upload

来源:https://www.cnblogs.com/QH-Jimmy/archive/2018/02/06/8421323.html
-Advertisement-
Play Games

最近不小心更新了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,不知道有沒有用。

  

  啊……年後深圳求職,一年萌新求帶走。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 理解瀏覽器事件模型 understandEventModel.html 代碼: understandEventModel.js: jQuery 事件模型 jQueryEventModel.html: jQueryEventModel.js: ...
  • 後端開發:1、高級java軟體架構師實戰培訓視頻教程2、大型SpringMVC,Mybatis,Redis,Solr,Nginx,SSM分散式電商項目視頻教程3、Spark Streaming實時流處理項目實戰4、Java校招面試 Google面試官親授5、Java開發企業級許可權管理系統6、Java ...
  • 後端開發:1、高級java軟體架構師實戰培訓視頻教程2、大型SpringMVC,Mybatis,Redis,Solr,Nginx,SSM分散式電商項目視頻教程3、Spark Streaming實時流處理項目實戰4、Java校招面試 Google面試官親授5、Java開發企業級許可權管理系統6、Java ...
  • 後端開發:1、高級java軟體架構師實戰培訓視頻教程2、大型SpringMVC,Mybatis,Redis,Solr,Nginx,SSM分散式電商項目視頻教程3、Spark Streaming實時流處理項目實戰4、Java校招面試 Google面試官親授5、Java開發企業級許可權管理系統6、Java ...
  • QQ音樂網站所有音樂(包括付費、無損等版權音樂解析介面地址url)。 mp3 普通高品 mp3 高品質 ape 格式 flac 格式 url中的vkey獲取是關鍵,歡迎大家交流心得!利用這個介面筆者寫了個微信點歌的公眾號微點歌:vdiange 歡迎大家測試! 大家可以添加微點歌:vdiange 測試 ...
  • 本文最初發表於 "博客園" ,併在 "GitHub" 上持續更新 前端的系列文章 。歡迎在GitHub上關註我,一起入門和進階前端。 以下是正文。 HTML5的介紹 Web 技術發展時間線 1991 HTML 1994 HTML2 1996 CSS1 + JavaScript 1997 HTML4 ...
  • 作用: export和export default實現的功能相同,即:可用於導出(暴露)常量、函數、文件、模塊等,以便其他文件調用。 區別: 1、export導出多個對象,export default只能導出一個對象 2、export導出對象需要用{ },export default不需要{ },如 ...
  • 減小文件搜索範圍 配置 resolve.modules Webpack的 配置模塊庫(即 node_modules)所在的位置,在 js 里出現 這樣不是相對、也不是絕對路徑的寫法時,會去 目錄下找。但是預設的配置,會採用向上遞歸搜索的方式去尋找,但通常項目目錄里只有一個 ,且是在項目根目錄,為了減 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...