el-upload 多文件 上传 只执行一次成功回调on-success的问题

本文讲述了在Element UI上传组件中遇到的多文件上传问题,如何通过解除file-list绑定和使用独立变量避免重复上传效果,并分享了解决方案。作者还介绍了封装的upload组件和自定义方法,以确保文件操作的准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、问题背景**

  • 背景:使用element的上传组件,在使用多文件上传时,发现如果同时选择两个文件,上传完成之后,upload组件的列表只显示一个文件。
  • 通过查看控制台发现on-success方法在上传文件时,只执行了一次。返回的两个文件,其中一个状态是uploading。而我们取文件又只取了status是success的文件。

二、探索和解决**

1,解除绑定upload组件的file-list绑定(非根本解决)
这个方法简单粗暴,通过自己验证确实是可以的。不会出现选择多个文件上传,最后只有一个生效的问题。但是这个解决方法有个弊端就是说,当我们需要数据回显时,upload组件的flie-list并没有绑定数据,所以不会渲染出来。
2,通过绑定初始变量,实际操作使用另一个变量(根本解决);
第二种方法是:我们可以通过定义两个变量:
一个赋初值给upload组件,仅仅只是作为展示使用;
一个是真实我们要操控的变量,所有的对于文件的操作,包括上传和删除都对这个变量进行操作
贴一下关键代码

<template>
  <el-dialog :title="title" :visible.sync="dialogVisible" width="60%">
    <upload-file
      type="file"
      :upload-success="uploadSuccess"
      :remove="updateFileList"
      :file-list="fileList"
      :limit-count="5"
      :limit-size="10"
      ref="uploadFile"
    ></upload-file>
  </el-dialog>
</template>

<script>
import { MESSAGE_API } from '@/api';
import UploadFile from '@/components/upload-file';

export default {
  name: 'property-dialog',
  model: {
    prop: 'dialogVisible',
    event: 'change'
  },
  components: {
    UploadFile
  },
  props: {
    dialogVisible: {
      type: Boolean,
      default: false
    },
    options: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      fileList: [], //fileList仅仅作为展示使用
      realFileList: [] // 所有关于文件的上传和删除直接操作 realFileList。
    };
  },
  methods: {
    dialogCancel() {
      this.$emit('change', false);
    },
    updateFileList(file) {
      // 剔除被删除的文件
      this.realFileList = this.realFileList.filter(v => v.path !== file.path);
    },

    async uploadSuccess(result, file) {
      // 对于上传成功的文件 只保存到realFileList
      if (result.success) {
        const { fileName, path, fileSize } = file.response.data;
        const fileObj = {
          fileName,
          path,
          fileSize,
          name: fileName
        };

        this.realFileList.push(fileObj);
      }
    },

    async dialogSubmit(){
      // 提交文件 attachments 也是提交 realFileList
      const attachments = this.realFileList;
      console.log(`attachments`, attachments)
      this.$emit('change', false);
    },
    async getDetail(){
      const { id, areaCode, appCode } = this.options.colum;
      const result = await MESSAGE_API.getEmailDetail({ id, areaCode, appCode });
      const { attachmentRequest } = result.data;
      // 获取数据对两个文件列表都进行赋值
      this.fileList = attachmentRequest ? attachmentRequest.attachments.map(v => ({ ...v, name: v.fileName })) : [];
      this.realFileList = attachmentRequest ? attachmentRequest.attachments.map(v => ({ ...v, name: v.fileName })) : [];
    }
  }
};
</script>

封装的upload组件,如果需要可以自取

<template lang="html">
  <div class="upload-file">
    <el-upload
      :action="url"
      :headers="headers"
      :before-upload="beforeUpload"
      :on-success="uploadSuccess"
      :on-preview="isPicture ? onPreview : download"
      :on-remove="remove"
      :before-remove="beforeRemove"
      :on-exceed="onExceed"
      :list-type="listType"
      :accept="accept"
      :file-list="fileList"
      :limit="limitCount"
      :disabled="disabled"
      :class="[isHideUpload && 'hide-upload-button', disabled && 'operate-disabled', uploadClassName]"
      multiple
      ref="elUpload"
    >
      <template v-if="isPicture">
        <i class="el-icon-plus"></i>
      </template>

      <template v-else>
        <el-button :disabled="disabled" size="small" type="primary">{{uploadText}}</el-button>
      </template>

      <div slot="tip" class="el-upload__tip">{{ tip }}</div>
    </el-upload>

    <image-viewer-s v-show="dialogVisible" :on-close="closeViewer" :url-list="[dialogImageUrl]"></image-viewer-s>

  </div>
</template>

<script>
import Cookies from 'js-cookie';

const API_HOST = process.env.VUE_APP_API_HOST;
const PREFIX = process.env.VUE_APP_PUBLIC_API_EMAIL_PREFIX;
const APP_CODE = process.env.VUE_APP_APP_CODE;

const TOKEN_KEY = process.env.VUE_APP_TOKEN_KEY;

export default {
  name: 'upload-file',
  props: {
    type: {
      type: String,
      default: 'file' // file/picture
    },
    limitCount: {
      type: Number,
      default: 1
    },
    limitSize: {
      type: Number,
      default: 5 // 单位 M
    },
    fileList: {
      type: Array,
      default: () => ([])
    },
    uploadSuccess: {
      type: Function,
      default: () => {}
    },
    remove: {
      type: Function,
      default: () => {}
    },
    download: {
      type: Function,
      default: () => {}
    },
    previewImg: {
      type: Function,
      default: () => {}
    },
    tip: {
      type: String,
      default: ''
    },
    uploadClassName: {
      type: String,
      default: ''
    },
    disabled: {
      type: Boolean,
      default: false
    },
    uploadText: {
      type: String,
      default: '+ Upload'
    }
  },
  data() {
    return {
      url: `${API_HOST}${PREFIX}/file/uploadFile`,
      dialogImageUrl: '',
      dialogVisible: false
    };
  },
  computed: {
    headers() {
      return {
        Authorization: Cookies.get(TOKEN_KEY),
        appCode: APP_CODE
      };
    },
    isPicture() {
      return this.type === 'picture';
    },
    accept() {
      return this.isPicture ? 'image/*' : '';
    },
    listType() {
      return this.isPicture ? 'picture-card' : 'text';
    },
    isHideUpload() {
      return this.fileList.length >= this.limitCount;
    }
  },
  methods: {
    onExceed(files, fileList) {
      this.$message.warning(`No more than ${ this.limitCount } files, you have selected ${files.length} files, total ${files.length + fileList.length} files`);
    },
    async onPreview(file) {
      const { url } = file;
      if (url.includes('base64')) {
        this.dialogImageUrl = url;
      } else {
        this.dialogImageUrl = await this.previewImg(file.response.data);
      }

      this.dialogVisible = true;
    },
    beforeUpload(file) {
      const { size } = file;
      if (this.isOverSize(size)) {
        this.$message.error(`The file size is ${(size / 1024 / 1024).toFixed(2)}M,over ${this.limitSize}M`);
        return false;
      }
    },
    beforeRemove(file) {
      const { size } = file;
      if (!this.isOverSize(size)) {
        return this.$confirm(`Delete ${ file.name }?`);
      }
    },
    isOverSize(size) {
      if (size / 1024 / 1024 < this.limitSize) {
        return false;
      }
      return true;
    },
    closeViewer() {
      this.dialogVisible = false;
    },
    clearFiles() {
      this.$refs.elUpload.clearFiles();
    }
  }
};
</script>

<style lang="scss" scoped>
.upload-file {
  .operate-disabled {
    /deep/.el-upload--picture-card {
      background-color: #F5F7FA;
      border-color: #E4E7ED;
      color: #C0C4CC;
      cursor: not-allowed;

      &:hover,
      &:focus {
        border-color: #E4E7ED;
      }
    }
  }
}
</style>

三、总结

问题的解决是一个探索的过程。当然这么说也有点问题,实际的方案并不是自己出的,而是通过求助网友。自己只是把问题的解决方案拿出来,分享给大家,互助共赢。感谢这个大佬。
https://blog.csdn.net/qq_41780372/article/details/117773863

<think>好的,我需要解决用户关于element-ui的el-upload组件中on-success钩子函数执行时机和触发机制的问题。首先,我需要回顾一下用户提供的引用内容,看看是否有相关的线索。 引用[1]提到当使用on-success钩子函数时,response参数获取的PDF二进制文件无法正确解析,文件大小不正常。这说明on-success可能在文件上传完成后被触发,但处理响应时可能存在问题。引用[3]指出,当使用自定义的http-request方法并关闭自动上传(auto-upload="false")时,on-success方法不会执行,而使用默认方式则可以。这说明on-success的触发可能与上传请求的成功回调有关,特别是当使用默认的上传方法时才会正确触发。引用[4]提到handleAvatarSuccess函数的响应不正常,这可能与后端返回的数据格式或钩子函数的触发条件有关。 接下来,我需要明确on-success钩子函数的官方定义。根据element-ui文档,on-success是在文件上传成功时触发的回调函数。触发条件是服务器返回正确的HTTP状态码(通常是2xx),并且上传组件识别到成功响应后才会调用。如果使用自定义的上传方法(http-request),需要手动触发on-success,否则它不会自动执行,如引用[3]所述的情况。 另外,自动上传(auto-upload)的设置会影响on-success的触发。当auto-upload为true时,选择文件后会自动上传成功则触发on-success;如果为false,需要手动调用submit方法,上传成功后才触发。引用[3]中的问题正是由于关闭了自动上传,但未正确触发on-success导致的。 还要考虑后端返回的数据格式是否符合element-ui的预期。如果后端返回的数据结构不正确,即使上传成功on-success也可能无法正常处理响应数据,如引用[1]和[4]提到的问题。例如,response需要包含特定的字段,如status或code,组件才能识别为成功。 总结触发机制的关键点:1)上传请求成功(HTTP状态码2xx);2)使用默认上传方法或正确触发自定义上传成功回调;3)后端返回的数据格式正确。如果其中任何一环出现问题on-success可能不会按预期执行。 用户的问题可能集中在为什么有时候on-success不触发,比如使用自定义上传方法时,或者后端返回的数据格式不对。需要解释清楚这些触发条件,并提供解决方法,比如在自定义http-request中手动调用on-success,或调整后端响应格式。 最后,生成的相关问题应该围绕如何确保on-success触发、处理上传失败的情况,以及自定义上传方法时的注意事项等。</think>### el-upload组件中on-success钩子函数的执行机制 #### 1. **触发条件** on-success钩子函数在以下两个条件同时满足时触发: - **文件上传HTTP请求成功**(服务器返回2xx状态码) - **组件正确识别响应数据格式**(需符合Element-UI定义的响应结构)[^3][^4] #### 2. **执行时机** - **自动上传模式**(`auto-upload="true"`) 用户选择文件后自动触发上传上传成功执行`on-success`。 ```html <el-upload :auto-upload="true" :on-success="handleSuccess"> </el-upload> ``` - **手动上传模式**(`auto-upload="false"`) 需调用`submit()`方法手动触发上传成功后才执行`on-success`: ```html <el-upload ref="upload" :auto-upload="false" :on-success="handleSuccess"> <button @click="$refs.upload.submit()">手动上传</button> </el-upload> ``` #### 3. **常见问题与解决方案 **问题1:自定义http-request时on-success不触发** - **原因**:覆盖默认的`http-request`方法后,需手动触发`on-success` - **解决方案**: ```javascript submitUpload(file) { const formData = new FormData(); formData.append('file', file.file); axios.post('/upload', formData).then(res => { // 手动调用on-success this.handleSuccess(res, file.file); }); } ``` **问题2:响应数据解析异常** - **原因**:后端返回的数据缺少必要字段(如`status`或`data`)[^1] - **预期响应格式**: ```json { "status": "success", "data": { "url": "file_path" } } ``` **问题3:二次上传失效** - **原因**:未清除已上传文件列表[^2] - **解决方案**: ```javascript handleSuccess(res, file) { // 清空文件列表 this.$refs.upload.clearFiles(); } ``` ---
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值