element-ui upload 组件源码分享

发布于:2025-05-28 ⋅ 阅读:(25) ⋅ 点赞:(0)

upload 上传组件源码简单分享,主要从以下几个方面:

1、upload 组件页面结构。

2、upload 组件属性。

3、upload 组件 slot。

4、upload 组件方法。

一、组件页面结构。

二、组件属性。

2.1 action 必选参数,上传的地址,类型 string,默认无。

 XMLHttpRequest 对象的 api 地址:XMLHttpRequest - Web API | MDN

2.2 headers 设置上传的请求头部,类型 object,无默认值。

2.3 multiple 是否支持多选文件,类型 boolean,无默认值。

2.4 data 上传时附带的额外参数,类型 object,无默认值。

2.5 name 上传的文件字段名,类型 string,默认 file。

2.6 with-credentials 支持发送 cookie 凭证信息,类型 boolean,默认 false。

2.7 show-file-list 是否显示已上传文件列表,类型 boolean,默认 true。

2.8 drag 是否启用拖拽上传,类型 boolean,默认 false。

2.9 accept 接受上传的文件类型(thumbnail-mode 模式下此参数无效),类型 string,无默认值。

2.10 on-preview 点击文件列表中已上传的文件时的钩子,类型 function(file),无默认值。

2.11 on-remove 文件列表移除文件时的钩子,类型 function(file, fileList),无默认值。

2.12 on-success 文件上传成功时的钩子,类型 function(response, file, fileList),无默认值。

// on-success 回调函数主要针对于自动上传使用,调用 upload 组件默认上传方法
// 以下是使用手动上传时,on-success 回调函数的使用
<template>
  <div>
    <el-upload
      ref="uploadRef"
      :auto-upload="false"
      :on-change="handleAvatarChange"
      :show-file-list="true"
      class="upload-avatar-wrapper"
      accept="image/*"
      :action="uploadUrl"
      :on-preview="onPreview"
      :on-remove="onRemove"
      :on-success="onSuccess"
    >
      <!-- 显示当前已选/已上传的图片 -->
      <div class="avatar-preview-list">
        <img
          v-if="tempPreviews.url"
          :src="tempPreviews.url"
          class="avatar"
          style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
        />
      </div>
      <div
        v-if="!tempPreviews.url"
        class="avatar-uploader-icon"
        style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
      >
        <i class="el-icon-plus"></i>
      </div>
    </el-upload>

    <el-button @click="customUpload">上传</el-button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        token:
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3Mjc5NzA2LCJleHAiOjE3NDc4ODQ1MDZ9.KfSi5zAq3c45Gjv6tkH5is5LtDx_74X99bb5fR_YXPc',
        form: {
          operName: 'super001',
          password: '123456',
          avatar: '',
        },
        tempPreviews: {}, // 存储预览图片对象
        selectedFile: null, // 当前选择的文件
        serverUrl: 'http://localhost:3000',
        uploadUrl: '/api/user/uploadAvatar',
      };
    },
    methods: {
      onSuccess(response, file, fileList) {
        console.log('onSuccess response::', response);
        this.$message.success('上传成功');
        this.form.avatar = `${this.serverUrl}${response.url}`;
      },

      onRemove(file, fileList) {
        this.tempPreviews = {};
        this.selectedFile = null;
      },

      onPreview(file) {
        console.log('onPreview file::', file);
      },

      handleAvatarChange(file, fileList) {
        // 如果已经有文件被选中,则阻止继续选择
        if (this.selectedFile) {
          this.$message.warning('只能选择一个文件');
          return false;
        }

        const reader = new FileReader();
        reader.onload = (e) => {
          this.tempPreviews = {
            url: e.target.result,
            uid: file.uid,
          };
        };

        if (file.raw) {
          reader.readAsDataURL(file.raw);
          this.selectedFile = file.raw;
        }
      },

      customUpload() {
        if (!this.selectedFile) {
          this.$message.warning('请先选择一个文件');
          return;
        }

        const formData = new FormData();
        formData.append('avatar', this.selectedFile); // 单个文件
        formData.append('operName', this.form.operName);
        formData.append('password', this.form.password);

        fetch(this.uploadUrl, {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${this.token}`,
          },
          body: formData,
        })
          .then((response) => {
            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
          })
          .then((data) => {
            console.log('Success:', data);
            this.onSuccess(data, { raw: this.selectedFile }, [
              { raw: this.selectedFile },
            ]);
            this.selectedFile = null;
            this.tempPreviews = {};
          })
          .catch((error) => {
            console.error('Error:', error);
            this.$message.error('上传失败');
          });
      },
    },
  };
</script>

<style scoped>
  .upload-avatar-wrapper {
    width: 60px;
    height: 60px;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .upload-avatar-wrapper:hover {
    border-color: #409eff;
  }

  .avatar-uploader-icon {
    font-size: 28px;
  }

  .avatar {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
</style>
// 自动上传,调用upload 默认的上传方法
<template>
  <div>
    <el-upload
      ref="uploadRef"
      :auto-upload="true"
      :on-change="handleAvatarChange"
      :show-file-list="false"
      class="upload-avatar-wrapper"
      accept="image/*"
      action="/api/user/uploadAvatar"
      name="avatar"
      :headers="{
         'Authorization': `Bearer ${token}`
      }"
      :data="{
          operName: 'super001',
          password: '123456',
      }"
      :multiple="false"
      :limit="1"
      :on-exceed="handleExceed"
      :with-credentials="false"
      :on-success="handleSuccess"
      :on-error="handleError"
      :on-remove="handleRemove"
    >
      <!-- 显示当前头像 -->
      <div class="avatar-preview">
        <img
          v-if="form.avatar || tempPreview"
          :src="form.avatar || tempPreview"
          class="avatar"
          style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
        />
      </div>

      <!-- 触发上传按钮 -->
      <div
        class="avatar-uploader-icon"
        style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
      >
        <i class="el-icon-plus"></i>
      </div>
    </el-upload>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        token:
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3Mjc5NzA2LCJleHAiOjE3NDc4ODQ1MDZ9.KfSi5zAq3c45Gjv6tkH5is5LtDx_74X99bb5fR_YXPc',
        serverUrl: 'http://localhost:3000',
        form: {
          operName: 'super001',
          password: '123456',
          avatar: '', // 单个字符串存储头像URL
        },
        tempPreview: null, // 临时存放本地预览
      };
    },
    methods: {
      handleExceed(files, fileList) {
        this.$message.warning('只能选择一个文件');
      },
      handleAvatarChange(file, fileList) {
        const reader = new FileReader();
        reader.onload = (e) => {
          // 设置临时预览图
          this.tempPreview = e.target.result;
        };
        if (file.raw) {
          reader.readAsDataURL(file.raw);
        }
      },
      handleSuccess(response, file, fileList) {
        this.$message.success('上传成功');
        const uploadedUrl = response.url;
        if (uploadedUrl) {
          this.form.avatar = `${this.serverUrl}${uploadedUrl}`;
          this.tempPreview = null; // 清除临时预览图
        }
      },
      handleError(err, file, fileList) {
        console.error('Upload Error:', err);
        this.$message.error('上传失败');
        this.tempPreview = null; // 出错时清除临时预览图
      },
      handleRemove(file, fileList) {
        this.form.avatar = ''; // 清空 form.avatar
        this.tempPreview = null; // 清除临时预览图
      },
    },
  };
</script>

<style scoped>
  .avatar-preview {
    display: flex;
    justify-content: flex-start;
    margin-bottom: 10px;
  }
  .avatar {
    width: 60px;
    height: 60px;
    object-fit: cover;
    border: 1px solid #ddd;
    margin-right: 10px;
  }
  .upload-avatar-wrapper {
    width: 60px;
    height: 60px;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .upload-avatar-wrapper:hover {
    border-color: #409eff;
  }
  .avatar-uploader-icon {
    font-size: 28px;
  }
</style>

2.13 on-error 文件上传失败时的钩子,类型 function(err, file, fileList),无默认值。

2.14 on-progress 文件上传时的钩子,类型 function(event, file, fileList),无默认值。

// 增加上传进度条
<template>
  <div>
    <!-- 文件上传组件 -->
    <el-upload
      ref="uploadRef"
      :auto-upload="true"
      :on-change="handleAvatarChange"
      :show-file-list="false"
      class="upload-avatar-wrapper"
      accept="image/*"
      action="/api/user/uploadAvatar"
      name="avatar"
      :headers="{
         'Authorization': `Bearer ${token}`
      }"
      :data="{
          operName: 'super001',
          password: '123456',
      }"
      :multiple="false"
      :limit="1"
      :on-exceed="handleExceed"
      :with-credentials="false"
      :on-success="handleSuccess"
      :on-error="handleError"
      :on-remove="handleRemove"
      :on-progress="handleProgress"
    >
      <!-- 显示当前头像 -->
      <div class="avatar-preview">
        <img
          v-if="form.avatar || tempPreview"
          :src="form.avatar || tempPreview"
          class="avatar"
          style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
        />
      </div>

      <!-- 触发上传按钮 -->
      <div
        class="avatar-uploader-icon"
        style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
      >
        <i class="el-icon-plus"></i>
      </div>
    </el-upload>

    <!-- 进度条 -->
    <el-progress v-if="percentage > 0" :percentage="percentage"></el-progress>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        token:
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3Mjc5NzA2LCJleHAiOjE3NDc4ODQ1MDZ9.KfSi5zAq3c45Gjv6tkH5is5LtDx_74X99bb5fR_YXPc', // 替换为实际的Token
        serverUrl: 'http://localhost:3000',
        form: {
          operName: 'super001',
          password: '123456',
          avatar: '', // 单个字符串存储头像URL
        },
        tempPreview: null, // 临时存放本地预览
        percentage: 0, // 新增:用于存储上传进度百分比
      };
    },
    methods: {
      handleProgress(event, file, fileList) {
        // 更新进度百分比
        this.percentage = Math.floor(event.percent);
        console.log(`正在上传 ${file.name}: ${this.percentage}%`);
      },

      handleExceed(files, fileList) {
        this.$message.warning('只能选择一个文件');
      },

      handleAvatarChange(file, fileList) {
        const reader = new FileReader();
        reader.onload = (e) => {
          // 设置临时预览图
          this.tempPreview = e.target.result;
        };
        if (file.raw) {
          reader.readAsDataURL(file.raw);
        }
      },

      handleSuccess(response, file, fileList) {
        this.$message.success('上传成功');
        const uploadedUrl = response.url;
        if (uploadedUrl) {
          this.form.avatar = `${this.serverUrl}${uploadedUrl}`;
          this.tempPreview = null; // 清除临时预览图
          this.percentage = 100; // 完成时设置为100%
          setTimeout(() => {
            this.percentage = 0; // 成功后两秒隐藏进度条
          }, 2000);
        }
      },

      handleError(err, file, fileList) {
        console.error('Upload Error:', err);
        this.$message.error('上传失败');
        this.tempPreview = null; // 出错时清除临时预览图
        this.percentage = 0; // 错误时重置进度条
      },

      handleRemove(file, fileList) {
        this.form.avatar = ''; // 清空 form.avatar
        this.tempPreview = null; // 清除临时预览图
        this.percentage = 0; // 删除文件时重置进度条
      },
    },
  };
</script>

<style scoped>
  .avatar-preview {
    display: flex;
    justify-content: flex-start;
    margin-bottom: 10px;
  }
  .avatar {
    width: 60px;
    height: 60px;
    object-fit: cover;
    border: 1px solid #ddd;
    margin-right: 10px;
  }
  .upload-avatar-wrapper {
    width: 60px;
    height: 60px;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .upload-avatar-wrapper:hover {
    border-color: #409eff;
  }
  .avatar-uploader-icon {
    font-size: 28px;
  }
</style>

2.15 on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用,类型 function(file, fileList),无默认值。

2.16 before-upload 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。类型 function(file),无默认值。

<template>
  <div>
    <!-- 文件上传组件 -->
    <el-upload
      ref="uploadRef"
      :auto-upload="true"
      :on-change="handleAvatarChange"
      :show-file-list="false"
      class="upload-avatar-wrapper"
      accept="image/*"
      action="/api/user/uploadAvatar"
      name="avatar"
      :headers="{
         'Authorization': `Bearer ${token}`
      }"
      :data="{
          operName: 'super001',
          password: '123456',
      }"
      :multiple="false"
      :limit="1"
      :on-exceed="handleExceed"
      :with-credentials="false"
      :on-success="handleSuccess"
      :on-error="handleError"
      :on-remove="handleRemove"
      :on-progress="handleProgress"
      :before-upload="beforeUpload"
    >
      <!-- 显示当前头像 -->
      <div class="avatar-preview">
        <img
          v-if="form.avatar || tempPreview"
          :src="form.avatar || tempPreview"
          class="avatar"
          style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
        />
      </div>

      <!-- 触发上传按钮 -->
      <div
        class="avatar-uploader-icon"
        style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
      >
        <i class="el-icon-plus"></i>
      </div>
    </el-upload>

    <!-- 进度条 -->
    <el-progress v-if="percentage > 0" :percentage="percentage"></el-progress>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        token:
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3Mjc5NzA2LCJleHAiOjE3NDc4ODQ1MDZ9.KfSi5zAq3c45Gjv6tkH5is5LtDx_74X99bb5fR_YXPc', // 替换为实际的Token
        serverUrl: 'http://localhost:3000',
        form: {
          operName: 'super001',
          password: '123456',
          avatar: '', // 单个字符串存储头像URL
        },
        tempPreview: null, // 临时存放本地预览
        percentage: 0, // 新增:用于存储上传进度百分比
      };
    },
    methods: {
      beforeUpload(file) {
        const isJPG = file.name.endsWith('.jpg');
        if (!isJPG) {
          this.$message.error('您只能上传 JPG 格式的图片!');
        }
        return isJPG;
      },

      handleProgress(event, file, fileList) {
        // 更新进度百分比
        this.percentage = Math.floor(event.percent);
        console.log(`正在上传 ${file.name}: ${this.percentage}%`);
      },

      handleExceed(files, fileList) {
        this.$message.warning('只能选择一个文件');
      },

      handleAvatarChange(file, fileList) {
        const reader = new FileReader();
        reader.onload = (e) => {
          // 设置临时预览图
          this.tempPreview = e.target.result;
        };
        if (file.raw) {
          reader.readAsDataURL(file.raw);
        }
      },

      handleSuccess(response, file, fileList) {
        this.$message.success('上传成功');
        const uploadedUrl = response.url;
        if (uploadedUrl) {
          this.form.avatar = `${this.serverUrl}${uploadedUrl}`;
          this.tempPreview = null; // 清除临时预览图
          this.percentage = 100; // 完成时设置为100%
          setTimeout(() => {
            this.percentage = 0; // 成功后两秒隐藏进度条
          }, 2000);
        }
      },

      handleError(err, file, fileList) {
        console.error('Upload Error:', err);
        this.$message.error('上传失败');
        this.tempPreview = null; // 出错时清除临时预览图
        this.percentage = 0; // 错误时重置进度条
      },

      handleRemove(file, fileList) {
        this.form.avatar = ''; // 清空 form.avatar
        this.tempPreview = null; // 清除临时预览图
        this.percentage = 0; // 删除文件时重置进度条
      },
    },
  };
</script>

<style scoped>
  .avatar-preview {
    display: flex;
    justify-content: flex-start;
    margin-bottom: 10px;
  }
  .avatar {
    width: 60px;
    height: 60px;
    object-fit: cover;
    border: 1px solid #ddd;
    margin-right: 10px;
  }
  .upload-avatar-wrapper {
    width: 60px;
    height: 60px;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .upload-avatar-wrapper:hover {
    border-color: #409eff;
  }
  .avatar-uploader-icon {
    font-size: 28px;
  }
</style>

2.17 before-remove 删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,则停止删除。类型 function(file, fileList),无默认值。

// before-remove 回调函数使用
<template>
  <div>
    <!-- 文件上传组件 -->
    <el-upload
      ref="uploadRef"
      :auto-upload="true"
      :on-change="handleAvatarChange"
      :show-file-list="true"
      class="upload-avatar-wrapper"
      accept="image/*"
      action="/api/user/uploadAvatar"
      name="avatar"
      :headers="{
         'Authorization': `Bearer ${token}`
      }"
      :data="{
          operName: 'super001',
          password: '123456',
      }"
      :multiple="false"
      :limit="1"
      :on-exceed="handleExceed"
      :with-credentials="false"
      :on-success="handleSuccess"
      :on-error="handleError"
      :on-remove="handleRemove"
      :on-progress="handleProgress"
      :before-upload="beforeUpload"
      :before-remove="beforeRemove"
    >
      <!-- 显示当前头像 -->
      <div class="avatar-preview">
        <img
          v-if="form.avatar || tempPreview"
          :src="form.avatar || tempPreview"
          class="avatar"
          style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
        />
      </div>

      <!-- 触发上传按钮 -->
      <div
        class="avatar-uploader-icon"
        style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
      >
        <i class="el-icon-plus"></i>
      </div>
    </el-upload>

    <!-- 进度条 -->
    <el-progress v-if="percentage > 0" :percentage="percentage"></el-progress>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        token:
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3Mjc5NzA2LCJleHAiOjE3NDc4ODQ1MDZ9.KfSi5zAq3c45Gjv6tkH5is5LtDx_74X99bb5fR_YXPc', // 替换为实际的Token
        serverUrl: 'http://localhost:3000',
        form: {
          operName: 'super001',
          password: '123456',
          avatar: '', // 单个字符串存储头像URL
        },
        tempPreview: null, // 临时存放本地预览
        percentage: 0, // 新增:用于存储上传进度百分比
      };
    },
    methods: {
      beforeRemove(file, fileList) {
        console.log('beforeRemove file::', file, 'fileList::', fileList);
        // 可以在此添加任何逻辑,比如询问用户是否确定删除
        return this.$confirm(`确定要移除 ${file.name} 吗?`, '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        })
          .then(() => {
            // 如果用户点击了“确定”,则返回 true 允许移除
            return true;
          })
          .catch(() => {
            // 如果用户点击了“取消”,则返回 false 阻止移除
            return false;
          });
      },

      beforeUpload(file) {
        const isJPG = file.name.endsWith('.jpg');
        if (!isJPG) {
          this.$message.error('您只能上传 JPG 格式的图片!');
          return false;
        }
        return true;
      },

      handleProgress(event, file, fileList) {
        // 更新进度百分比
        this.percentage = Math.floor(event.percent);
        console.log(`正在上传 ${file.name}: ${this.percentage}%`);
      },

      handleExceed(files, fileList) {
        this.$message.warning('只能选择一个文件');
      },

      handleAvatarChange(file, fileList) {
        const reader = new FileReader();
        reader.onload = (e) => {
          // 设置临时预览图
          this.tempPreview = e.target.result;
        };
        if (file.raw) {
          reader.readAsDataURL(file.raw);
        }
      },

      handleSuccess(response, file, fileList) {
        this.$message.success('上传成功');
        const uploadedUrl = response.url;
        if (uploadedUrl) {
          this.form.avatar = `${this.serverUrl}${uploadedUrl}`;
          this.tempPreview = null; // 清除临时预览图
          this.percentage = 100; // 完成时设置为100%
          setTimeout(() => {
            this.percentage = 0; // 成功后两秒隐藏进度条
          }, 2000);
        }
      },

      handleError(err, file, fileList) {
        console.error('Upload Error:', err);
        this.$message.error('上传失败');
        this.tempPreview = null; // 出错时清除临时预览图
        this.percentage = 0; // 错误时重置进度条
      },

      handleRemove(file, fileList) {
        console.log('handleRemove file::', file, 'fileList::', fileList);
        this.form.avatar = ''; // 清空 form.avatar
        this.tempPreview = null; // 清除临时预览图
        this.percentage = 0; // 删除文件时重置进度条
      },
    },
  };
</script>

<style scoped>
  .avatar-preview {
    display: flex;
    justify-content: flex-start;
    margin-bottom: 10px;
  }
  .avatar {
    width: 60px;
    height: 60px;
    object-fit: cover;
    border: 1px solid #ddd;
    margin-right: 10px;
  }
  .upload-avatar-wrapper {
    width: 60px;
    height: 60px;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .upload-avatar-wrapper:hover {
    border-color: #409eff;
  }
  .avatar-uploader-icon {
    font-size: 28px;
  }
</style>

2.1 list-type 文件列表的类型,类型 string,可选值 text/picture/picture-card,默认 text。

2.19 auto-upload 是否在选取文件后立即进行上传,类型 boolean,默认 true。

2.20 file-list 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]。类型 array,默认 []。

2.21 http-request 覆盖默认的上传行为,可以自定义上传的实现,类型 function,无默认值。

<template>
  <div>
    <el-upload
      ref="uploadRef"
      :auto-upload="true"
      :on-change="handleAvatarChange"
      :show-file-list="true"
      class="upload-avatar-wrapper"
      accept="image/*"
      :action="uploadUrl"
      :http-request="httpRequest"
      :on-preview="onPreview"
      :on-remove="onRemove"
      :on-success="onSuccess"
    >
      <!-- 显示当前已选/已上传的图片 -->
      <div class="avatar-preview-list">
        <img
          v-if="tempPreviews.url"
          :src="tempPreviews.url"
          class="avatar"
          style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
        />
      </div>
      <div
        v-if="!tempPreviews.url"
        class="avatar-uploader-icon"
        style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
      >
        <i class="el-icon-plus"></i>
      </div>
    </el-upload>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        token:
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ4MzExMjUyLCJleHAiOjE3NDg5MTYwNTJ9.B8PwiMxPfpyQELGLdBBdFrMuNxdJvpFwtsE9-6jGAWk',
        form: {
          operName: 'super001',
          password: '123456',
          avatar: '',
        },
        tempPreviews: {}, // 存储预览图片对象
        selectedFile: null, // 当前选择的文件
        serverUrl: 'http://localhost:3000',
        uploadUrl: '/api/user/uploadAvatar',
      };
    },
    methods: {
      onSuccess(response, file, fileList) {
        console.log('onSuccess response::', response);
        this.$message.success('上传成功');
        this.form.avatar = `${this.serverUrl}${response.url}`;
      },

      onRemove(file, fileList) {
        this.tempPreviews = {};
        this.selectedFile = null;
      },

      onPreview(file) {
        console.log('onPreview file::', file);
      },

      handleAvatarChange(file, fileList) {
        if (fileList.length > 1) {
          this.$message.warning('只能选择一个文件');
          fileList.splice(0, 1); // 清除多余的文件
          return false;
        }

        const reader = new FileReader();
        reader.onload = (e) => {
          this.tempPreviews = {
            url: e.target.result,
            uid: file.uid,
          };
        };

        if (file.raw) {
          reader.readAsDataURL(file.raw);
          this.selectedFile = file.raw;
        }
      },

      httpRequest(option) {
        console.log('httpRequest option::', option);
        const formData = new FormData();
        formData.append('avatar', option.file);
        formData.append('operName', this.form.operName);
        formData.append('password', this.form.password);

        fetch(option.action, {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${this.token}`,
          },
          body: formData,
        })
          .then((response) => {
            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
          })
          .then((data) => {
            console.log('Upload success:', data);
            this.form.avatar = `${this.serverUrl}${data.url}`;
            option.onSuccess(data); // 告诉 el-upload 成功
          })
          .catch((error) => {
            console.error('Upload failed:', error);
            this.$message.error('上传失败');
            option.onError(error); // 告诉 el-upload 出错
          });
      },
    },
  };
</script>

<style scoped>
  .upload-avatar-wrapper {
    width: 60px;
    height: 60px;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .upload-avatar-wrapper:hover {
    border-color: #409eff;
  }

  .avatar-uploader-icon {
    font-size: 28px;
  }

  .avatar {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
</style>

2.22 disabled 是否禁用,类型 boolean,默认 false。

2.23 limit 最大允许上传个数,类型 number,无默认值。

2.24 on-exceed 文件超出个数限制时的钩子,类型 function(files, fileList),无默认值。

<template>
  <div>
    <el-upload
      ref="uploadRef"
      :auto-upload="true"
      :limit="2"
      :multiple="true"
      :on-change="handleAvatarChange"
      :show-file-list="false"
      class="upload-avatar-wrapper"
      accept="image/*"
      :action="uploadUrl"
      :http-request="httpRequest"
      :on-preview="onPreview"
      :on-remove="onRemove"
      :on-success="onSuccess"
      :on-exceed="handleExceed"
    >
      <!-- 显示当前已选/已上传的图片 -->
      <div class="avatar-preview-list">
        <img
          v-for="(preview, index) in tempPreviews"
          :key="index"
          :src="preview.url"
          class="avatar"
          style="
            width:60px;
            height:60px;
            margin-right:10px;
            border:1px #ddd solid;
            object-fit: cover;"
        />
      </div>
      <div
        v-if="tempPreviews.length < 2"
        class="avatar-uploader-icon"
        style="
          width:60px;
          height:60px;
          text-align:center;
          line-height:60px;
          border:1px #ddd solid;"
      >
        <i class="el-icon-plus"></i>
      </div>
    </el-upload>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        token:
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ4MzExMjUyLCJleHAiOjE3NDg5MTYwNTJ9.B8PwiMxPfpyQELGLdBBdFrMuNxdJvpFwtsE9-6jGAWk',
        form: {
          operName: 'super001',
          password: '123456',
          avatar: '',
        },
        tempPreviews: [], // 存储多个预览图
        selectedFiles: [], // 存储选中的多个文件
        serverUrl: 'http://localhost:3000',
        uploadUrl: '/api/user/uploadAvatar',
      };
    },
    methods: {
      onSuccess(response, file, fileList) {
        console.log('onSuccess response::', response);
        this.$message.success('上传成功');
        this.form.avatar = `${this.serverUrl}${response.url}`;
      },

      onRemove(file, fileList) {
        // 从预览列表中移除对应文件
        this.tempPreviews = this.tempPreviews.filter(
          (preview) => preview.uid !== file.uid
        );
        this.selectedFiles = this.selectedFiles.filter(
          (f) => f.uid !== file.uid
        );
      },

      onPreview(file) {
        console.log('onPreview file::', file);
      },

      handleAvatarChange(file, fileList) {
        const limit = 2;

        // 如果文件总数超过限制,只保留前两个
        if (fileList.length > limit) {
          this.$message.warning('最多只能选择两个文件');
          this.$refs.uploadRef.clearFiles(); // 清空所有文件
          this.$refs.uploadRef.handleStart(fileList[0]);
          this.$refs.uploadRef.handleStart(fileList[1]);

          // 同步更新本地预览和文件数组
          this.tempPreviews = [];
          this.selectedFiles = [];

          // 重新添加前两个文件的预览
          for (let i = 0; i < 2 && i < fileList.length; i++) {
            const f = fileList[i];
            const reader = new FileReader();
            reader.onload = (e) => {
              this.tempPreviews.push({
                url: e.target.result,
                uid: f.uid,
              });
            };
            if (f.raw) {
              reader.readAsDataURL(f.raw);
              this.selectedFiles.push(f.raw);
            }
          }

          return false;
        }

        // 正常处理每个文件的预览
        const reader = new FileReader();
        reader.onload = (e) => {
          const preview = {
            url: e.target.result,
            uid: file.uid,
          };

          // 避免重复添加
          if (!this.tempPreviews.some((p) => p.uid === file.uid)) {
            this.tempPreviews.push(preview);
          }
        };

        if (file.raw) {
          reader.readAsDataURL(file.raw);

          // 避免重复保存原始文件
          if (!this.selectedFiles.some((f) => f.uid === file.raw.uid)) {
            this.selectedFiles.push(file.raw);
          }
        }
      },

      // 超出数量时提示
      handleExceed(files, fileList) {
        this.$message.warning('最多只能上传两个文件');
      },

      httpRequest(option) {
        console.log('httpRequest option::', option);
        const formData = new FormData();
        formData.append('avatar', option.file);
        formData.append('operName', this.form.operName);
        formData.append('password', this.form.password);

        fetch(option.action, {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${this.token}`,
          },
          body: formData,
        })
          .then((response) => {
            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
          })
          .then((data) => {
            console.log('Upload success:', data);
            this.form.avatar = `${this.serverUrl}${data.url}`;
            option.onSuccess(data); // 告诉 el-upload 成功
          })
          .catch((error) => {
            console.error('Upload failed:', error);
            this.$message.error('上传失败');
            option.onError(error); // 告诉 el-upload 出错
          });
      },
    },
  };
</script>

<style scoped>
  .upload-avatar-wrapper {
    width: auto;
    min-width: 60px;
    height: 60px;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
  }

  .upload-avatar-wrapper:hover {
    border-color: #409eff;
  }

  .avatar-uploader-icon {
    font-size: 28px;
  }

  .avatar {
    width: 60px;
    height: 60px;
    object-fit: cover;
  }
</style>

三、组件 slot 挂载。

3.1 trigger 触发文件选择框的内容。

3.2 tip 提示说明文字。

四、组件方法。

4.1 clearFiles 清空已上传的文件列表(该方法不支持在 before-upload 中调用)。

4.2 abort 取消上传请求,参数 (file: fileList 中的 file 对象)。

4.3 submit 手动上传文件列表。


网站公告

今日签到

点亮在社区的每一天
去签到