vue图片上传组件封装

956 阅读1分钟

前言

最近做了一个后台,上传商品图片时,要求有以下限制

  • 上传图片不大于2M
  • 可自定义上传图片的比例
  • 图片有拖拽功能

接下来并是我们的实现过程

上传图片不大于2M

<template lang="">
  <div>
      <el-upload ref="uploadRef" slot="footer" :action="imgConfig.action" list-type="picture-card" multiple
        :file-list="imgConfig.fileList" accept="image/jpeg,image/jpg,image/gif,image/png,image/svg"
        :before-upload="beforeUpload" :on-success="handleImageSuccess" :on-remove="removeImage"
        :headers="imgConfig.headers" :limit='imgConfig.limit' name="images">
        <i class="el-icon-plus"></i>
      </el-upload>
  </div>
</template>

<script>
  import vuedraggable from 'vuedraggable';
  export default {
    props: {
      imgConfig: {
        action: String, // 图片上传路径
        headers: Object, // 请求头
        fileList: Array, // 图片列表
        limit: Number, // 现在张数
        width: Number, // 宽高占比
        height: Number
      },
    },

    data() {
      return {
        isCanUpload: false,
        isFirstMount: true // 控制防止重复回显
      }
    },
    created() {
    },
    mounted() {

    },
    methods: {
      removeImage(file, fileList) {
        this.imgConfig.imgList = fileList;
      },
      beforeUpload(file) {
        this.isFirstMount = false
        const _self = this;
        const isIMAGE = file.type === 'image/jpeg' || 'image/jpg' || 'image/gif' || 'image/png' || 'images/svg';
        const isLt2M = file.size / 1024 / 1024 < 2;
        if (!isIMAGE) {
          this.$message.error('上传文件只能是图片格式!');
        }
        if (!isLt2M) {
          this.$message.error('上传文件大小不能超过 2MB!');
        }
        return isIMAGE && isLt2M;
      },
    },
  }
</script>

自定义限制上传图片比例

改造beforeUpload 方法

beforeUpload(file) {
  this.isFirstMount = false
  const _self = this;
  const isIMAGE = file.type === 'image/jpeg' || 'image/jpg' || 'image/gif' || 'image/png' || 'images/svg';
  const isLt2M = file.size / 1024 / 1024 < 0.5;
  if (!isIMAGE) {
    this.$message.error('上传文件只能是图片格式!');
  }
  if (!isLt2M) {
    this.$message.error('上传文件大小不能超过 2MB!');
  }
  var that = this;
  // 限制尺寸占比
  const isSize = new Promise(function (resolve, reject) {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = (theFile) => {
      const image = new Image();
      image.src = theFile.target.result;
      image.onload = function () {
        const { width, height } = image;
        if (that.imgConfig.width == null || that.imgConfig.height == null) {
          resolve();
        }
        let valid = width / height !== that.imgConfig.width / that.imgConfig.height;
        valid ? reject() : resolve();
      }
    }
  }).then(() => {
    return file;
  }, () => {
    that.$message.error(`图片尺寸宽高比应为:${that.imgConfig.width}:${that.imgConfig.height}!`);
    return Promise.reject();
  });
  return isIMAGE && isLt2M && isSize;
},

图片实现拖拽功能

实现思路

  • 引入拖拽功能
  • 隐藏之前的图片展示列表

引入拖拽插件 vuedraggable

yarn add vuedraggable

改造之后代码如下

<template lang="">
  <div>
    <vuedraggable tag="ul" v-model="imgConfig.fileList" class="draggable-box" draggable=".draggable-item"
      @start="onDragStart" @end="onDragEnd">
      <li v-for="(item,index) in imgConfig.fileList" :key="index + item.url" class="draggable-item">
        <el-image :src="item.url" :preview-src-list="[item.url]" style="width: 148px; height: 148px">
        </el-image>
        <div class="shadow" @click="onRemoveHandler(index)">
          <i class="el-icon-delete"></i>
        </div>
      </li>
      <el-upload ref="uploadRef" slot="footer" :action="imgConfig.action" list-type="picture-card" multiple
        :file-list="imgConfig.fileList" accept="image/jpeg,image/jpg,image/gif,image/png,image/svg"
        :before-upload="beforeUpload" :on-success="handleImageSuccess" :on-remove="removeImage"
        :headers="imgConfig.headers" :limit='imgConfig.limit' name="images">
        <i class="el-icon-plus"></i>
      </el-upload>
    </vuedraggable>
  </div>
</template>
<script>
  import vuedraggable from 'vuedraggable';
  export default {
    props: {
      imgConfig: {
        action: String, // 图片上传路径
        headers: Object, // 
        fileList: Array,
        limit: Number,
        width: Number,
        height: Number
      },
    },
    components: {
      vuedraggable
    },
    data() {
      return {
        isFirstMount: true // 控制防止重复回显
      }
    },
    created() {
    },
    mounted() {
      if (this.imgConfig.fileList.length > 0) {
        this.syncElUpload()
      }
    },

    methods: {
      // 同步el-upload数据
      syncElUpload(val) {
        const imgList = val || this.imgConfig.fileList
        this.$refs.uploadRef.uploadFiles = imgList.map((v, i) => {
          return {
            name: 'pic' + i,
            url: v.url,
          }
        })
        this.isFirstMount = false
      },
      // 移除单张图片
      onRemoveHandler(index) {
        this.$confirm('确定删除该图片?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
          .then(() => {
            this.imgConfig.fileList = this.imgConfig.fileList.filter((v, i) => {
              return i !== index
            })
          })
          .catch(() => { })
      },
      onDragStart(e) {
        e.target.classList.add('hideShadow')
      },
      onDragEnd(e) {
        e.target.classList.remove('hideShadow')
      },



      handleImageSuccess(res, file, fileList) {
        if (res.code != 200) {
          this.$message.error(res.message);
          return false;
        }
        fileList.forEach(item => {
          if (item.response)
            item.url = item.response.data;
        });
        this.imgConfig.fileList = fileList;
      },

      removeImage(file, fileList) {
        this.imgConfig.imgList = fileList;
      },

      beforeUpload(file) {
        this.isFirstMount = false
        const _self = this;
        const isIMAGE = file.type === 'image/jpeg' || 'image/jpg' || 'image/gif' || 'image/png' || 'images/svg';
        const isLt2M = file.size / 1024 / 1024 < 0.5;
        if (!isIMAGE) {
          this.$message.error('上传文件只能是图片格式!');
        }
        if (!isLt2M) {
          this.$message.error('上传文件大小不能超过 2MB!');
        }
        var that = this;
        // 限制尺寸占比
        const isSize = new Promise(function (resolve, reject) {
          const reader = new FileReader();
          reader.readAsDataURL(file);
          reader.onload = (theFile) => {
            const image = new Image();
            image.src = theFile.target.result;
            image.onload = function () {
              const { width, height } = image;
              if (that.imgConfig.width == null || that.imgConfig.height == null) {
                resolve();
              }
              let valid = width / height !== that.imgConfig.width / that.imgConfig.height;
              valid ? reject() : resolve();
            }
          }
        }).then(() => {
          return file;
        }, () => {
          that.$message.error(`图片尺寸宽高比应为:${that.imgConfig.width}${that.imgConfig.height}!`);
          return Promise.reject();
        });
        return isIMAGE && isLt2M && isSize;
      },
    },
  }
</script>
<style lang="scss">
  .draggable-box {
    display: flex;
    flex: wrap;

    .el-upload-list--picture-card {
      display: none; // 隐藏之前的图片列表
    }
  }

  .draggable-item {
    margin-right: 5px;
    margin-bottom: 5px;
    border: 1px solid #ddd;
    border-radius: 6px;
    position: relative;
    overflow: hidden;
    width: 148px;
    height: 148px;

    .shadow {
      position: absolute;
      top: 0;
      right: 0;
      background-color: rgba(0, 0, 0, .5);
      opacity: 0;
      transition: opacity .3s;
      color: #fff;
      font-size: 20px;
      line-height: 20px;
      padding: 2px;
      cursor: pointer;
    }

    &:hover {
      .shadow {
        opacity: 1;
      }
    }

    .el-upload-list__item .is-success {
      display: none;
    }
  }
</style>