基于element 圖片上傳封裝

我們用過element ui文件上傳的朋友都知道,它屬性齊全,api強大,但是我們在好多后臺系統(tǒng)上傳的時候發(fā)現(xiàn),每一個上傳我們都需要重新配置一遍,action,headers,data,name,以及上傳前校驗,上傳個數(shù)等等。煩不勝煩。有沒有辦法就寫一個圖片上傳,一個上傳方法,其他的通過簡單地配置就能實現(xiàn)上傳呢,答案當然是有的。今天就帶大家來實現(xiàn)一個單圖,多圖上傳封裝,其中還包含了圖片預(yù)覽,格式校驗,以及圖片刪除,數(shù)據(jù)返回類型等操作,滿足你的基本要求。本次封裝不包含裁剪以及視頻和文件上傳。這些功能在接下來會逐步完成,希望大家喜歡,先上圖!


QQ截圖20201112112951.png

我們成功的上傳了一張圖片,我們再來看一下上傳成功后的控制臺顯示的源文件內(nèi)容


QQ截圖20201112113151.png

我們發(fā)現(xiàn),先是第一行和最后一行的WebKitFormBoundary 碼,第二行的ContentDisposition,該行包含一些文件基本信息,還有第三行文件內(nèi)容類型,后端的同志通過我們傳遞的文件數(shù)據(jù)規(guī)則,來進行文件解析,
傳遞的數(shù)據(jù)規(guī)則里包含所傳遞文件的基本信息 ,如文件名與文件類型,以便后端寫出正確格式的文件。

到這里,我們明白了大致的上傳原理。接下來,我們來訂制一下專屬我們的上傳組件流程。

首先,我們使用的是axios,不需要像ajax一樣自己去通過FormData實例化一個文件fd插入到文件內(nèi)容中去,我們只需要在element提供的文件上傳headers屬性中添加進去'ContentType': 'multipart/form-data'就可以實現(xiàn)上傳formdata編碼類型的傳輸了。至于為什么要用formdata類型,涉及到二進制文件編碼云云,我也是半知半解,想詳細了解的同學(xué)可以自行查閱,這里不多說。
然后我們就開始寫組件的屬性,把常用的upload屬性都拿過來

props:{
    value:{},
    //action是上傳的地址
    action:{
        type:String,
        default: process.env.VUE_APP_BASE_API + process.env.VUE_APP_MY_UPLOAD_URL  || ''
    },
    //最大允許上傳個數(shù)
    limit: {
        type: Number,
        default: 20
    },
    //是否采用頭像上傳模式
    avatar:{
        type:Boolean,
        default:false,
    },
    //根據(jù)業(yè)務(wù)需要,設(shè)置是否返回給父組件字符串類型
    //或者直接返回數(shù)組
    isString:{
        type:Boolean,
        default:false,
    },
    //是否顯示已上傳文件列表,如果要換成頭像上傳則設(shè)置成false
    //并且把avatar屬性設(shè)置成true
    showFileList: {
        type: Boolean,
        default: true
    },
    //設(shè)置限制上傳圖片的大小
    size: {
        type: Number,
        default: 2
    },
    //上傳時附帶的額外字段,看你們后端是否要求,沒有則不設(shè)置
    dataObj: {
        type: Object,
        default:()=> {
            return {type:5}//這個沒有就默認空
        }
    },
    //上傳的文件字段名,后端要求的字段名
    name: {
        type: String,
        default: 'files'
    },
    //文件列表的類型,text/picture/picture-card
    listType: {
        type: String,
        default: 'picture-card'
    },
},

這里屬性定義完了,我們來重點說一下這個element上傳的一個坑點。他的file-list也就是文件上傳列表,將來我們用作多圖照片墻回顯的地方,需要按照它的格式來才行,否則你會發(fā)現(xiàn),渲染不出來。好多的ui組件都是這個模式,移動端的vant組件,需要一個叫image:true的屬性,一個叫url的屬性。我們得element則是需要[{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]這樣的屬性。
所以我們在檢測父組件傳遞過來的value時候,要把原來的['1','2','3']字符串list變成對象的list,即示例要求的圖片數(shù)組形式。
還有就是我們在監(jiān)測父組件傳遞過來的圖片數(shù)據(jù)時,要把它傳遞過來的數(shù)據(jù)添加到上傳圖片成功后的通知變量-------emitList里面,這樣我們才能在有父組件傳遞變量的情況下,我們再接著上傳時不會清空或者覆蓋父組件傳遞過來的數(shù)據(jù)。

//文件上傳成功時的鉤子
handleSuccess(file,fileInfo){
    //定義一個上傳成功的變量,用來暫停監(jiān)聽父組件的數(shù)據(jù),
    //因為我們還沒有通知父組件接受參數(shù)
    this.changeFlag = false;
    this.loading = true;
    let item = file.data[0];
    //頭像上傳(不帶預(yù)覽)
    if(this.avatar){
        this.imageUrl = item;
        this.$emit('input', item);
    }else{
        //多圖上傳(帶預(yù)覽)
        this.emitList.push(item);
        let val = [...this.emitList];
        //如果需要回傳的是字符串類型,則轉(zhuǎn)字符串回傳父組件
        if (this.isString) {
            val = val.join(',')
        }
        this.$emit('input', val);
    }
    this.loading = false;
},

//對父組件傳遞過來的數(shù)據(jù)進行監(jiān)聽
watch:{
    value:{
        immediate:true,   
        handler:function(val){
            //this.changeFlag利用這個變量,讓我們在上傳的時候暫停掉對父組件傳遞數(shù)據(jù)的監(jiān)聽
            //因為這個時候我們還沒有通知父組件去接受我們上傳成功后的圖片路徑
            if(this.changeFlag){
                //判斷是否是頭像上傳,
                if(!this.avatar && val){
                    //判斷是否傳遞過來的數(shù)據(jù)是字符串,是字符串轉(zhuǎn)為數(shù)組;有的業(yè)務(wù)需要如此
                    //如果是字符串有可能為空
                    if (this.isString) {
                        val = val.split(',')
                    }
                    //把父組件傳遞過來的圖片數(shù)組添加到上傳成功后的暫存變量中,
                    //再次上傳后重新給父組件,更新圖片綁定數(shù)據(jù)
                    this.emitList = [...val];
                    //循環(huán)遍歷父組件數(shù)據(jù),重新賦值成element組件所需要的回顯數(shù)據(jù)
                    val.forEach(item=>{
                        let obj = {
                            uid: item.uid,
                            name: item.name,
                            url: item
                        }
                        this.fileList.push(obj)
                    })
                    //如果是頭像上傳則直接賦值給圖片路徑即可
                }else{
                    this.imageUrl = val;
                }
                
            }
            
        }
    },
},

基本核心代碼全部寫完,我們的上傳回顯就成功了。接下來我們再來完成刪除和大圖預(yù)覽,我們的整個上傳組件就完美寫出來了

//我們先來寫刪除
//文件列表移除文件時的鉤子
handleRemove(file, fileList) {
    //同樣的暫停掉監(jiān)聽父組件數(shù)據(jù),以免產(chǎn)生圖片展示bug
    this.changeFlag = false;
     //獲取要刪除的圖片路徑。判斷是父組件傳遞的圖片圖片路徑還是剛剛上傳產(chǎn)生的圖片路徑
    const url = file.response ? file.response.data[0] : file.url;
    //遍歷當前圖片路徑在通知給父組件的數(shù)據(jù)中是哪一個。然后刪除掉。再通知父組件更新數(shù)據(jù)
    this.emitList.forEach((item, index) => {
        if (item.indexOf(url) > -1) {
            this.emitList.splice(index, 1)
            let val = [...this.emitList];
            //判斷回傳父組件是否是字符串類型
            if (this.isString) {
                val = val.join(',')
            }
            this.$emit('input', val);
        }
    })
},

我們打印一下刪除時候的鉤子參數(shù),file和fileList,發(fā)現(xiàn)刪除的時候,我們的父組件傳遞過來的圖片兒數(shù)據(jù)和上傳之后的圖片數(shù)據(jù),返回值是不太一致的
刪除原有的父組件傳遞過來的圖片時,file參數(shù)為:


QQ截圖20201113084139.png

如果是剛上傳后再刪除當前上傳的圖片,file參數(shù)為:


QQ截圖20201113085543.png

我們發(fā)現(xiàn)多了response參數(shù),在這里我們才能取到圖片的url,而不是下面最外層的blob本地url。
我們刪除的基本思路是找到我們當前要刪除的圖片路徑去對比我們要通知父組件更新的路徑數(shù)據(jù),兩者的交集則是我們要在emitList中刪除的。這樣一個刪除就完成了。接下來我們再來完成預(yù)覽縮略圖大圖:
//點擊文件列表中已上傳的文件時的鉤子
handlePreview(file) {
    if (this.showFileList && this.listType == 'picture-card') {
        this.imgSpreadUrl = file.url
        this.imgSpreadVisible = true
    }
},
//預(yù)覽沒什么好說的,打開dialog,然后給彈出層圖片一個路徑就好了,這個大家都懂,哈哈。

然后我們加上兩個小判斷,一個是應(yīng)用頭像模式,一個是回傳字符串來滿足服務(wù)端可能需要你的多圖上傳數(shù)據(jù)是字符串類型的要求。
最后給大家看一下效果圖


QQ截圖20201113102512.png

好了,到這里我們的圖片上傳功能就做好了。最后給大家貼一組件代碼和使用方式,希望對你有幫助的朋友能給點個贊,十分感謝?。。。。。?!

//上傳圖片組件
<template>
  <div>
        <el-upload
            v-loading="loading"
          class="upload-demo"
          :action="action"
            :limit="limit"
            :show-file-list="showFileList"
            :file-list="fileList"
            :data="dataObj"
            :name="name"
            :headers="headers"
            :list-type="listType"
            :on-success="handleSuccess"
            :on-change="handleChange"
            :on-exceed="handleExceed"
          :on-preview="handlePreview"
          :on-remove="handleRemove"
            :before-upload="beforeUpload">
          <div v-if="!avatar">
                <i slot="default" class="el-icon-plus"></i>
            </div>
            <div v-else>
                <img v-if="imageUrl" :src="imageUrl" class="avatar">
                <i v-else class="el-icon-plus avatar-uploader-icon"></i>
            </div>
        </el-upload>
        <el-dialog width="800px" :visible.sync="imgSpreadVisible" append-to-body>
          <img class="spread-image" :src="imgSpreadUrl">
        </el-dialog>
    </div>
</template>

<script>
//引入token,一般上傳圖片接口可能需要token
import { getToken } from '@/utils/auth'
export default {
props:{
    value:{},
    //action是上傳的地址
    action:{
        type:String,
        default: process.env.VUE_APP_BASE_API + process.env.VUE_APP_MY_UPLOAD_URL  || ''
    },
    //最大允許上傳個數(shù)
    limit: {
        type: Number,
        default: 20
    },
    //是否采用頭像上傳模式
    avatar:{
        type:Boolean,
        default:false,
    },
    //根據(jù)業(yè)務(wù)需要,設(shè)置是否返回給父組件字符串類型
    //或者直接返回數(shù)組
    isString:{
        type:Boolean,
        default:false,
    },
    //是否顯示已上傳文件列表,如果要換成頭像上傳則設(shè)置成false
    //并且把avatar屬性設(shè)置成true
    showFileList: {
        type: Boolean,
        default: true
    },
    //設(shè)置限制上傳圖片的大小
    size: {
        type: Number,
        default: 2
    },
    //上傳時附帶的額外字段,看你們后端是否要求,沒有則不設(shè)置
    dataObj: {
        type: Object,
        default:()=> {
            return {type:5}//這個沒有就默認空
        }
    },
    //上傳的文件字段名,后端要求的字段名
    name: {
        type: String,
        default: 'files'
    },
    //文件列表的類型,text/picture/picture-card
    listType: {
        type: String,
        default: 'picture-card'
    },
},
    watch:{
        value:{
            immediate:true,   
            handler:function(val){
                //this.changeFlag利用這個變量,讓我們在上傳的時候暫停掉對父組件傳遞數(shù)據(jù)的監(jiān)聽
                //因為這個時候我們還沒有通知父組件去接受我們上傳成功后的圖片路徑
                if(this.changeFlag){
                    //判斷是否是頭像上傳,
                    if(!this.avatar && val){
                        //判斷是否傳遞過來的數(shù)據(jù)是字符串,是字符串轉(zhuǎn)為數(shù)組;有的業(yè)務(wù)需要如此
                        //如果是字符串有可能為空
                        if (this.isString) {
                          val = val.split(',')
                        }
                        //把父組件傳遞過來的圖片數(shù)組添加到上傳成功后的暫存變量中,
                        //再次上傳后重新給父組件,更新圖片綁定數(shù)據(jù)
                        this.emitList = [...val];
                        //循環(huán)遍歷父組件數(shù)據(jù),重新賦值成element組件所需要的回顯數(shù)據(jù)
                        val.forEach(item=>{
                            let obj = {
                                uid: item.uid,
                                name: item.name,
                                url: item
                            }
                            this.fileList.push(obj)
                        })
                        //如果是頭像上傳則直接賦值給圖片路徑即可
                    }else{
                        this.imageUrl = val;
                    }
                    
                }
                
            }
        },
    },
    computed: {
      // 設(shè)置上傳的請求頭部
      headers() {
        return {
          'ContentType': 'multipart/form-data',// 設(shè)置Content-Type類型為multipart/form-data
          'token': getToken()// 設(shè)置token
        }
      }
    },
  data() {
    return {
            fileList:[],//element自帶的圖片list
            emitList:[],//回顯的圖片list
            imgSpreadVisible:false,//查看縮略圖的彈窗狀態(tài)
            imgSpreadUrl:'',//縮略圖大圖圖片路徑
            loading:false,//加載狀態(tài)
            changeFlag:true,
            imageUrl:''
    }
  },
  methods: {
        //上傳前校驗一下圖片大小
        beforeUpload(file){
            const isLtSize = file.size / 1024 / 1024 < this.size
            if (!isLtSize) {
              this.$message.error('文件大小不能超過 ' + this.size + 'M')
            }
            return isLtSize
        },
        //文件上傳成功時的鉤子
        handleSuccess(file,fileInfo){
            //定義一個上傳成功的變量,用來暫停監(jiān)聽父組件的數(shù)據(jù),
            //因為我們還沒有通知父組件接受參數(shù)
            this.changeFlag = false;
            this.loading = true;
            let item = file.data[0];
            //頭像上傳(不帶預(yù)覽)
            if(this.avatar){
                this.imageUrl = item;
                this.$emit('input', item);
            }else{
                //多圖上傳(帶預(yù)覽)
                this.emitList.push(item);
                let val = [...this.emitList];
                //如果需要回傳的是字符串類型,則轉(zhuǎn)字符串回傳父組件
                if (this.isString) {
                  val = val.join(',')
                }
                this.$emit('input', val);
            }
            this.loading = false;
        },
        //文件狀態(tài)改變時的鉤子
        handleChange(){
            if (this.loading) {
                //用于圖片校驗
              this.$emit('change', true)
            }
        },
        //文件列表移除文件時的鉤子
        handleRemove(file, fileList) {
            //同樣的暫停掉監(jiān)聽父組件數(shù)據(jù),以免產(chǎn)生圖片展示bug
            this.changeFlag = false;
             //獲取要刪除的圖片路徑。判斷是父組件傳遞的圖片圖片路徑還是剛剛上傳產(chǎn)生的圖片路徑
            const url = file.response ? file.response.data[0] : file.url;
            //遍歷當前圖片路徑在通知給父組件的數(shù)據(jù)中是哪一個。然后刪除掉。再通知父組件更新數(shù)據(jù)
            this.emitList.forEach((item, index) => {
              if (item.indexOf(url) > -1) {
                this.emitList.splice(index, 1)
                let val = [...this.emitList];
                    //判斷回傳父組件是否是字符串類型
                    if (this.isString) {
                      val = val.join(',')
                    }
                this.$emit('input', val);
              }
            })
        },
        //點擊文件列表中已上傳的文件時的鉤子
        handlePreview(file) {
            if (this.showFileList && this.listType == 'picture-card') {
                this.imgSpreadUrl = file.url
                this.imgSpreadVisible = true
            }
        },
        //文件超出個數(shù)限制時的鉤子
        handleExceed(files, fileList) {
            this.$message.error('最多上傳' + this.limit + '個文件')
        },
  }
}
</script>

<style scoped>
    .spread-image{display: block;max-width: 100%;max-height: 500px;margin: auto;}
    .avatar{width: 100%;height: auto;}
</style>

//頁面使用
<template>
  <div class="container">
        <!-- 數(shù)組類型的多圖上傳 -->
        <upload-image v-model="arrayImage"></upload-image>
        <!-- 字符串類型的多圖上傳 -->
        <upload-image v-model="stringImage" :isString="true" ></upload-image>
        <!-- 頭像類型的上傳 -->
        <upload-image v-model="avatarValue":avatar="true" :showFileList="false"></upload-image>
    </div>
</template>

<script>
import uploadImage from './components/upload.vue'
export default {
    components:{
        uploadImage
    },
  data() {
    return {
      arrayImage:[],//數(shù)組類型的多圖上傳
      stringImage:'https://gcjf.guochengjinfu.cn/20201112/textProPic/1e6a8d9a94504af2b2f0d0136987de31.png',
      //字符串類型多圖上傳
      avatarValue:'',//頭像上傳
    }
  }
}
</script>

希望對朋友們有幫助,最后的最后~~~~~~~~~給點個贊吧!謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容