Vue封裝一個(gè)簡單輕量的上傳文件組件的示例
一、之前遇到的一些問題
項(xiàng)目中多出有上傳文件的需求,使用現(xiàn)有的UI框架實(shí)現(xiàn)的過程中,不知道什么原因,總會(huì)有一些莫名其妙的bug。比如用某上傳組件,明明注明(:multiple="false"),可實(shí)際上還是能多選,上傳的時(shí)候依然發(fā)送了多個(gè)文件;又比如只要加上了(:file-list="fileList")屬性,希望能手動(dòng)控制上傳列表的時(shí)候,上傳事件this.refs.[upload(組件ref)].submit()就不起作用了,傳不了??傊?,懶得再看它怎么實(shí)現(xiàn)了,我用的是功能,界面本身還是要重寫的,如果堅(jiān)持用也會(huì)使項(xiàng)目多很多不必要的邏輯、樣式代碼……
之前用Vue做項(xiàng)目用的視圖框架有element-ui,團(tuán)隊(duì)內(nèi)部作為補(bǔ)充的zp-ui,以及iview??蚣苁呛糜?,但是針對自己的項(xiàng)目往往不能全部拿來用,尤其是我們的設(shè)計(jì)妹子出的界面與現(xiàn)有框架差異很大,改源碼效率低又容易導(dǎo)致未知的bug,于是自己就抽時(shí)間封裝了這個(gè)上傳組件。
二、代碼與介紹
父組件
<template>
<div class="content">
<label for="my-upload">
<span>上傳</span>
</label>
<my-upload
ref="myUpload"
:file-list="fileList"
action="/uploadPicture"
:data="param"
:on-change="onChange"
:on-progress="uploadProgress"
:on-success="uploadSuccess"
:on-failed="uploadFailed"
multiple
:limit="5"
:on-finished="onFinished">
</my-upload>
<button @click="upload" class="btn btn-xs btn-primary">Upload</button>
</div>
</template>
<script>
import myUpload from './components/my-upload'
export default {
name: 'test',
data(){
return {
fileList: [],//上傳文件列表,無論單選還是支持多選,文件都以列表格式保存
param: {param1: '', param2: '' },//攜帶參數(shù)列表
}
},
methods: {
onChange(fileList){//監(jiān)聽文件變化,增減文件時(shí)都會(huì)被子組件調(diào)用
this.fileList = [...fileList];
},
uploadSuccess(index, response){//某個(gè)文件上傳成功都會(huì)執(zhí)行該方法,index代表列表中第index個(gè)文件
console.log(index, response);
},
upload(){//觸發(fā)子組件的上傳方法
this.$refs.myUpload.submit();
},
removeFile(index){//移除某文件
this.$refs.myUpload.remove(index);
},
uploadProgress(index, progress){//上傳進(jìn)度,上傳時(shí)會(huì)不斷被觸發(fā),需要進(jìn)度指示時(shí)會(huì)很有用
const{ percent } = progress;
console.log(index, percent);
},
uploadFailed(index, err){//某文件上傳失敗會(huì)執(zhí)行,index代表列表中第index個(gè)文件
console.log(index, err);
},
onFinished(result){//所有文件上傳完畢后(無論成?。﹫?zhí)行,result: { success: 成功數(shù)目, failed: 失敗數(shù)目 }
console.log(result);
}
},
components: {
myUpload
}
}
</script>
父組件處理與業(yè)務(wù)有關(guān)的邏輯,我特意加入索引參數(shù),便于界面展示上傳結(jié)果的時(shí)候能夠直接操作第幾個(gè)值,并不是所有方法都必須的,視需求使用。
子組件
<template> <div> <input style="display:none" @change="addFile" :multiple="multiple" type="file" :name="name" id="my-upload"/> </div> </template>
上傳文件,html部分就這么一對兒標(biāo)簽,不喜歡復(fù)雜啰嗦
<script>
export default {
name: 'my-upload',
props: {
name: String,
action: {
type: String,
required: true
},
fileList: {
type: Array,
default: []
},
data: Object,
multiple: Boolean,
limit: Number,
onChange: Function,
onBefore: Function,
onProgress: Function,
onSuccess: Function,
onFailed: Function,
onFinished: Function
},
methods: {}//下文主要是methods的介紹,此處先省略
}
</script>
這里定義了父組件向子組件需要傳遞的屬性值,注意,這里把方法也當(dāng)做了屬性傳遞,都是可以的。
自己寫的組件,沒有像流行框架發(fā)布的那樣完備和全面,另外針對開頭提到的綁定file-list就不能上傳了的問題(更可能是我的姿勢不對),本人也想極力解決掉自身遇到的這個(gè)問題,所以希望能對文件列表有絕對的控制權(quán),除了action,把file-list也作為父組件必須要傳遞的屬性。(屬性名父組件使用“-”連接,對應(yīng)子組件prop中的駝峰命名)
三、主要的上傳功能
methods: {
addFile, remove, submit, checkIfCanUpload
}
methods內(nèi)一共4個(gè)方法,添加文件、移除文件、提交、檢測(上傳之前的檢驗(yàn)),下面一一講述:
1.添加文件
addFile({target: {files}}){//input標(biāo)簽觸發(fā)onchange事件時(shí),將文件加入待上傳列表
for(let i = 0, l = files.length; i < l; i++){
files[i].url = URL.createObjectURL(files[i]);//創(chuàng)建blob地址,不然圖片怎么展示?
files[i].status = 'ready';//開始想給文件一個(gè)字段表示上傳進(jìn)行的步驟的,后面好像也沒去用......
}
let fileList = [...this.fileList];
if(this.multiple){//多選時(shí),文件全部壓如列表末尾
fileList = [...fileList, ...files];
let l = fileList.length;
let limit = this.limit;
if(limit && typeof limit === "number" && Math.ceil(limit) > 0 && l > limit){//有數(shù)目限制時(shí),取后面limit個(gè)文件
limit = Math.ceil(limit);
// limit = limit > 10 ? 10 : limit;
fileList = fileList.slice(l - limit);
}
}else{//單選時(shí),只取最后一個(gè)文件。注意這里沒寫成fileList = files;是因?yàn)閒iles本身就有多個(gè)元素(比如選擇文件時(shí)一下子框了一堆)時(shí),也只要一個(gè)
fileList = [files[0]];
}
this.onChange(fileList);//調(diào)用父組件方法,將列表緩存到上一級(jí)data中的fileList屬性
},
2.移除文件
這個(gè)簡單,有時(shí)候在父組件叉掉某文件的時(shí)候,傳一個(gè)index即可。
remove(index){
let fileList = [...this.fileList];
if(fileList.length){
fileList.splice(index, 1);
this.onChange(fileList);
}
},
3.提交上傳
這里使用了兩種方式,fetch和原生方式,由于fetch不支持獲取上傳的進(jìn)度,如果不需要進(jìn)度條或者自己模擬進(jìn)度或者XMLHttpRequest對象不存在的時(shí)候,使用fetch請求上傳邏輯會(huì)更簡單一些
submit(){
if(this.checkIfCanUpload()){
if(this.onProgress && typeof XMLHttpRequest !== 'undefined')
this.xhrSubmit();
else
this.fetchSubmit();
}
},
4.基于上傳的兩套邏輯,這里封裝了兩個(gè)方法xhrSubmit和fetchSubmit
fetchSubmit
fetchSubmit(){
let keys = Object.keys(this.data), values = Object.values(this.data), action = this.action;
const promises = this.fileList.map(each => {
each.status = "uploading";
let data = new FormData();
data.append(this.name || 'file', each);
keys.forEach((one, index) => data.append(one, values[index]));
return fetch(action, {
method: 'POST',
headers: {
"Content-Type" : "application/x-www-form-urlencoded"
},
body: data
}).then(res => res.text()).then(res => JSON.parse(res));//這里res.text()是根據(jù)返回值類型使用的,應(yīng)該視情況而定
});
Promise.all(promises).then(resArray => {//多線程同時(shí)開始,如果并發(fā)數(shù)有限制,可以使用同步的方式一個(gè)一個(gè)傳,這里不再贅述。
let success = 0, failed = 0;
resArray.forEach((res, index) => {
if(res.code == 1){
success++; //統(tǒng)計(jì)上傳成功的個(gè)數(shù),由索引可以知道哪些成功了
this.onSuccess(index, res);
}else if(res.code == 520){ //約定失敗的返回值是520
failed++; //統(tǒng)計(jì)上傳失敗的個(gè)數(shù),由索引可以知道哪些失敗了
this.onFailed(index, res);
}
});
return { success, failed }; //上傳結(jié)束,將結(jié)果傳遞到下文
}).then(this.onFinished); //把上傳總結(jié)果返回
},
xhrSubmit
xhrSubmit(){
const _this = this;
let options = this.fileList.map((rawFile, index) => ({
file: rawFile,
data: _this.data,
filename: _this.name || "file",
action: _this.action,
onProgress(e){
_this.onProgress(index, e);//閉包,將index存住
},
onSuccess(res){
_this.onSuccess(index, res);
},
onError(err){
_this.onFailed(index, err);
}
}));
let l = this.fileList.length;
let send = async options => {
for(let i = 0; i < l; i++){
await _this.sendRequest(options[i]);//這里用了個(gè)異步方法,按次序執(zhí)行this.sendRequest方法,參數(shù)為文件列表包裝的每個(gè)對象,this.sendRequest下面緊接著介紹
}
};
send(options);
},
這里借鑒了element-ui的上傳源碼
sendRequest(option){
const _this = this;
upload(option);
function getError(action, option, xhr) {
var msg = void 0;
if (xhr.response) {
msg = xhr.status + ' ' + (xhr.response.error || xhr.response);
} else if (xhr.responseText) {
msg = xhr.status + ' ' + xhr.responseText;
} else {
msg = 'fail to post ' + action + ' ' + xhr.status;
}
var err = new Error(msg);
err.status = xhr.status;
err.method = 'post';
err.url = action;
return err;
}
function getBody(xhr) {
var text = xhr.responseText || xhr.response;
if (!text) {
return text;
}
try {
return JSON.parse(text);
} catch (e) {
return text;
}
}
function upload(option) {
if (typeof XMLHttpRequest === 'undefined') {
return;
}
var xhr = new XMLHttpRequest();
var action = option.action;
if (xhr.upload) {
xhr.upload.onprogress = function progress(e) {
if (e.total > 0) {
e.percent = e.loaded / e.total * 100;
}
option.onProgress(e);
};
}
var formData = new FormData();
if (option.data) {
Object.keys(option.data).map(function (key) {
formData.append(key, option.data[key]);
});
}
formData.append(option.filename, option.file);
xhr.onerror = function error(e) {
option.onError(e);
};
xhr.onload = function onload() {
if (xhr.status < 200 || xhr.status >= 300) {
return option.onError(getError(action, option, xhr));
}
option.onSuccess(getBody(xhr));
};
xhr.open('post', action, true);
if (option.withCredentials && 'withCredentials' in xhr) {
xhr.withCredentials = true;
}
var headers = option.headers || {};
for (var item in headers) {
if (headers.hasOwnProperty(item) && headers[item] !== null) {
xhr.setRequestHeader(item, headers[item]);
}
}
xhr.send(formData);
return xhr;
}
}
最后把請求前的校驗(yàn)加上
checkIfCanUpload(){
return this.fileList.length ? (this.onBefore && this.onBefore() || !this.onBefore) : false;
},
如果父組件定義了onBefore方法且返回了false,或者文件列表為空,請求就不會(huì)發(fā)送。
代碼部分完了,使用時(shí)只要有了on-progress屬性并且XMLHttpRequest對象可訪問,就會(huì)使用原生方式發(fā)送請求,否則就用fetch發(fā)送請求(不展示進(jìn)度)。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Vue如何實(shí)現(xiàn)文件預(yù)覽和下載功能的前端上傳組件
- Vue框架+Element-ui(el-upload組件)使用http-request方法上傳文件并顯示文件上傳進(jìn)度功能
- 利用Vue3+Element-plus實(shí)現(xiàn)大文件分片上傳組件
- vue2中基于vue-simple-upload實(shí)現(xiàn)文件分片上傳組件功能
- vue3.0搭配.net core實(shí)現(xiàn)文件上傳組件
- Vue開發(fā)之封裝上傳文件組件與用法示例
- vue webuploader 文件上傳組件開發(fā)
- Vue 中自定義文件上傳組件的實(shí)現(xiàn)代碼
相關(guān)文章
創(chuàng)建vue項(xiàng)目沒有router、view的解決辦法
在學(xué)習(xí)vue的時(shí)候遇到很多問題,這里做一些總結(jié),下面這篇文章主要給大家介紹了關(guān)于創(chuàng)建vue項(xiàng)目沒有router、view文件夾的解決辦法,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11
Vue與.net?Core?接收List<T>泛型參數(shù)
這篇文章主要介紹了Vue與.net?Core?接收List<T>泛型參數(shù),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-04-04
Vue?Echarts實(shí)現(xiàn)帶滾動(dòng)效果的柱形圖
這篇文章主要為大家詳細(xì)介紹了Vue?Echarts實(shí)現(xiàn)帶滾動(dòng)效果的柱形圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
vue tab切換,解決echartst圖表寬度只有100px的問題
這篇文章主要介紹了vue tab切換,解決echartst圖表寬度只有100px的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07
Vue使用axios進(jìn)行數(shù)據(jù)異步交互的方法
大家都知道在Vue里面有兩種出名的插件能夠支持發(fā)起異步數(shù)據(jù)傳輸和接口交互,分別是axios和vue-resource,同時(shí)vue更新到2.0之后,宣告不再對vue-resource更新,而是推薦的axios,今天就講一下怎么引入axios,需要的朋友可以參考下2024-01-01
基于el-table實(shí)現(xiàn)行內(nèi)增刪改功能
這篇文章主要介紹了基于el-table實(shí)現(xiàn)行內(nèi)增刪改功能,用過通過操作按鈕點(diǎn)擊刪除或者編輯功能即可實(shí)現(xiàn)相應(yīng)的效果,下面小編給大家分享實(shí)例代碼感興趣的朋友跟隨小編一起看看吧2024-04-04

