序言
昨天在寫博客的個人動態頁面,裏面涉及到了圖片上傳。
之前我都是用的別人的插件和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); });