封裝一個Vue文件上傳組件案例詳情
前言
在面向特定用戶的項目中,引 其他ui組件庫導(dǎo)致打包體積過大,首屏加載緩慢,還需要根據(jù)UI設(shè)計師設(shè)計的樣式,重寫大量的樣式覆蓋引入的組件庫的樣式。因此嘗試自己封裝一個自己的組件,代碼參考了好多前輩的文章
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事件時,將文件加入待上傳列表 for (let i = 0, l = files.length; i < l; i++) { files[i].url = URL.createObjectURL(files[i])// 創(chuàng)建blob地址,不然圖片怎么展示? files[i].status = 'ready'// 開始想給文件一個字段表示上傳進行的步驟的,后面好像也沒去用...... } let fileList = [...this.fileList] if (this.multiple) { // 多選時,文件全部壓如列表末尾 fileList = [...fileList, ...files] const l = fileList.length let limit = this.limit if (limit && typeof limit === 'number' && Math.ceil(limit) > 0 && l > limit) { // 有數(shù)目限制時,取后面limit個文件 limit = Math.ceil(limit) // limit = limit > 10 ? 10 : limit; fileList = fileList.slice(l - limit) } } else { // 單選時,只取最后一個文件。注意這里沒寫成fileList = files;是因為files本身就有多個元素(比如選擇文件時一下子框了一堆)時,也只要一個 fileList = [files[0]] } this.onChange(fileList)// 調(diào)用父組件方法,將列表緩存到上一級data中的fileList屬性 }, // 移除某一個文件 remove (index) { const fileList = [...this.fileList] if (fileList.length) { fileList.splice(index, 1) this.onChange(fileList) } }, // 檢測是否可以提交 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 => { // 多線程同時開始,如果并發(fā)數(shù)有限制,可以使用同步的方式一個一個傳,這里不再贅述。 let success = 0; let failed = 0 resArray.forEach((res, index) => { if (res.code === 1) { success++ // 統(tǒng)計上傳成功的個數(shù),由索引可以知道哪些成功了 this.onSuccess(index, res) } else if (res.code === 520) { // 約定失敗的返回值是520 failed++ // 統(tǒng)計上傳失敗的個數(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) { // ????????這里補充一個配置 // _this.onFinished(res) // ???????? // } // })) // const l = this.fileList.length // const send = async options => { // for (let i = 0; i < l; i++) { // await _this.sendRequest(options[i])// 這里用了個異步方法,按次序執(zhí)行this.sendRequest方法,參數(shù)為文件列表包裝的每個對象,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) { // ????????這里補充一個配置 _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) { // ????????這里補充一個配置 // _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])// 這里用了個異步方法,按次序執(zhí)行this.sendRequest方法,參數(shù)為文件列表包裝的每個對象,this.sendRequest下面緊接著介紹 // } // } // send(options) }, // 發(fā)起上傳請求這里借鑒了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> <!-- 自己封裝的手動上傳文件的組件 --> <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)聽文件變化,增減文件時都會被子組件調(diào)用 console.log(fileList) this.fileList = [...fileList] }, uploadSuccess (index, response) { // 某個文件上傳成功都會執(zhí)行該方法,index代表列表中第index個文件 console.log('圖片上傳成功') console.log(index, response) this.submitWorksCover() }, upload () { // 觸發(fā)子組件的上傳方法 this.$refs.myUpload.submit() }, removeFile (index) { // 移除某文件 this.$refs.myUpload.remove(index) }, uploadProgress (index, progress) { // 上傳進度,上傳時會不斷被觸發(fā),需要進度指示時會很有用 const { percent } = progress console.log(index, percent) }, uploadFailed (index, err) { // 某文件上傳失敗會執(zhí)行,index代表列表中第index個文件 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é)
前端項目如果不是中臺項目,自己封裝組件是非常有比較的,這里比較麻煩的地方是 父組件引用需要些大量的方法,您也可以再封
到此這篇關(guān)于 封裝一個Vue文件上傳組件案例詳情的文章就介紹到這了,更多相關(guān)Vue 封裝文件上傳組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3+vite+ts使用monaco-editor編輯器的簡單步驟
因為畢設(shè)需要用到代碼編輯器,根據(jù)調(diào)研,我選擇使用monaco-editor代碼編輯器,下面這篇文章主要給大家介紹了關(guān)于vue3+vite+ts使用monaco-editor編輯器的簡單步驟,需要的朋友可以參考下2023-01-01Vue實現(xiàn)實時更新sessionStorage數(shù)據(jù)的示例代碼
這篇文章主要為大家詳細介紹了Vue如何實現(xiàn)實時更新sessionStorage數(shù)據(jù),文中的示例代碼講解詳細,具有一定的參考價值,需要的可以參考一下2023-06-06淺析vue-cli3配置webpack-bundle-analyzer插件【推薦】
小編最近為了優(yōu)化vue項目性能,需要使用webpack-bundle-analyzer插件來分析報文件,在網(wǎng)上沒有找到合適的,下面小編給大家寫出來一個供大家參考2019-10-10Vue-router中hash模式與history模式的區(qū)別詳解
這篇文章主要給大家介紹了關(guān)于Vue-router中hash模式與history模式區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Vue.js實現(xiàn)簡單動態(tài)數(shù)據(jù)處理
本篇文章主要介紹了Vue.js實現(xiàn)簡單動態(tài)數(shù)據(jù)處理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02