随手记:上传组件,类型支持图片,视频;功能支持查看,放大缩小,旋转,拖拽,添加水印

<template>
  <div class="component-upload-image">
    <el-upload
      ref="uploadImage"
      :action="uploadImgUrl"
      :list-type="listType"
      :headers="headers"
      :data="params"
      :on-success="handleUploadSuccess"
      :before-upload="handleBeforeUpload"
      :on-error="handleUploadError"
      :on-remove="removeImage"
      :on-preview="handlePreview"
      :on-exceed="handleExceed"
      :file-list="imageList"
      :multiple="multiple"
      :limit="limit"
      :disabled="disabled"
      :show-file-list="showFileList"
      :accept="accept"
      :class="{
        'up-img-medium': size == 'medium', 
        'up-img-small': size == 'small', 
        'up-img-mini': size == 'mini',
        'hide-add-icon': !addShow || fileList.length === limit || disabled,
        'hide-del-icon': !delShow,
        'hide-status-icon': disabled
      }"
    >
      <i class="el-icon-plus"></i>
    </el-upload>
    <el-dialog :visible.sync="dialogVisible" title="" width="100%" append-to-body class="upload-prev-image">
      <div class="image-box" ref="maskBox" @mousedown="onmousedownHandle">
        <img v-if="dialogVisible" id="dialogImage" :src="dialogImageUrl" @wheel="handleWheel" @drag="handleDrag"  class="pre-img" :class="{'pre-img-max': originalScale}" :style="{
        'transform': 'scale(' + scaleTimes + ') rotate(' + rotateAngle + 'deg) translate(' + translateX + 'px, ' + translateY + 'px)','top': top + 'px', 'left': left + 'px',
      }">
      </div>
      <div class="handle-list">
        <div class="handle-list-btn">
          <i class="el-icon-zoom-out" @click="handleReduce"></i>
          <i class="el-icon-zoom-in" @click="handleAmplify"></i>
          <i :class="originalScale ? 'el-icon-full-screen' : 'el-icon-c-scale-to-original'" @click="originalScale = !originalScale"></i>
          <i class="el-icon-refresh-left" @click="handleAnticlockwise"></i>
          <i class="el-icon-refresh-right" @click="handleClockwise"></i>
          <i class="el-icon-arrow-left" v-if="preImgList.length > 1" @click="handleBack"></i>
          <i class="el-icon-arrow-right" v-if="preImgList.length > 1" @click="handleNext"></i>
        </div>
      </div>
    </el-dialog>
  </div>
</template>

<script>
/**
 * 
 * 上传使用方法:
 * <ImageUpload :limit="3" :fileList="form.imgList" :omen="1" />
 * 
 * 预览使用方法:
 * <ImageUpload :fileList="[detail.imgurl]" :disabled="true" />
 * 
 * fileList 对应的值需为数组 1:['','',...]  或  2:[{name:'',url:''}]
 * 
 */
import { getToken } from "@/utils/auth";
import * as imageConversion from 'image-conversion';
export default {
  props: {
    // 是否多选
    multiple: {
      type: Boolean,
      default: true
    },
    // value: text/picture/picture-card
    listType: {
      type: String,
      default: "picture-card"
    },
    fileList: {
      type: Array,
      default: () => {
        return []
      }
    },
    limit: {
      type: Number,
      default: 10
    },
    // 上传格式
    accept: {
      type: String,
      default: ".jpeg,.jpg,.png"
    },
    showFileList: {
      type: Boolean,
      default: true
    },
    // 默认是返回url数组 ['','']  否则为返回对象 {name: '', url: ''} 
    returnObj: {
      type: Boolean,
      default: false
    },
    // 大小 medium small mini
    size: {
      type: String,
      default: "mini"
    },
    // 是否显示删除按钮
    delShow: {
      type: Boolean,
      default: true
    },
    // 是否显示添加按钮
    addShow: {
      type: Boolean,
      default: true
    },
    // 单张图片大小 默认10m 小于1m用小数点代替 例:500kb == 0.5
    omen: {
      type: Number,
      default: 10
    },
    disabled: {
      type: Boolean,
      default: false
    },
    // 上传时需要的参数
    params: {
      type: Object,
      default: function () {
        return {}
      }
    },
    addressUrl: {
      type: String,
      default: '/tool/oss/upload'
    },

    // 预览开启水印(false禁止true允许)
    WaterMarkShow: {
      type: Boolean,
      default: false
    },
    // 预览水印配置
    objmsg: {
      type: Object,
      default: function () {
        return {
          rotate: 20, //旋转角度  默认20
          fontcolor: "255, 255, 255, 0.8", //字体颜色  rgba类型  默认 255, 255, 255, 0.2(黑色)
          density: 3, //稠密度    数值越大,水印越多
          str: ["XXX专用"], //水印文字 数组类型  最大三行(即lingth<=3)[必传]
        }
      }
    },
    // 图片预览数组
    previewList: {
      type: Array,
      default: () => {
        return []
      }
    },
    // 压缩宽度
    compressWidth: {
      type: Number,
      default: 300
    },
    // 压缩高度
    compressHeight: {
      type: Number,
      default: 300
    },
    // 是否压缩尺寸
    compressSize: {
      type: Boolean,
      default: false
    },
  },
  data() {
    return {
      OSSURL: process.env.VUE_APP_BASE_FILEURL,
      dialogVisible: false,
      uploadImgUrl: process.env.VUE_APP_BASE_API + this.addressUrl, // 上传的图片服务器地址
      headers: {
        Authorization: "Bearer " + getToken(),
        // 'X-Route-Label': 'quanzai'
      },
      dialogImageUrl: '',
      imageList: [],
      count: 0,
      scaleTimes: 1, // 缩放倍数
      rotateAngle: 0, // 旋转角度
      previewIndex: 0, // 当前预览图片下标
      originalScale: true, // 是否原始比例
      translateX: 0, // 横向平移距离
      translateY: 0, // 纵向平移距离
      preImgList: [],
      top: 0,
      left: 0,

    };
  },
  watch: {
    fileList: {
      handler(val) {
        if (val) {
          if (this.fileList.length > 0 && !this.fileList[0].url && this.count === 0) { // 默认只执行一次,避免显示重复
            this.formatFileList();
            this.count = 1;
          }
        } else {
          this.imageList = [];
          return [];
        }
        // if (!val.length && this.$refs.uploadImage) {
        //   this.$refs.uploadImage.clearFiles();
        // }
      },
      deep: true,
      immediate: true
    },
    previewList: {
      handler(val) {
        if(this.previewList.length) {
          this.preImgList = JSON.parse(JSON.stringify(this.previewList));
        }else {
          this.preImgList = [];
        }
      }
    }
  },
  created() {
    // if (this.fileList.length > 0 && !this.fileList[0].url) {
    //   this.formatFileList()
    // }
  },
  methods: {
    // 清空图片
    clearFiles() {
      this.$refs.uploadImage.clearFiles();
    },
    // 格式化图片
    formatFileList() {      
      this.fileList.forEach(v => {
        let params = {          
          name: '',
          url: v
        }
        if (typeof v == 'object'){
          params.id = v.id;
          params.url = v.qrcodeUrl;
        }
        this.imageList.push(params)
      })
      console.log(this.imageList)
    },

    // 移除图片
    removeImage(e, a) {      
      this.$emit("on-remove", e);      
      let index = this.fileList.findIndex(v => v == e.url)
      this.fileList.splice(index, 1)
      if(!this.fileList.length) {
        this.$refs.uploadImage.clearFiles();
      }
      console.log(e, a)
    },

    // 上传成功
    handleUploadSuccess(res, e) {
      if (this.returnObj) { // 返回对象
        this.fileList.push({
          name: e.name,
          url: this.OSSURL + res.msg
        })
      }else { // 返回地址数据
        this.fileList.push(this.OSSURL + res.msg)
      }
      this.$emit("on-success", this.OSSURL + res.msg);
      this.loading.close();
    },

    // 获取文件后缀
    getFileSuffix(fileName) {
      return fileName.match(/\.\w+$/)[0].toLowerCase();
    },

    // 上传中
    handleBeforeUpload(file) {
      if (!this.accept.includes(this.getFileSuffix(file.name))) {
        this.$message.error("请上传【" + this.accept + "】格式的图片");
        return false;
      }
      if (file.size / 1024 > (this.omen * 1024)) {
        let unit = '', om = ''
        if (this.omen < 1) {
          unit = 'KB'
          om = this.omen * 1000
        } else {
          unit = 'MB'
          om = this.omen
        }
        this.$message.error(`图片大小不能超过${om}${unit}`);
        return false;
      }
      this.loading = this.$loading({
        lock: true,
        text: "上传中",
        background: "rgba(0, 0, 0, 0.7)",
      });
      let index = this.accept.indexOf('.png');
      if(this.returnObj || index == -1) return;
      return new Promise((resolve, reject) => {
        let _URL = window.URL || window.webkitURL;
        let isLt1M = file.size / 1024 / 1024 > 1; // 判定图片大小是否大于1MB
        // 这里需要计算出图片的长宽
        let img = new Image();
        img.onload = ()=> {
          if(this.compressSize) {
            file.width = img.width; // 获取到width放在了file属性上
            file.height = img.height; // 获取到height放在了file属性上
            let valid = img.width > this.compressWidth || img.height > this.compressHeight; // 图片宽度/高度超出
            // 这里我只判断了图片的宽度,compressAccurately有多个参数时传入对象
            if (valid || isLt1M) {
              imageConversion.compressAccurately(file, { size: 100, width: this.compressWidth, height: this.compressHeight }).then(res => {
                resolve(res);
              })
            } else resolve(file);
          }else {
            imageConversion.compress(file, 0.85).then(res=>{
              resolve(res);
            })
          }
        } 
        // 需要把图片赋值
        img.src = _URL.createObjectURL(file);
      })
    },

    // 上传失败
    handleUploadError() {
      this.$message({
        type: "error",
        message: "上传失败",
      });
      this.loading.close();
    },

    // 超出提示
    handleExceed() {
      this.$message({
        type: "error",
        message: "超出上传文件数量,最多上传" + this.limit + '张',
      });
    },

    // 预览图片
    handlePreview(file) {
      this.loading = this.$loading({
        lock: true,
        text: "加载中",
        background: "rgba(0, 0, 0, 0.7)",
      });
      this.dialogImageUrl = '';
      if(!this.WaterMarkShow) {
        if(file.response) {
          this.dialogImageUrl = this.editFormatFileUrl(file.response.msg);
        }else {
          this.dialogImageUrl = file.url;
        }
      };
      this.dialogVisible = true;

      if(file.resetIndex !== 1) { // 点击图片预览
        this.previewIndex = 0;
      }
      if(!this.WaterMarkShow) {
        this.loading.close();
        this.$nextTick(() => {
          let node = document.getElementById('dialogImage');
          this.left = (window.innerWidth/ 2) - (node.width/2) < 0 ? 0 : (window.innerWidth/ 2) - (node.width/2);
          this.top = (window.innerHeight/ 2) - (node.height/2) < 0 ? 0 : (window.innerHeight/ 2) - (node.height/2);
        })
        return false;
      };
      var drawWaterMark = {};
      drawWaterMark.init = (objmsg) => {
        console.log('--------------',objmsg)
          var canvas = document.createElement('canvas');
          var ctx = canvas.getContext('2d');
          var img = new Image();
          img.src = objmsg.imgpath;
          img.setAttribute("crossOrigin", 'Anonymous');
            img.onload = () => {
                //绘制和图片大小相同的canvas
                canvas.width = img.width;
                canvas.height = img.height;
                this.left = (window.innerWidth/ 2) - (img.width/2) < 0 ? 0 : (window.innerWidth/ 2) - (img.width/2);
                this.top = (window.innerHeight/ 2) - (img.height/2) < 0 ? 0 : (window.innerHeight/ 2) - (img.height/2);
                //canvas绘制图片,0 0  为左上角坐标原点
                ctx.drawImage(img, 0, 0);
                //绘制水印
                if (objmsg.rotate != undefined && objmsg.rotate != null) {//旋转角度[默认20]
                    ctx.rotate((Math.PI / 120) * -objmsg.rotate);
                } else {
                    ctx.rotate((Math.PI / 120) * -20);
                };
                var fontsize = 20;
                // if (objmsg.fontsize != undefined && objmsg.fontsize != null) { //字体大小[默认20px]
                //     fontsize = objmsg.fontsize;
                // };
                if (img.width >= 3456) { // 根据图片大小改变水印文案字体大小
                    fontsize = 50;
                } else if (img.width >= 2700) {
                    fontsize = 30;
                } else if (img.width >= 2000) {
                    fontsize = 26;
                } else if (img.width >= 1436) {
                    fontsize = 20;
                } else if (img.width >= 800) {
                    fontsize = 12;
                } else if (img.width >= 500) {
                    fontsize = 10;
                } else {
                    fontsize = 8;
                };
                ctx.font = fontsize + "px Microsoft Yahei";
                var fontcolor = '255, 255, 255, 0.2';
                if (objmsg.fontcolor != undefined && objmsg.fontcolor != null) {//字体颜色透明度[默认白色]
                    fontcolor = objmsg.fontcolor;
                };
                ctx.fillStyle = "rgba(" + fontcolor + ")";
                ctx.textAlign = "center";
                ctx.textBaseline = "middle";
                var density = 3;
                if (objmsg.density != undefined && objmsg.density != null) {//稠密度[默认3]
                    density = objmsg.density
                };
                let maxpixel;
                let minPixel;
                if(img.width > img.height) {
                  maxpixel = img.width;
                  minPixel = img.height;
                }else {
                  maxpixel = img.height;
                  minPixel = img.width
                };
                for (var i = -1000; i < maxpixel; i += minPixel / density) {
                    for (var k = 0; k < maxpixel; k += minPixel / density) {
                        var str = objmsg.str;
                        if (str.length == 1) {
                            ctx.fillText(str[0], i, k);
                        } else if(str.length==2){
                            ctx.fillText(str[0], i, k);
                            ctx.fillText(str[1], i, k + (fontsize-0+5));//多行
                        } else if (str.length == 3 || str.length > 3) {
                            ctx.fillText(str[0], i, k);
                            ctx.fillText(str[1], i, k + (fontsize - 0 + 5));//多行
                            ctx.fillText(str[2], i, k + (fontsize*2 - 0 + 5));//多行
                        }
                    }
                };
                var base64 = canvas.toDataURL("image/jpeg");//添加过水印的base64图片
                if (objmsg.domid != undefined && objmsg.domid != null) {//id图片
                  let url1 = this.getBase64URL(base64)
                  this.dialogImageUrl = url1;

                  let preUrl = '';
                  if(file.response) {
                    preUrl = this.editFormatFileUrl(file.response.msg);
                  }else {
                    preUrl = file.url;
                  }
                  let preIndex = this.preImgList.findIndex(v => v == preUrl);
                  this.preImgList[preIndex] = this.dialogImageUrl;
                  this.loading.close();
                };
                if (objmsg.cb != undefined && objmsg.cb != null) {//回调函数
                  objmsg.cb(base64);//回调函数
                  this.loading.close();
                }
            }
           
      };
      let that = this;
      var xhr = new XMLHttpRequest();
      xhr.open('get', file.url, true);
      // 设置请求头(这一步得设置不然oss图片还是跨域)
      xhr.setRequestHeader("Cache-Control", "no-cache");
      xhr.responseType = 'blob';
      xhr.onload = function () {
        if (this.status == 200) {
          let imgFile =  URL.createObjectURL(this.response);
          that.objmsg.imgpath = imgFile; //需要添加水印图片路径  [必传]
          that.objmsg.domid = "dialogImage"; //图片dom的id   用来更换添加水印后的图片
          drawWaterMark.init(that.objmsg);
        }
      };
      xhr.send();
    },  
    // 将文件转为url格式
    getBase64URL(pic) {
        const blob = this.base64ImgtoFile(pic)
        const blobUrl = window.URL.createObjectURL(blob);
        return blobUrl
    },
    base64ImgtoFile (dataurl, filename = 'file') {
        //将base64格式分割:['data:image/png;base64','XXXX']
        const arr = dataurl.split(',')
        // .*? 表示匹配任意字符到下一个符合条件的字符 刚好匹配到:
        // image/png
        const mime = arr[0].match(/:(.*?);/)[1]  //image/png
        //[image,png] 获取图片类型后缀
        const suffix = mime.split('/')[1] //png
        const bstr = atob(arr[1])   //atob() 方法用于解码使用 base-64 编码的字符串
        let n = bstr.length
        const u8arr = new Uint8Array(n)
        while (n--) {
            u8arr[n] = bstr.charCodeAt(n)
        }
        return new File([u8arr], `${filename}.${suffix}`, {
            type: mime
        })
    },
    // 清空预览数组 
    handleClearList() {
      this.imageList = [];
    },

    // 预览图片鼠标滚轮事件
    handleWheel(e) {
      if(e.deltaY < 0) { // 向下滚动 放大图片
        this.scaleTimes = this.accAdd(this.scaleTimes, 0.015);
      }else { // 向上滚动 缩小图片
        if(this.scaleTimes > 0.2) {
          this.scaleTimes = this.accSub(this.scaleTimes, 0.015);
        }
      }
    },
    // 缩小按钮操作
    handleReduce() {
      if(this.scaleTimes > 0.2) {
        this.scaleTimes = this.accSub(this.scaleTimes, 0.2);
      }
    },
    // 放大按钮操作
    handleAmplify() {
      this.scaleTimes = this.accAdd(this.scaleTimes, 0.2);
    },
    // 逆时针旋转
    handleAnticlockwise() {
      this.rotateAngle = this.accSub(this.rotateAngle, 90);
    },
    // 顺时针旋转
    handleClockwise() {
      this.rotateAngle = this.accAdd(this.rotateAngle, 90);
    },
    // 上一张
    handleBack() {
      if(this.previewIndex == 0) {
        this.previewIndex = this.preImgList.length - 1;
      }else {
        let index = this.preImgList.findIndex(v => v == this.dialogImageUrl);
        this.previewIndex  = index - 1;
      }
      console.log(this.preImgList, this.previewIndex)
      this.scaleTimes = 1;
      this.rotateAngle = 0;
      this.originalScale = true;
      this.handlePreview({ url: this.preImgList[this.previewIndex], resetIndex: 1 });
    },
    // 下一张
    handleNext() {
      if(this.previewIndex == this.preImgList.length - 1) {
        this.previewIndex = 0;
      }else {
        let index = this.preImgList.findIndex(v => v == this.dialogImageUrl);
        this.previewIndex  = index + 1;
      }
      console.log(this.preImgList, this.previewIndex)
      this.scaleTimes = 1;
      this.rotateAngle = 0;
      this.originalScale = true;
      this.handlePreview({ url: this.preImgList[this.previewIndex], resetIndex: 1 });
    },
    handleDrag(e) {
      // console.log(e.clientX, e.clientY)
      // this.translateX = e.clientX;
      // this.translateY = e.clientY;
    },

    // 鼠标按下
    onmousedownHandle(e) {
      this.$refs.maskBox.onmousemove = (el) => {
        const ev = el || window.event; // 阻止默认事件
        ev.preventDefault();
        this.left += ev.movementX;
        this.top += ev.movementY;
      };
      this.$refs.maskBox.onmouseup = () => {
        // 鼠标抬起时将操作区域的鼠标按下和抬起事件置为null 并初始化
        this.$refs.maskBox.onmousemove = null;
        this.$refs.maskBox.onmouseup = null;
      };
      if (e.preventDefault) {
        e.preventDefault();
      } else {
        return false;
      }
    },

  }
};
</script>

<style lang="scss">

.up-img-medium {
  .el-upload--picture-card {
    line-height: 160px;
  }
  .el-upload--picture-card, .el-upload-list--picture-card .el-upload-list__item {
    width: 150px;
    height: 150px;
  }
}
.up-img-small {
  .el-upload--picture-card {
    line-height: 110px;
  }
  .el-upload--picture-card, .el-upload-list--picture-card .el-upload-list__item {
    width: 100px;
    height: 100px;
  }
}
.up-img-mini {
  .el-upload--picture-card {
    line-height: 66px;
  }
  .el-upload--picture-card, .el-upload-list--picture-card .el-upload-list__item {
    width: 60px;
    height: 60px;
  }
  .el-icon-plus {
    font-size: 23px;
  }
  .el-upload-list--picture-card .el-upload-list__item-actions span + span {
    margin-left: 8px;
  }
}

.hide-add-icon {
  .el-upload {
    display: none !important;
  }
}

.hide-del-icon {
  .el-upload-list__item-delete {
    display: none !important;
  }
}

.hide-status-icon {
  .el-upload-list__item-status-label {
    display: none !important;
  }
}

.upload-prev-image {
  .el-dialog {
    background: transparent;
    box-shadow: none;
    margin-top: 0 !important;
    margin-bottom: 0 !important;
  }
  .el-dialog__close {
    color: #fff;
    font-size: 35px;
    position: fixed;
    top: 20px;
    right: 20px;
    z-index: 10;
  }
  .el-dialog__header{
    padding: 0 !important;
  }
  .el-dialog__footer{
    display: none !important;
  }
  .el-dialog__body{
    height: 100vh;
    max-height: 100vh;
    padding: 0 !important;
    display: flex;
    justify-content: center;
    align-items: center;
    overflow: hidden;
  }
}

.el-upload-list--picture-card {
  line-height: 1.2;
}
.pre-img{
  transition: transform 0.3s ease 0s;
  display: block;
  margin: 0 auto;
}
.pre-img-max{
  max-width: 100%;
  max-height: 100%;
}
.handle-list{
  position: absolute;
  left: 50%;
  bottom: 30px;
  -webkit-transform: translateX(-50%);
  transform: translateX(-50%);
  width: 282px;
  height: 44px;
  padding: 0 23px;
  background-color: #606266;
  border-color: #FFFFFF;
  border-radius: 22px;
  .handle-list-btn{
    position: relative;
    height: 100%;
    font-size: 23px;
    display: flex;
    justify-content: space-around;
    align-items: center;
    z-index: 1;
    color: #FFFFFF;
    opacity: 0.8;
    i{
      cursor: pointer;
    }
  }
}
.el-loading-mask{
  z-index: 99999 !important;
}

.image-box {
  position: relative;
  width: 100%;
  height: 100vh;
  margin: 0 auto;
  border: 1px solid #333;
  overflow: hidden;
}
.image-box img {
  position: absolute;
  cursor: pointer;
}

</style>

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

peachSoda7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值