封裝一個(gè)Vue文件上傳組件案例詳情
前言
在面向特定用戶的項(xiàng)目中,引 其他ui組件庫導(dǎo)致打包體積過大,首屏加載緩慢,還需要根據(jù)UI設(shè)計(jì)師設(shè)計(jì)的樣式,重寫大量的樣式覆蓋引入的組件庫的樣式。因此嘗試自己封裝一個(gè)自己的組件,代碼參考了好多前輩的文章
1. 子組件
<template>
<div class="digital_upload">
<input
style="display: none"
@change="addFile"
:multiple="multiple"
type="file"
:name="name"
:id="id"
:accept="accept"
/>
<label :for="id">
<slot></slot>
</label>
</div>
</template>
<script>
export default {
name: 'my-upload',
props: {
name: String,
action: {
type: String,
required: true
},
fileList: {
type: Array,
default () {
return []
}
},
accept: {
type: String,
require: true
},
id: {
type: String,
default: 'my-upload'
},
data: Object,
multiple: Boolean,
limit: Number,
onChange: Function,
onBefore: Function,
onProgress: Function,
onSuccess: Function,
onFailed: Function,
onFinished: Function
},
methods: {
// input的 chang事件處理方法
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]
const 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屬性
},
// 移除某一個(gè)文件
remove (index) {
const fileList = [...this.fileList]
if (fileList.length) {
fileList.splice(index, 1)
this.onChange(fileList)
}
},
// 檢測(cè)是否可以提交
checkIfCanUpload () {
console.log(this.fileList.length)
return this.fileList.length ? ((this.onBefore && this.onBefore()) || !this.onBefore) : false
},
// 根據(jù)情況使用不同的提交的方法
submit () {
console.log('開始提交')
if (this.checkIfCanUpload()) {
// console.log('開始提交2')
if (this.onProgress && typeof XMLHttpRequest !== 'undefined') {
this.xhrSubmit()
} else {
this.fetchSubmit()
}
}
},
// fethc 提交
fetchSubmit () {
const keys = Object.keys(this.data); const values = Object.values(this.data); const action = this.action
const promises = this.fileList.map(each => {
each.status = 'uploading'
const 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; let 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é)果返回
},
// xhr 提交
// xhrSubmit () {
// const _this = this
// const options = this.fileList.map((rawFile, index) => ({
// file: rawFile,
// data: _this.data,
// filename: _this.name || 'file',
// action: _this.action,
// headers: {
// Authorization: window.sessionStorage.getItem('token')
// },
// onProgress (e) {
// _this.onProgress(index, e)// 閉包,將index存住
// },
// onSuccess (res) {
// _this.onSuccess(index, res)
// },
// onError (err) {
// _this.onFailed(index, err)
// },
// onFinished (res) { // ????????這里補(bǔ)充一個(gè)配置
// _this.onFinished(res) // ????????
// }
// }))
// const l = this.fileList.length
// const send = async options => {
// for (let i = 0; i < l; i++) {
// await _this.sendRequest(options[i])// 這里用了個(gè)異步方法,按次序執(zhí)行this.sendRequest方法,參數(shù)為文件列表包裝的每個(gè)對(duì)象,this.sendRequest下面緊接著介紹
// }
// }
// send(options)
// },
xhrSubmit () {
const _this = this
const options = {
file: this.fileList,
data: _this.data,
filename: _this.name || 'file',
action: _this.action,
headers: {
Authorization: window.sessionStorage.getItem('token')
},
onProgress (e) {
_this.onProgress(1, e)// 閉包,將index存住
},
onSuccess (res) {
_this.onSuccess(1, res)
},
onError (err) {
_this.onFailed(1, err)
},
onFinished (res) { // ????????這里補(bǔ)充一個(gè)配置
_this.onFinished(res) // ????????
}
}
// this.fileList.map((rawFile, index) => ({
// file: rawFile,
// data: _this.data,
// filename: _this.name || 'file',
// action: _this.action,
// headers: {
// Authorization: window.sessionStorage.getItem('token')
// },
// onProgress (e) {
// _this.onProgress(index, e)// 閉包,將index存住
// },
// onSuccess (res) {
// _this.onSuccess(index, res)
// },
// onError (err) {
// _this.onFailed(index, err)
// },
// onFinished (res) { // ????????這里補(bǔ)充一個(gè)配置
// _this.onFinished(res) // ????????
// }
// }))
console.log(options)
_this.sendRequest(options)
// const l = this.fileList.length
// const send = async options => {
// for (let i = 0; i < l; i++) {
// await _this.sendRequest(options[i])// 這里用了個(gè)異步方法,按次序執(zhí)行this.sendRequest方法,參數(shù)為文件列表包裝的每個(gè)對(duì)象,this.sendRequest下面緊接著介紹
// }
// }
// send(options)
},
// 發(fā)起上傳請(qǐng)求這里借鑒了element-ui的上傳源碼
sendRequest (option) {
// const _this = this
upload(option)
function getError (action, option, xhr) {
// eslint-disable-next-line no-void
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])
})
}
option.file.forEach(item => {
formData.append(option.filename, item)
})
// 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) {
// eslint-disable-next-line no-prototype-builtins
if (headers.hasOwnProperty(item) && headers[item] !== null) {
xhr.setRequestHeader(item, headers[item])
}
}
xhr.send(formData)
return xhr
}
}
}
}
</script>
<style lang="less" scoped>
</style>2 父組件使用
<template>
<div id="upload_works">
<div class="type_info" >
<div class="info">支持JPG,PNG,GIF格式,大小不超過20M</div>
<!-- 自己封裝的手動(dòng)上傳文件的組件 -->
<digital-upload
accept="image/*"
ref="myUpload"
action="/api/production/uploadImgList"
name="files"
id="my-upload"
multiple
:limit="10"
:file-list="fileList"
:data="param"
:on-change="onChange"
:on-progress="uploadProgress"
:on-success="uploadSuccess"
:on-failed="uploadFailed"
:on-finished="onFinished"
>
<div class="normal_button">上傳文件</div>
</digital-upload>
<div class="three">或拖放到這里</div>
</div>
</div>
</template>
<script>
import digitalUpload from '@/digitalComponents/upload/digitalUpload.vue'
export default {
data () {
return {
fileList: [],
param: { id: null }
}
},
methods: {
onChange (fileList) { // 監(jiān)聽文件變化,增減文件時(shí)都會(huì)被子組件調(diào)用
console.log(fileList)
this.fileList = [...fileList]
},
uploadSuccess (index, response) { // 某個(gè)文件上傳成功都會(huì)執(zhí)行該方法,index代表列表中第index個(gè)文件
console.log('圖片上傳成功')
console.log(index, response)
this.submitWorksCover()
},
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: {
digitalUpload
}
}
</script>
<style lang="less" scoped>
</style>3.效果

4.總結(jié)
前端項(xiàng)目如果不是中臺(tái)項(xiàng)目,自己封裝組件是非常有比較的,這里比較麻煩的地方是 父組件引用需要些大量的方法,您也可以再封
到此這篇關(guān)于 封裝一個(gè)Vue文件上傳組件案例詳情的文章就介紹到這了,更多相關(guān)Vue 封裝文件上傳組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3+vite+ts使用monaco-editor編輯器的簡(jiǎn)單步驟
因?yàn)楫呍O(shè)需要用到代碼編輯器,根據(jù)調(diào)研,我選擇使用monaco-editor代碼編輯器,下面這篇文章主要給大家介紹了關(guān)于vue3+vite+ts使用monaco-editor編輯器的簡(jiǎn)單步驟,需要的朋友可以參考下2023-01-01
Vue實(shí)現(xiàn)實(shí)時(shí)更新sessionStorage數(shù)據(jù)的示例代碼
這篇文章主要為大家詳細(xì)介紹了Vue如何實(shí)現(xiàn)實(shí)時(shí)更新sessionStorage數(shù)據(jù),文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,需要的可以參考一下2023-06-06
淺析vue-cli3配置webpack-bundle-analyzer插件【推薦】
小編最近為了優(yōu)化vue項(xiàng)目性能,需要使用webpack-bundle-analyzer插件來分析報(bào)文件,在網(wǎng)上沒有找到合適的,下面小編給大家寫出來一個(gè)供大家參考2019-10-10
Vue-router中hash模式與history模式的區(qū)別詳解
這篇文章主要給大家介紹了關(guān)于Vue-router中hash模式與history模式區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Vue.js實(shí)現(xiàn)簡(jiǎn)單動(dòng)態(tài)數(shù)據(jù)處理
本篇文章主要介紹了Vue.js實(shí)現(xiàn)簡(jiǎn)單動(dòng)態(tài)數(shù)據(jù)處理,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
vue實(shí)現(xiàn)商品購(gòu)物車全選反選
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)商品購(gòu)物車全選反選,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04

