vue2+element-UI上传图片封装

针对上传组件进行封装,在页面直接引用即可,上传到minio文件服务器:

可以预览,重新上传,只读模式,可以传入展示缩略图尺寸,传入上传校验尺寸

javascript 复制代码
<template>
  <div>
    <div v-if="readOnly" class="avatar-uploader" :style="{ width: width + 'px', height: height + 'px' }">
      <div v-if="imageUrl" class="image-container" @click.stop>
        <img :src="`${baseUrl}/file/file/previewByCode/${imageUrl}`" class="avatar" :style="{ width: width + 'px', height: height + 'px' }">
        <div class="icon-wrapper" :class="{ readonly: readOnly }">
          <span class="icon-item preview" @click="handlePreview">
            <el-tooltip
                content="查看"
                placement="top"
                effect="dark"
              >
              <el-button type="primary" size="mini" class="el-icon-zoom-in"></el-button>
            </el-tooltip>
          </span>
        </div>
      </div>
      <i v-else class="el-icon-plus avatar-uploader-icon"></i>
    </div>
    <el-upload
      v-else
      class="avatar-uploader"
      :action="action"
      :show-file-list="false"
      :on-success="handleAvatarSuccess"
      :before-upload="beforeAvatarUpload"
      :style="{ width: width + 'px', height: height + 'px' }"
    >
      <div v-if="imageUrl" class="image-container" @click.stop>
        <img :src="`${baseUrl}/file/file/previewByCode/${imageUrl}`" class="avatar" :style="{ width: width + 'px', height: height + 'px' }">
        <div class="icon-wrapper" :class="{ readonly: readOnly }">
          <span class="icon-item preview" @click="handlePreview">
            <el-tooltip
                content="查看"
                placement="top"
                effect="dark"
              >
                <el-button type="primary" size="mini" class="el-icon-zoom-in"></el-button>
            </el-tooltip>
          </span>
          <span class="icon-item reupload" @click="handleReUpload">
            <el-tooltip
                content="重新上传"
                placement="top"
                effect="dark"
              >
                <el-button type="primary" size="mini" class="el-icon-upload"></el-button>
            </el-tooltip>
          </span>
        </div>
      </div>
      <i v-else class="el-icon-plus avatar-uploader-icon"></i>
    </el-upload>

    <!-- 图片预览遮罩层 -->
    <div v-show="previewVisible" class="image-preview-modal" @click="previewVisible = false">
      <img :src="previewImage" alt="预览图" class="preview-image" @click.stop>
    </div>
  </div>
</template>

<script>
export default {
  name: "ImageUpload",
  data() {
    return {
      baseUrl: this.$global.baseUrl,
      // 预览控制
      previewVisible: false,
      previewImage: ''
    };
  },
  props: {
    imageUrl: {
      type: String,
      default: ""
    },
    action: {
      type: String,
      default: ''
    },
    width: {
      type: [String, Number],
      default: 178
    },
    height: {
      type: [String, Number],
      default: 178
    },
    readOnly: {
      type: Boolean,
      default: false
    },
    // 图片尺寸校验
    maxWidth: {
      type: Number,
      default: null
    },
    maxHeight: {
      type: Number,
      default: null
    },
  },
  methods: {
    handleAvatarSuccess(res, file) {
      const data = res.data || res;
      let imageUrl = '';
      if (typeof data === 'string') {
        imageUrl = data;
      } else if (data.url) {
        imageUrl = data.url;
      }
      this.$emit('upload-success', data);
    },
    beforeAvatarUpload(file) {
      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/bmp';
      const isLt2M = file.size / 1024 / 1024 < 10;

      if (!isJPG) {
        this.$message.error('上传图片只能是 JPG、PNG、GIF、BMP 格式!');
        return false;
      }
      if (!isLt2M) {
        this.$message.error('上传图片大小不能超过 10MB!');
        return false;
      }

      // 图片尺寸校验
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = (e) => {
          const img = new Image();
          img.onload = () => {
            const { width, height } = img;
            let isValid = true;
            let errorMessage = '';

            // 如果上传尺寸校验的props不存在,使用展示尺寸作为默认值
            const effectiveMaxWidth = this.maxWidth !== null ? this.maxWidth : this.width;
            const effectiveMaxHeight = this.maxHeight !== null ? this.maxHeight : this.height;

            if (width !== effectiveMaxWidth) {
              errorMessage = `仅支持上传png/jpg/jpeg文件,上传尺寸为${effectiveMaxWidth}px*${effectiveMaxHeight}px`;
              isValid = false;
            } else if (height !== effectiveMaxHeight) {
              errorMessage = `仅支持上传png/jpg/jpeg文件,上传尺寸为${effectiveMaxWidth}px*${effectiveMaxHeight}px`;
              isValid = false;
            }
            if (!isValid) {
              this.$message.error(errorMessage);
              reject(new Error(errorMessage));
            } else {
              resolve(true);
            }
          };
          img.src = e.target.result;
        };
        reader.readAsDataURL(file);
      });
    },
    // 原生预览,无任何依赖
    handlePreview() {
      if (!this.imageUrl) return;
      this.previewImage = `${this.baseUrl}/file/file/previewByCode/${this.imageUrl}`;
      this.previewVisible = true;
    },
    handleReUpload() {
      if (this.readOnly) return;
      const uploadInput = this.$el.querySelector('.el-upload__input');
      if (uploadInput) {
        uploadInput.click();
      }
    }
  }
};
</script>

<style scoped>
.avatar-uploader {
  border: none;
  border-radius: 6px;
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f5f7fa;
}

.avatar-uploader:hover {
  cursor: pointer;
}

.image-container .icon-wrapper {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  opacity: 0;
  transition: opacity 0.3s;
  z-index: 10;
}
.image-container:hover .icon-wrapper {
  opacity: 1;
}

.image-container .icon-wrapper.readonly {
  opacity: 1;
  background: transparent;
  justify-content: center;
}
.image-container .icon-wrapper.readonly .icon-item {
  background: rgba(0, 0, 0, 0.4);
}

.icon-item {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  cursor: pointer;
  font-size: 16px;
  transition: all 0.2s;
}
.icon-item:hover {
  background: #409EFF;
  transform: scale(1.1);
}

/* 按钮样式 */
.icon-item .el-button {
  border: none;
  border-radius: 50%;
  width: 32px;
  height: 32px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent !important;
  color: #fff !important;
  box-shadow: none !important;
}

.icon-item .el-button:hover {
  background: transparent !important;
  color: #fff !important;
  box-shadow: none !important;
}

.icon-item .el-button:focus {
  background: transparent !important;
  color: #fff !important;
  box-shadow: none !important;
}

.image-container {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.avatar-uploader img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: default;
}

.avatar-uploader .el-upload {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  text-align: center;
}

.avatar {
  display: block;
}

/* 图片预览样式 */
.image-preview-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(0, 0, 0, 0.5);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: zoom-out;
}
.preview-image {
  max-width: 90%;
  max-height: 90%;
  cursor: default;
  object-fit: contain;
}
</style>

图片:

引用方式:

javascript 复制代码
			<el-form-item prop="douyinQrcode" label="抖音二维码">
                <image-upload
                  v-model="douyinQrcode"
                  :image-url="douyinQrcode"
                  :width="90"
                  :height="90"
                  :action="baseUrl + '/file/file/upload/banner'"
                  @upload-success="handleDouyinQrcodeSuccess"
                ></image-upload>
                <p class="hint">仅支持上传png/jpg/jpeg文件,上传尺寸为90*90px,图片大小不超过10M</p>
              </el-form-item>
相关推荐
广师大-Wzx2 小时前
JavaWeb:前端部分
java·前端·javascript·css·vue.js·前端框架·html
M ? A2 小时前
你的 Vue v-memo 与 v-once,VuReact 会编译成什么样的 React 代码?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
耿雨飞2 小时前
Python 后端开发技术博客专栏 | 第 03 篇 面向对象编程进阶 -- 从 SOLID 原则到 Python 特色 OOP
开发语言·python·面向对象·oop
charlie1145141912 小时前
嵌入式现代C++工程实践——第14篇:第二次重构 —— 模板登场,编译时绑定端口和引脚
开发语言·c++·stm32·安全·重构
源码站~2 小时前
基于python的校园代跑(跑腿)系统
开发语言·python
BugShare2 小时前
一个用 Rust 编写的、速度极快的 Python 包和项目管理器
开发语言·python·rust
耿雨飞2 小时前
Python 后端开发技术博客专栏 | 第 04 篇 Python 内存管理与垃圾回收 -- 从引用计数到分代回收
开发语言·python·垃圾回收
雾岛听蓝2 小时前
Qt 输入与多元素控件详解
开发语言·经验分享·笔记·qt
执笔画流年呀2 小时前
多线程及其特性
java·服务器·开发语言