一、写在前面

最近的项目中用到了element的upload组件,有这样的一个需求,需要一次性上传多张图片或者一次性上传多个文件,虽然element官网的组件提供了这样的一个上传组件,也有多选文件的属性【multiple 是否支持多选文件 boolean】,但是会有一个问题,此组件默认的自动上传是使用的单文件上传接口,多次调用进行上传,即 3张图片就会调用3次上传的接口,然后on-success方法也会调用3次,就不满足业务场景了。相信你看到这篇文章的时候,肯定已经找了好几个解决方案了,很多都是使用的手动上传,假如需求是自动上传呢,那么就不是满足不了需求了吗?
element官网的栗子

二、方案分析

<template>
    <el-upload
        :ref="`Uploader-${uploadId}`"
        v-loading="loading"
        action="upload"
        :style="style"
        :auto-upload="false"
        list-type="picture-card"
        :name="uploadId"
        accept="image/*"
        multiple
        :file-list="fileList"
        :before-upload="handleBeforeUpload"
        :http-request="httpRequest"
        :on-change="handleChange"
        :on-remove="handleRemove"
    >
        <i class="el-icon-plus" />
    </el-upload>
</template>

先看一下上面的组件代码,相信也是和网上大部分的都大同小异了。大概也是三步

1、关闭原生的自动上传属性 【:auto-upload=“false”】;

2、使用【:on-change=“handleChange”】回调的钩子;

3、自定义【:http-request=“httpRequest”】上传,覆盖默认的上传方法;

4、手动触发 Upload.submit() 上传文件列表的方法。

三、废话少说,直接开撸

到这里了呢也不墨迹了,直接上代码吧,想看看思路的可以看一下这段话,不想看的呢就直接复制后面的完整组件代码到你本地的组件中就可以了。
我的思路如下

  1. 如此在这里插入图片描述

  2. 将el-upload的name属性设置一个唯一的标识符【:name=“uploadId”】(因为一个页面上可能有多个上传组件,必须要一个唯一的标识符来区分,不然会大水冲了龙王庙的)。

  3. 在methods中写【:on-change=“handleChange”】的 handleChange方法,文件状态改变回调,官方的解释如下 (文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用) ,所以这个方法要进行状态的判断【status == ‘ready’】 就是将文件添加进来的状态。

/**
  * 文件上传change
  */
 handleChange(file, fileList) {
     //获取添加文件进来的状态
     (file.status == 'ready') && this.uploadFiles.push(file.raw);
     //获取原始文件的个数
     this.fileTotal = document.getElementsByName(this.uploadId)[0].files.length;
     //如果原始文件和upload的个数相同的时候就说明已经添加完了,可以触发上传的方法了
     if (this.uploadFiles.length === this.fileTotal) {
         //获取上传文件的组件
         const Uploader = this.$refs[`Uploader-${this.uploadId}`];
         //触发上传文件列表的方法
         Uploader.submit();
     }
 }
  1. 获取用户选择的文件总数,刚刚写的name就可以派上用场了。代码如此,因为name是唯一的,所以页面就算有多个上传文件的组件,找到的document对象列表也只有一个。
//获取原始文件的个数
     this.fileTotal = document.getElementsByName(this.uploadId)[0].files.length;
  1. 到这了,能拿到用户选择文件的总数和添加到upload组件的文件总数就可以进行判断了,如果原始文件(input[type=“file”]标签)和upload的文件个数相同的时候就说明已经添加完了,可以触发上传的方法了。

  2. 这个时候就需要在data里初始化一个 【fm: new FormData()】,文件上传是用的formdata进行上传的,开始写【:http-request=“httpRequest”】的httpRequest方法逻辑,如下,因为在触发 Upload.submit()方法的时候,httpRequest方法也会执行多次,所以这里也需要进行判断处理的,如下:每次回调的时候就往 this.fm 里添加文件,直到文件添加完成之后再进行一次性上传即可。

 /**
   * 手动上传回调
   */
  async httpRequest(file) {
      this.fm.append(this.name, file.file);
      //当fm getall的数组长度与filetotal的长度一致,就说明万事俱备,就差上传了
      if (this.fm.getAll(this.name).length === this.fileTotal) {
          try {
              //这里的就是你的上传方法了,这个是我封装了的axios上传方法,
              //你们项目里用自己的上传方法即可 例如: axios.post('xxx',{data:this.fm})等。
              const { data } = await uploadFiles(this.fm);
              //这里是上传成功之后要做的事情
              if (data) {
                  this.$emit('upload', data);
                  console.log(`上传成功`, data);
              }
          } catch (error) {
              console.log(`上传文件出错`, error);
          } finally {
              this.loading = false;
          }
      }
  },
完整组件代码
<template>
    <el-upload
        :ref="`Uploader-${uploadId}`"
        v-loading="loading"
        action="upload"
        :style="style"
        :auto-upload="false"
        list-type="picture-card"
        :show-file-list="showFileList"
        :name="uploadId"
        accept="image/*"
        multiple
        :file-list="fileList"
        :before-upload="handleBeforeUpload"
        :http-request="httpRequest"
        :on-change="handleChange"
        :on-remove="handleRemove"
    >
        <i class="el-icon-plus" />
    </el-upload>
</template>
<script>
import { uploadFiles } from '@/http';
export default {
    name: 'AppImagesUploader',
    model: {
        event: 'upload',
        prop: 'images'
    },
    props: {

        name: { //上传字段的name
            type: [String],
            required: false,
            default: 'files'
        },

        size: { //中间的加号大小
            type: [String],
            required: false,
            default: '28px'
        },
        w: { //宽
            type: [String],
            required: false,
            default: 'auto'
        },
        h: { //高
            type: [String],
            required: false,
            default: 'auto'
        },
        images: { //图片数组
            type: [Array],
            required: false,
            default() {
                return [];
            }
        }

    },
    data() {
        return {
            uploadId: Math.random().toString(36).substr(2).toLocaleUpperCase(),
            loading: false,
            showFileList: false,
            fileList: [],
            fm: new FormData(),
            uploadFiles: [], //待上传的文件列表
            fileTotal: 0 //上传文件总数
        };
    },
    computed: {
        style() {
            const { w, h, size } = this;
            return {
                width: w,
                height: h,
                fontSize: size
            };
        }
    },

    created() {
        this.fileList = this.images.map(url => ({ url }));
    },

    methods: {

        /**
         * 手动上传回调
         */
        async httpRequest(file) {
            this.fm.append(this.name, file.file);
            //当fm getall的数组长度与filetotal的长度一致,就说明万事俱备,就差上传了
            if (this.fm.getAll(this.name).length === this.fileTotal) {
                try {
                    //这里的就是你的上传方法了
                    const { data } = await uploadFiles(this.fm);
                    //这里是上传成功之后要做的事情
                    if (data) {
                        this.showFileList = true;
                        if (this.fileList.length) {
                            for (const index in data) {
                                this.fileList.push({ url: this.$GET_SERVER_STATIC(data[index].path) });
                            }
                        } else {
                            this.fileList = data.map(item => ({ url: this.$GET_SERVER_STATIC(item.path) }));
                        }
                        this.$emit('upload', this.fileList.map(item => item.url));
                    }
                } catch (error) {
                    console.log(`上传文件出错`, error);
                } finally {
                    //无论成功与失败都要清空文件列表,否则会出现无法上传的情况
                    this.uploadFiles = [];
                    this.fm.delete(this.name);
                    this.loading = false;
                }
            }
        },

        /**
         * 上传前回调
         */
        handleBeforeUpload(file) {
            const isSize = file.size / 1024 / 1024 < 4;
            this.loading = true;
            if (!isSize) {
                this.$_app.message.error('上传图片大小不能超过 4MB!');
            }
            if (!isSize) this.loading = false;
            return isSize;
        },

        /**
         * 文件上传change
         */
        handleChange(file, fileList) {
            //获取添加文件进来的状态
            (file.status == 'ready') && this.uploadFiles.push(file.raw);
            this.showFileList = false;
            //获取原始文件的个数
            this.fileTotal = document.getElementsByName(this.uploadId)[0].files.length;
            //如果原始文件和upload的个数相同的时候就说明已经添加完了,可以触发上传的方法了
            if (this.uploadFiles.length === this.fileTotal) {
                //获取上传文件的组件
                const Uploader = this.$refs[`Uploader-${this.uploadId}`];
                //触发上传文件列表的方法
                Uploader.submit();
            }
        },

        /**
         * 移除图片回调
         */
        handleRemove(file, fileList) {
            const index = this.fileList.findIndex((item) => item.uid == file.uid);
            this.fileList.splice(index, 1);
            this.$emit('upload', this.fileList.map(item => item.url));
        }
    }
};
</script>
<style lang="scss" scoped>
.image-uploader {
    /deep/.el-upload{
        border: 1px dashed #d9d9d9;
        border-radius: 6px;
        cursor: pointer;
        position: relative;
        overflow: hidden;
        display: flex;
        align-items: center;
        justify-content: center;
        height: 100%;
        background: #fff;
        img{
            display: inline-block;
            width: 100%;
        }
    }
}
</style>

四、样式截图

  1. 默认的样式,未选择文件的时候
    在这里插入图片描述
  2. 默认的样式,已选择文件的时候
    在这里插入图片描述
  3. 上传成功的回调
    在这里插入图片描述
  4. 接口展示 多文件一次性上传,只调用一次接口。
    在这里插入图片描述

五、写在后面

上面就是vue项目element upload 一次性自动上传多文件多图片的解决方案的全部内容了,加油,快去试试吧。

代码部分是由真实项目简化而来的,有问题请留言或者@博主,谢谢支持o( ̄︶ ̄)o~

感谢您的阅读,如果此文章或项目对您有帮助,请给个一键三连吧,GitHub有开源项目,需要的小伙伴可以顺手star一下!

GitHub: https://github.com/langyuxiansheng

更多信息请关注公众号: “笔优站长”

笔优站长

扫码关注“笔优站长”,支持站长
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐