自己封裝一個文件上傳函數(使用axios)

序言

昨天在寫博客的個人動態頁面,裏面涉及到了圖片上傳。

之前我都是用的別人的插件和elementUI的upload組件。但是現在沒法用了。

頁面的效果差別有點大,如果改elementUI的樣式,會很累。

這時候很愁人啊。(懶啊!)

這是頁面開發的效果,很像qq空間的感覺。

 

一、選型

1、選擇1:自己從新寫系列。

我參考了(簡稱xhr)和Fetch

文檔地址:

XMLHttpRequest:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest

fetch:https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

上面的xhr其實就是我們用的最多的http的核心函數,早年的jquery.ajax和如今的axios都是用的這個。

fetch,兼容性差點,但是是最新的標準函數。已經沒有那麼多亂七八糟的東西了。把xhr幹成了ajax的樣子。

總結:

xhr的情況下需要完整的自己寫一遍ajax函數

fetch呢是新標準,缺點是ie全系不支持,並且edge也有部分不支持。

哎呀:就是懶啊。不想自己寫。(而且:自己寫封裝要考慮好多東西)

2、選axios

這個嗎,沒什麼多說的。肯定不會選jq。

而且我早先的時候自己封裝過axios,形成了自己項目下的專屬使用的ajax函數。所以這次的封裝是在自己封裝的ajax函數上進行復用。

這裏放出下我現如今的整體的axios內部的情況。

主角是下半部分的axios.upload

"use strict";
import Vue from "vue";
import axios from "axios";
import VueAxios from "vue-axios";

import { Notification, Loading } from "element-ui";
import apiList from "./api-list"; //接口列表數據
const store = require("store");
import control from "@/common/control_center/index";
// axios全局導入設置
Vue.use(VueAxios, axios);
Vue.prototype.$api = apiList; //將接口列表數據綁定到vue全局

//自定義消息提示函數信息
let customMsg = {
  //成功信息提示
  sucIfno(info) {
    Notification({
      title: "答對了!",
      type: "success",
      message: info
    });
  },
  //錯誤信息提示
  errIfno(info) {
    Notification({
      title: "答錯了呢",
      type: "error",
      message: info
    });
  }
};
// 授權函數封裝
const authorize = herders => {
  const user_info = store.get("user_info");
  if (user_info && user_info.sign) {
    herders.Authorization = user_info.sign;
    return herders;
  } else {
    return herders;
  }
};
// axios函數封裝
const ajax = ({
  url = "",
  loading = false, //加載攔截
  baseURL = apiList.baseURL,
  data = {},
  headers = { "Content-Type": "application/json;charset=UTF-8" }, //頭部信息處理
  method = "get",
  success = false, //成功信息提示
  error = true, //錯誤信息提示
  timeout = 1000
}) => {
  // 數據過濾,過濾字段中空數據等
  const filter = record => {
    for (let key in record) {
      !record[key] && delete record[key];
    }
    return record;
  };
  //接口全局加載提示
  let loadingInstance = "";
  if (loading !== false) {
    loadingInstance = Loading.service({
      lock: true,
      text: loading !== true ? loading : "努力加載中……",
      spinner: "el-icon-loading",
      background: "rgba(0, 0, 0, 0.5)"
    });
  }
  return new Promise((suc, err) => {
    // 預處理數據部分
    method = method.toLocaleLowerCase(); //轉化爲小寫
    headers = authorize(headers);
    axios({
      url: url,
      baseURL: baseURL,
      headers: headers,
      method: method,
      [method === "post" ? "data" : "params"]: filter(data),
      timeout: timeout
    })
      .then(response => {
        loadingInstance && loadingInstance.close();
        // 刷新口令以及接口判斷是否退出登錄
        if (!control.refresh_sign_or_out(response)) {
          customMsg.errIfno("數據異常,退出登錄");
          err(response);
        }

        const res = response.data;
        //自定義成功失敗處理,code值代表後端接口數據處理成功或者失敗
        // 後端返回格式
        /*data = {
          code: 0, // 0成功,1失敗
          msg: "", // 錯誤信息
          data: "" // 數據
        };*/
        if (res && res.code === 0) {
          success !== false &&
            customMsg.sucIfno(success === true ? "信息處理成功" : success);

          suc(res);
        } else {
          error !== false &&
            customMsg.errIfno(res.msg ? res.msg : "信息處理失敗");

          err(res);
        }
      })
      .catch(e => {
        console.log(e);
        loadingInstance && loadingInstance.close();
        error !== false ? customMsg.errIfno("接口異常") : false;
        //catch代表網絡異常部分和後端返回結果無關
        err(e);
      });
  });
};

//暴露的ajax函數,進一步封裝節流和防抖
let shakeTime = "";
axios.ajax = options => {
  //參數預處理
  let shake = options.shake || false; //不等於false直接傳true或者防抖時間
  //防抖函數處理
  if (shake === false) {
    //不進行防抖處理
    return new Promise((suc, err) => {
      ajax(options)
        .then(e => {
          suc(e);
        })
        .catch(e => {
          err(e);
        });
    });
  } else {
    //進行防抖處理
    return new Promise((suc, err) => {
      shakeTime && clearTimeout(shakeTime);
      let callNow = !shakeTime;
      if (callNow) {
        ajax(options)
          .then(e => {
            suc(e);
          })
          .catch(e => {
            err(e);
          });
      }
      shakeTime = setTimeout(
        () => {
          shakeTime = null;
        }, //見註解
        shake === true ? 700 : shake
      );
    });
  }
};
axios.upload = options => {
  // 對uri地址進行數據拼接
  const new_url = obj => {
    if (obj) {
      let fields = "";
      for (let key in obj) {
        fields = fields + `${key}=${obj[key]}`;
      }
      return "?" + fields;
    } else {
      return "";
    }
  };
  options.fdata = options.fdata || ""; //文件上傳的url拼接地址
  options.success = options.success || "文件上傳成功";
  options.url = options.url + new_url(options.fdata);

  options.headers = options.headers || {};
  let header = { "Content-Type": "multipart/form-data" };
  for (let i in header) {
    options.headers[i] = header[i];
  }

  options.method = "post";
  options.multiple = options.multiple || false; //是否多文件,默認false
  //文件類型驗證,注意傳入數組,默認["image/jpeg", "image/png"]
  options.type = options.type || ["image/jpeg", "image/png"];
  options.size = options.size || 0; //文件大小限制,默認0
  options.max = options.max || 5; //最多上傳幾個文件

  //文件驗證處理
  let input = document.createElement("input");
  input.type = "file";
  options.multiple ? (input.multiple = "multiple") : "";
  input.click();

  return new Promise((suc, err) => {
    let type = options.type;
    input.addEventListener("input", watchUpload, false);
    function watchUpload(event) {
      //移除監聽
      let remove = () => {
        input.removeEventListener("input", watchUpload, false);
        input = null;
      };
      const file = event.path[0].files;
      const len = file.length;
      // 文件數量限制
      if (len > options.max) {
        err(file);
        remove();
        customMsg.errIfno("文件個數超過" + options.max);
        return false;
      }
      let formData = new FormData();
      for (let i = 0; i < len; i++) {
        // 文件大小限制
        if (options.size !== 0 && file[i].size / 1024 / 1024 > options.size) {
          err(file[i]);
          remove();
          customMsg.errIfno(file[i].name + "文件超過指定大小");
          return false;
        }
        // 文件類型限制
        if (type.length > 0 && !type.includes(file[i].type)) {
          err(file);
          remove();
          customMsg.errIfno(file[i].name + "文件類型爲" + file[i].type);
          return false;
        }
        formData.append("dhtUpload", file[i], file[i].name);
      }
      options.data = formData;
      // 最終進行文件上傳
      options.baseURL = "";
      ajax(options)
        .then(e => {
          suc(e);
        })
        .catch(e => {
          err(e);
        });
    }
  });
};

export default axios;

二、開始編寫upload函數

注意,其實本身是直接用的axios效果沒什麼差別。不要被我自己封裝的ajax迷糊了。

1、不在頁面上展現input元素

這裏的初衷就是我以函數的形式,而不在頁面上存在input元素。我看網上很多教程都是預先定義好input節點的

這裏我放出自己一個基於input的顏色選擇器。文件的方式類似這個

//顏色選擇器
colorSelect() {
  let input = document.createElement("input");
  input.type = "color";
  input.click();
  input.addEventListener("input", watchColorPicker, false);
  function watchColorPicker(event) {
    //console.log(event.target.value);

    //移除監聽
    input.removeEventListener("input", watchColorPicker, false);
    input = "";
  }
}

2、先看下input監聽取得的值是什麼

我們要的是path裏面的值。看看path裏面

注意這裏就一個input元素,我們取input中的files數組就是我們的文件了。

這裏看看單個文件的情況

這裏我們能看到每個文件的大小,類型的情況。這些在後面會使用到。比如我們限制文件上傳個數,文件的大小和文件類型

3、瞭解FormData()

這裏我直接放MDN文檔了。

https://developer.mozilla.org/zh-CN/docs/Web/API/FormData/Using_FormData_Objects

其實我們只需要知道,是下面這樣用的就行了。

let formData = new FormData();
formData.append("dhtUpload", file[i], file[i].name);

append參數:文件名稱,文件,文件名稱

這裏其實我沒太理解

放一下elementUI源碼中的一個情況。

formData.append(option.filename, option.file, option.file.name);

具體沒太懂。

4、瞭解下ajax函數的情況

const ajax = ({
  url = "",
  loading = false, //加載攔截
  baseURL = apiList.baseURL,
  data = {},
  headers = { "Content-Type": "application/json;charset=UTF-8" }, //頭部信息處理
  method = "get",
  success = false, //成功信息提示
  error = true, //錯誤信息提示
  timeout = 1000
})

上面就是我的ajax函數的參數。

5、upload函數的參數預處理

// 對uri地址進行數據拼接
const new_url = obj => {
  if (obj) {
    let fields = "";
    for (let key in obj) {
      fields = fields + `${key}=${obj[key]}`;
    }
    return "?" + fields;
  } else {
    return "";
  }
};
options.baseURL = ""; //個人處理,需要兼容之前的elementui等插件的上傳
options.fdata = options.fdata || ""; //文件上傳的url拼接地址
options.success = options.success || "文件上傳成功";
options.url = options.url + new_url(options.fdata);
options.loading = options.loading || true;

options.headers = options.headers || {};
options.headers["Content-Type"] = "multipart/form-data";

options.method = "post";
options.multiple = options.multiple || false; //是否多文件,默認false
//文件類型驗證,注意傳入數組,默認["image/jpeg", "image/png"]
options.type = options.type || ["image/jpeg", "image/png"];
options.size = options.size || 0; //文件大小限制,默認0
options.max = options.max || 5; //最多上傳幾個文件

看着參數好多是吧。其實沒多少。

解析一下:

fdata:因爲文件上傳地址肯定是多樣的。但是我們又需要傳遞一些文件之外的信息。這時候其實可以在地址欄拼接參數。後端也是能獲取到的。

new_url 和fdata是爲了這個存在的。

success:單純的的文件上傳提示信息。這個是因爲之前ajax封裝將提示信息封裝進去了,但是默認成功不提示。

headers:這個沒什麼多說的,但是爲了保證headers可能有別的存在,那麼就這樣進行設置了。"Content-Type":"multipart/form-data"這個是必須的

loading:文件加載的時候全屏出現加載提示

method:必須是post

multiple:這個這裏缺失了一點,就是爲了控制文件是多文件還是單文件上傳

type:文件類型的控制。這裏傳入數組。默認控制圖片文件。傳入什麼默認限制這些類型

size:限制每個文件的大小,0不限制

max:限制文件個數,單文件沒意義

6、創建input對象,並且做好參數預處理。

//文件驗證處理
let input = document.createElement("input");
input.type = "file";
options.multiple ? (input.multiple = "multiple") : "";
input.click();

代碼就這麼簡單,這裏options.multiple就是剛纔參數設置的意義所在了

7、創建input監聽,獲取文件對象信息,注意外層用promise封裝下

這裏我放整個控制部分,然後拆開解析

return new Promise((suc, err) => {
  let type = options.type;
  input.addEventListener("input", watchUpload, false);
  function watchUpload(event) {
    //console.log(event);
    //移除監聽
    let remove = () => {
      input.removeEventListener("input", watchUpload, false);
      input = null;
    };
    const file = event.path[0].files;
    const len = file.length;
    // 文件數量限制
    if (len > options.max) {
      remove();
      customMsg.errIfno("文件個數超過" + options.max);

      err(file);
      return false;
    }
    let formData = new FormData();
    for (let i = 0; i < len; i++) {
      // 文件大小限制
      if (options.size !== 0 && file[i].size / 1024 / 1024 > options.size) {
        remove();
        customMsg.errIfno(file[i].name + "文件超過指定大小");

        err(file[i]);
        return false;
      }
      // 文件類型限制
      if (type.length > 0 && !type.includes(file[i].type)) {
        remove();
        customMsg.errIfno(file[i].name + "文件類型爲" + file[i].type);

        err(file);
        return false;
      }
      formData.append("dhtUpload", file[i], file[i].name);
    }
    options.data = formData;
    // 最終進行文件上傳
    ajax(options)
      .then(e => {
        suc(e);
      })
      .catch(e => {
        err(e);
      });

    // 沒有問題下,清空監聽。
    remove();
  }
});

8、獲取文件數組信息,注意你沒有選擇文件是不會觸發監聽的

//console.log(event);
    //移除監聽
    let remove = () => {
      input.removeEventListener("input", watchUpload, false);
      input = null;
    };
    const file = event.path[0].files;
    const len = file.length;
    // 文件數量限制
    if (len > options.max) {
      remove();
      customMsg.errIfno("文件個數超過" + options.max);
      err(file);
      return false;
    }
    let formData = new FormData();

這裏看註釋也知道我幹了什麼。

第一:我獲取文件數組。並且預先將移除監聽處理好,因爲很多地方會用到。

第二:我判斷文件數量限制。超過的話,promise(代碼中的err(file))返回錯誤;並且註銷監聽

第三:先聲明formdata對象

9、爲每個元素進行限制,並且加入到formdata中

for (let i = 0; i < len; i++) {
  // 文件大小限制
  if (options.size !== 0 && file[i].size / 1024 / 1024 > options.size) {
    remove();
    customMsg.errIfno(file[i].name + "文件超過指定大小");

    err(file[i]);
    return false;
  }
  // 文件類型限制
  if (type.length > 0 && !type.includes(file[i].type)) {
    remove();
    customMsg.errIfno(file[i].name + "文件類型爲" + file[i].type);

    err(file);
    return false;
  }
  formData.append("dhtUpload", file[i], file[i].name);
}
options.data = formData;

這裏應該很明顯了。我遍歷每個file文件,並且進行文件大小和文件類型的判斷限制。

最後將通過的formdata數據賦值給ajax的data對象中

10、萬事大吉,調用原本封裝的ajax

// 最終進行文件上傳
ajax(options)
  .then(e => {
    suc(e);
  })
  .catch(e => {
    err(e);
  });

// 沒有問題下,清空監聽。
remove();

三、完整的使用。

this.axios
  .upload({
    url: this.$api.static().upload_pictures
  })
  .then(e => {
    console.log("成功", e);
  })
  .catch(e => {
    console.log("錯誤", e);
  });

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章