基于Vue+ElementUi的全能图片上传公用组件

基于Vue+ElementUi的全能图片上传公用组件

集多图上传,图片大小格式限制,图片压缩,图片裁剪功能为一体

<template>
  <!-- 图片上传公共组件 -->
  <div id="uploadimg"
       class="uploadimg"
       :style="styleVar">
    <ul class="imgList">
      <li v-for="(item,i) in imgList"
          :key="i"
          :style="{'margin-bottom':limit>1?'16px':''}">
        <img :src="item.url">
        <el-progress v-show="!item.url"
                     type="circle"
                     :width="parseInt(imgShowWidth)-20"
                     :text-inside="false"
                     :percentage="progressList[i]"></el-progress>
        <span><i class="el-icon-zoom-in"
             @click="elImagSelect=i,elImageList=imgList.map(arry=>(arry.url)),$refs.previewImg.showViewer=true"></i>
          <i class="el-icon-delete"
             @click="imgDelete(i)"></i>
        </span>
        <p v-show="imgName">{{item.name}}</p>
      </li>
      <li v-show="imgList.length<limit">
        <el-upload action=""
                   :multiple="limit>1&&!tailoring"
                   :accept="accept"
                   :show-file-list="false"
                   :before-upload="beforeAvatarUpload"
                   :http-request="httpRequestImagePath">
          <i class="el-icon-plus avatar-uploader-icon"></i>
          <span v-show="limit>1">{{imgList.length+'/'+limit}}</span>
        </el-upload>
      </li>
    </ul>

    <el-dialog title="图片剪裁"
               :visible.sync="dialogTailoring"
               append-to-body
               width="540px">
      <div class="cropper-content">
        <div class="cropper"
             style="text-align:center">
          <vueCropper ref="cropper"
                      :img="option.img"
                      :outputSize="option.outputSize"
                      :outputType="option.outputType"
                      :info="option.info"
                      :canScale="option.canScale"
                      :autoCrop="option.autoCrop"
                      :autoCropWidth="option.autoCropWidth"
                      :autoCropHeight="option.autoCropHeight"
                      :fixed="option.fixed"
                      :fixedBox="option.fixedBox"
                      :fixedNumber="option.fixedNumber"></vueCropper>
        </div>
      </div>
      <div slot="footer"
           class="dialog-footer">
        <el-button @click="dialogTailoring = false">取 消</el-button>
        <el-button type="primary"
                   @click="finishTailoring">确认</el-button>
      </div>
    </el-dialog>

    <el-image ref="previewImg"
              style="width:0;height:0"
              :src="elImageList[elImagSelect]"
              :preview-src-list="elImageList">
    </el-image>
  </div>

</template>

<script>
import { VueCropper } from 'vue-cropper'
import axios from 'axios'
import store from '@/store'
import { getToken } from '@/utils/auth'
export default {
  components: { VueCropper },
  name: 'UploadImg',
  props: {
    //图片类型
    accept: {
      type: String,
      default: '.jpg,.png,.jpeg,.gif'
    },
    //图片个数
    limit: {
      type: Number,
      default: 1
    },
    //图片大小
    imgSize: {
      type: Number,
      default: 10
    },
    //组件传值
    uploadImgFaToSo: {
      type: Array,
      default: () => []
    },
    //图片展示尺寸
    imgShowWidth: {
      type: String,
      default: '155px'
    },
    //图片是否裁剪
    tailoring: {
      type: Boolean,
      default: false
    },
    //是否显示图片名
    imgName: {
      type: Boolean,
      default: false
    },
  },
  data () {
    return {
      imgList: [],
      elImageList: [],
      elImagSelect: -1,
      uploadState: [],//上传状态
      progressList: [],//上传进度
      fileName: '',
      fileUid: '',
      dialogTailoring: false,
      option: {
        img: '', // 裁剪图片的地址
        info: true, // 裁剪框的大小信息
        outputSize: 1, // 裁剪生成图片的质量
        outputType: 'png', // 裁剪生成图片的格式
        canScale: true, // 图片是否允许滚轮缩放
        autoCrop: true, // 是否默认生成截图框
        autoCropWidth: 300, // 默认生成截图框宽度
        autoCropHeight: 300, // 默认生成截图框高度
        fixedBox: false, // 固定截图框大小 不允许改变
        fixed: true, // 是否开启截图框宽高固定比例
        fixedNumber: [1, 1], // 截图框的宽高比例
        full: true, // 是否输出原图比例的截图
        canMoveBox: false, // 截图框能否拖动
        original: false, // 上传图片按照原始比例渲染
        centerBox: true, // 截图框是否被限制在图片里面
        infoTrue: false, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
        canMove: true,
      },
    }
  },
  computed: {
    styleVar () {
      return {
        '--imgShowWidth': this.imgShowWidth,
      }
    }
  },
  mounted () {
    //监听组件显示隐藏
    const myObserver = new ResizeObserver(entries => {
      entries.forEach(entry => {
        if (entry.contentRect.width == 0) {
          this.imgList = []
          this.uploadState = []
          this.progressList = []
          this.$emit('uploadImgSoToFa', [{ name: '', url: '' }])
        }
      })
    })
    myObserver.observe(document.querySelector('#uploadimg'))
  },
  watch: {
    uploadImgFaToSo (val) {
      val.forEach(arry => {
        if (arry.url !== '' && arry.url !== null) {
          this.imgList.push(arry)
          this.uploadState.push(1)
          this.progressList.push(100)
        }
      });
    }
  },
  methods: {
    httpRequestImagePath (options) {
      if (this.tailoring) {
        this.fileName = options.file.name.substring(0, options.file.name.lastIndexOf('.')) + '.png'
        this.fileUid = options.file.uid
        this.$nextTick(() => {
          this.option.img = URL.createObjectURL(options.file);
          this.dialogTailoring = true
        })
      } else {
        this.imgList.push(options.file)
        this.uploadState.push(0)
        this.progressList.push(0)
        //当图片大于300k时先压缩再上传
        if (options.file.size > 307200) {
          this.compressImg(new File([options.file], options.file.name, { type: options.file.type })).then(file => {
            file.uid = options.file.uid
            this.imgUpload(file)
          });
        } else {
          this.imgUpload(options.file)
        }
      }
    },
    //图片上传
    imgUpload (file) {
      var form = new FormData()
      form.append('file', file)
      axios({
        url: '/auth/file/upload',
        method: 'post',
        data: form,
        baseURL: process.env.VUE_APP_SERVICE_URL,
        headers: {
          Authorization: getToken(),
          tenant: store.getters.Tenant
        },
        //获取上传进度
        onUploadProgress: progressEvent => {
          let progress = parseInt(progressEvent.loaded / progressEvent.total * 100)
          this.imgList.forEach((arry, index) => {
            if (file.uid == arry.uid) this.progressList.splice(index, 1, progress == 100 ? 99 : progress)
          });
        },
      }).then(res => {
        this.imgList.forEach((arry, index) => {
          if (file.uid == arry.uid) {
            this.imgList[index].url = res.data.data.url
            this.uploadState[index] = 1
            this.progressList.splice(index, 1, 100)
          }
        });
        //当所有文件上传完毕后再传给父组件
        if (this.uploadState.indexOf(0) == -1) this.$emit('uploadImgSoToFa', this.imgList)
      }).catch(err => {
        this.$message.error(err.msg);
      });
    },
    //文件删除
    imgDelete (i) {
      this.imgList.splice(i, 1)
      this.uploadState.splice(i, 1)
      this.progressList.splice(i, 1)
      if (this.imgList.length == 0) {
        this.$emit('uploadImgSoToFa', [{ name: '', url: '' }])
      } else {
        this.$emit('uploadImgSoToFa', this.imgList)
      }
    },
    //图片裁剪
    finishTailoring () {
      this.$refs.cropper.getCropBlob((data) => {
        let file = new File([data], this.fileName, { type: "image/png" })
        file.uid = this.fileUid
        this.dialogTailoring = false
        this.imgList.push(file)
        this.uploadState.push(0)
        this.progressList.push(0)
        this.imgUpload(file)
      })
    },
    //图片选择校验
    beforeAvatarUpload (file) {
      let fileName = file.name
      let fileType = fileName.substring(fileName.lastIndexOf('.'), fileName.length);
      if (this.imgList.length >= this.limit) {
        this.$message.warning('当前限制选择 ' + this.limit + ' 个文件');
        return false
      }
      if (this.accept.indexOf(fileType) == -1) {
        this.$message.error('上传图片只能是 ' + this.accept.replaceAll(',', '/').replaceAll('.', '') + ' 格式!');
        return false
      }
      if (file.size / 1024 / 1024 > this.imgSize) {
        this.$message.error('上传图片大小不能超过 ' + this.imgSize + 'MB!');
        return false
      }
    },
    //base64转file
    base64ToFile (base64, name) {
      var arr = base64.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new File([u8arr], name, { type: mime });
    },
    //压缩图片
    compressImg (file) {
      var vm = this;
      var files;
      var fileSize = parseFloat(parseInt(file['size']) / 1024 / 1024).toFixed(2);
      var read = new FileReader();
      read.readAsDataURL(file);
      return new Promise(function (resolve, reject) {
        read.onload = function (e) {
          var img = new Image();
          img.src = e.target.result;
          img.onload = function () {
            //默认按比例压缩
            var w = this.width,
              h = this.height;
            //生成canvas
            var canvas = document.createElement('canvas');
            var ctx = canvas.getContext('2d');
            var base64;
            // 创建属性节点
            canvas.setAttribute("width", w);
            canvas.setAttribute("height", h);
            ctx.drawImage(this, 0, 0, w, h);
            base64 = canvas.toDataURL(file['type'], 0.8);
            // 回调函数返回file的值(将base64编码转成file)
            files = vm.base64ToFile(base64, file.name); //如果后台接收类型为base64的话这一步可以省略
            resolve(files)
          };
        };
      })
    }
  }
}
</script>

<style lang="scss">
.uploadimg {
  ul {
    display: inline-flex;
    flex-wrap: wrap;
    margin: 0;
    padding: 0;
    vertical-align: top;
    .move {
      -webkit-animation: fadeOutUp 1s 0.2s ease both;
      -moz-animation: fadeOutUp 1s 0.2s ease both;
    }
    li:last-of-type {
      animation: none;
    }
    li {
      overflow: hidden;
      position: relative;
      width: var(--imgShowWidth);
      height: var(--imgShowWidth);
      margin-right: 10px;
      padding: 1px;
      list-style: none;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px dashed #c0ccda;
      border-radius: 6px;
      -webkit-animation: fadeInDown 1s 0.2s ease both;
      -moz-animation: fadeInDown 1s 0.2s ease both;
      img {
        max-width: 100%;
        max-height: 100%;
      }
      > span {
        position: absolute;
        width: 100%;
        height: 100%;
        left: 0;
        top: 0;
        cursor: default;
        display: flex;
        justify-content: space-around;
        color: #fff;
        opacity: 0;
        font-size: var(--imgShowWidth);
        background-color: rgba(0, 0, 0, 0.5);
        transition: opacity 0.3s;
        i {
          cursor: pointer;
          font-size: 0.25em;
          line-height: var(--imgShowWidth);
        }
      }
      p {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        margin: 0;
        text-align: center;
        font-size: 1em;
        line-height: 2;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
      }
    }
    li:hover span {
      opacity: 1;
    }
  }
  > div {
    display: inline-block;
  }
  .el-upload {
    position: relative;
    width: var(--imgShowWidth);
    height: var(--imgShowWidth);
    border: 1px dashed #c0ccda;
    border-radius: 6px;
    .el-icon-plus {
      width: 100% !important;
      height: 100% !important;
      font-size: 24px;
      line-height: var(--imgShowWidth) !important;
    }
    span {
      position: absolute;
      bottom: 10px;
      left: 0;
      width: 100%;
      line-height: 1;
      text-align: center;
    }
  }
}
.cropper-content {
  width: 500px;
  height: 500px;
  background: pink;
}
.cropper {
  width: 500px;
  height: 500px;
  background: yellow;
}
@-webkit-keyframes fadeOutUp {
  0% {
    opacity: 1;
    -webkit-transform: translateY(0);
  }
  100% {
    opacity: 0;
    -webkit-transform: translateY(-20px);
  }
}
@-moz-keyframes fadeOutUp {
  0% {
    opacity: 1;
    -moz-transform: translateY(0);
  }
  100% {
    opacity: 0;
    -moz-transform: translateY(-20px);
  }
}
@-webkit-keyframes fadeInDown {
  0% {
    opacity: 0;
    -webkit-transform: translateY(-20px);
  }
  100% {
    opacity: 1;
    -webkit-transform: translateY(0);
  }
}
@-moz-keyframes fadeInDown {
  0% {
    opacity: 0;
    -moz-transform: translateY(-20px);
  }
  100% {
    opacity: 1;
    -moz-transform: translateY(0);
  }
}
</style>

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Vue 中使用 ElementUI 进行图片上传,可以通过以下步骤来实现: 1. 首先,确保已经安装了 Vue.jsElementUI。 2. 创建一个 Vue 组件,用于处理图片上传功能。可以命名为 ImageUpload.vue。 3. 在 ImageUpload.vue 组件中引入 ElementUI 的上传组件,代码如下: ```html <template> <div> <el-upload action="/api/upload" :on-success="handleSuccess" :before-upload="handleBeforeUpload" :show-file-list="false" > <el-button type="primary" icon="el-icon-upload">点击上传图片</el-button> </el-upload> </div> </template> <script> export default { methods: { handleSuccess(response) { // 上传成功后的处理逻辑 console.log(response); }, handleBeforeUpload(file) { // 在上传之前的处理逻辑,比如限制图片类型和大小 const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'; const isLt2M = file.size / 1024 / 1024 < 2; if (!isJPG) { this.$message.error('上传图片只能是 JPG/PNG 格式!'); } if (!isLt2M) { this.$message.error('上传图片大小不能超过 2MB!'); } return isJPG && isLt2M; }, }, }; </script> ``` 4. 在上述代码中,`<el-upload>` 组件ElementUI 提供的上传组件。其中,`action` 属性指定了上传的 URL 地址,在这里可以根据需求进行修改;`on-success` 属性绑定了上传成功后的处理函数,可以在其中进行需要的处理;`before-upload` 属性绑定了上传之前的处理函数,可以在其中进行图片类型和大小的限制等处理;`show-file-list` 属性设置为 `false`,表示不显示已上传文件的列表。 5. 可以在 `<el-upload>` 标签内部添加其他元素和样式来进行页面布局和美化,以满足自己的需求。 6. 最后,将 ImageUpload.vue 组件引入到需要使用图片上传功能的页面中,并在相应的位置使用组件标签,即可实现图片上传功能。 以上就是使用 VueElementUI 进行图片上传的简单实现方式。根据实际需求,可以根据 ElementUI 上传组件的 API 进行更多的自定义和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值