<template>
  <el-dialog
    title="图片剪切"
    :visible.sync="isShowDialog"
    :before-close="handleClose"
    :submitHandle="confirm"
    width="580px"
  >
    <div class="el-cropper">
      <div class="el-cropper-clipping">
        <div class="el-cropper-clipping__picture">
          <img
              class="el-cropper-clipping__picture-img"
              :src="sourImgUrl"
              :style="sourImgStyle"
              draggable="false"
              @drag="preventDefault"
              @dragstart="preventDefault"
              @dragend="preventDefault"
              @dragleave="preventDefault"
              @dragover="preventDefault"
              @dragenter="preventDefault"
              @mousedown="startMoveImg"
              @mousemove="moveImg"
              @mouseup="createImg"
              @mouseout="createImg"
          >
          <div
            class="el-cropper-clipping__picture-shade el-cropper-clipping__picture-shade--left"
            :style="shadeStyle"
          />
          <div
            class="el-cropper-clipping__picture-shade el-cropper-clipping__picture-shade--right"
            :style="shadeStyle"
          />
        </div>
        <div class="el-cropper-clipping__range">
          <input
            type="range"
            :step="1"
            :min="0"
            :max="100"
            class="el-cropper-clipping__range-input"
            v-model="scalingRatio"
          >
          <span class="el-cropper-clipping__range-track" />
          <i
            class="el-cropper-clipping__range-icon el-cropper-clipping__range-icon--plus"
            @click="zoomImg(-1)"
          />
          <i
            class="el-cropper-clipping__range-icon el-cropper-clipping__range-icon--minus"
            @click="zoomImg(-2)"
          />
        </div>
      </div>
      <div class="el-cropper-preview">
        <div class="el-cropper-preview__title">图片预览：</div>
        <img
          :src="createImgUrl"
          :style="previewImgStyle"
          v-if="createImgUrl !== ''"
        >
      </div>
    </div>
    <canvas
      v-show="false"
      ref="canvas"
    />
  </el-dialog>
</template>
<script>
export default {
  data() {
    return {
      isShowDialog: false,
      confirmCount: 0,
      // 源图片的路径
      sourImgUrl: '',
      sourceImg: null,
      // canvas画布上的图片和预览的图片
      createImgUrl: '',
      // 手动缩放比例
      scalingRatio: 0,
      // 原图在初始化后，在裁剪框中的缩放比例
      sourceZoomRadius: 0,
      // 需求比例
      nRatio: this.width / this.height,
      mouseDown: false,
      // 容器高度，宽度
      sourceImgContainer: {
        w: 300,
        h: 240,
        // 容器比例
        r: 1.25
      },
      // 遮罩信息
      shadeInfo: {
        w: 0,
        h: 0,
        x: 0,
        y: 0
      },
      // 剪裁图片源的信息
      sourImgInfo: {
        x: 0,
        y: 0,
        width: 0,
        height: 0,
        top: 0,
        left: 0,
        screenX: 0,
        screenY: 0,
        initWidth: 0,
        initHeight: 0,
        naturalWidth: 0,
        naturalHeight: 0
      }
    };
  },
  props: {
    visible: {
      type: Boolean,
      default: false
    },
    // 图片源
    file: {
      type: [File, String],
      default: null
    },
    maxFileSize: {
      type: Number,
      default: 3000000
    },
    // 用来计算需求所需裁剪的比例
    width: {
      type: Number,
      default: 400
    },
    height: {
      type: [Number, String],
      default: 400
    },
    size: {
      type: Number
    }
  },
  computed: {
    shadeStyle() {
      let {
        w,
        h
      } = this.shadeInfo;
      if (this.nRatio < this.sourceImgContainer.r) {
        // 两边遮挡
        h = this.sourceImgContainer.h;
        w = (this.sourceImgContainer.w - h * this.nRatio) / 2;
        this.shadeInfo.w = w;
      } else {
        // 上下遮挡
        w = this.sourceImgContainer.w;
        h = (this.sourceImgContainer.h - w / this.nRatio) / 2;
        this.shadeInfo.h = h;
      }
      this.shadeInfo.x = w;
      this.shadeInfo.y = h;
      w = w + 'px';
      h = h + 'px';
      return {
        width: w,
        height: h
      };
    },
    sourImgStyle() {
      return {
        width: this.sourImgInfo.width + 'px',
        height: this.sourImgInfo.height + 'px',
        top: this.sourImgInfo.top + 'px',
        left: this.sourImgInfo.left + 'px'
      };
    },
    previewImgStyle() {
      return {
        width: '150px',
        height: 150 / this.nRatio + 'px'
      };
    }
  },
  watch: {
    visible(newVal) {
      this.isShowDialog = newVal;
      if (newVal) {
        this.setSourceImg();
      } else {
        this.clearData();
      }
    },
    // 缩放比例控制原图显示大小
    scalingRatio(n, o) {
      this.sourImgInfo.width = this.sourImgInfo.initWidth * (Number(n) + 100) / 100;
      this.sourImgInfo.height = this.sourImgInfo.initHeight * (Number(n) + 100) / 100;
      const {
        top,
        left,
        width,
        height
      } = this.sourImgInfo;
      const nTop = -height + this.sourceImgContainer.h - this.shadeInfo.h;
      this.sourImgInfo.top = top < nTop ? nTop : top;
      const nLeft = -width + this.sourceImgContainer.w - this.shadeInfo.w;
      this.sourImgInfo.left = left < nLeft ? nLeft : left;
      this.$nextTick(() => {
        if (this.visible) {
          this.createImg();
        }
      });
    }
  },
  methods: {
    // 设置源图片
    setSourceImg() {
      if (!this.file) {
        return;
      }
      // 图片上传成功后，再剪切时file是个字符串
      if (typeof this.file === 'string') {
        this.getImgFile(this.file);
      } else {
        this.readAsDataURLFun(this.file);
      }
    },
    // 读取file文件
    readAsDataURLFun(file) {
      // 图片未上传成功时返回，显示图片
      const fr = new FileReader();
      fr.onload = () => {
        this.sourImgUrl = fr.result;
        this.startCrop();
      };
      // 给FileReader对象一个读取完成的方法,使用readAsDataURL会返回一个url,这个值就保存在事件对象的result里,img通过加载这个地址,完成图片的加载
      fr.readAsDataURL(file);
    },
    // 裁剪前的准备，初始化原图 预览图
    startCrop() {
      // 预加载图片
      const img = new Image();
      img.src = this.sourImgUrl;
      // 图片加载事件
      img.onload = () => {
        const sourceRadius = img.naturalWidth / img.naturalHeight;
        // 图片比例与需求比例作比较 因为容器已经根据需求比例加了遮罩
        // this.nRatio图片的剪切比例
        if (sourceRadius < this.nRatio) {
          this.sourImgInfo.width = this.sourceImgContainer.w - this.shadeInfo.w * 2;
          this.sourImgInfo.height = this.sourImgInfo.width / sourceRadius;
          this.sourceZoomRadius = this.sourImgInfo.width / img.naturalWidth;
        } else {
          this.sourImgInfo.height = this.sourceImgContainer.h - this.shadeInfo.h * 2;
          this.sourImgInfo.width = this.sourImgInfo.height * sourceRadius;
          this.sourceZoomRadius = this.sourImgInfo.height / img.naturalHeight;
        }
        // 初始化的时候图片在容器中居中显示
        this.sourImgInfo.left = (this.sourceImgContainer.w - this.sourImgInfo.width) / 2;
        this.sourImgInfo.top = (this.sourceImgContainer.h - this.sourImgInfo.height) / 2;
        this.sourImgInfo.x = this.sourImgInfo.left;
        this.sourImgInfo.y = this.sourImgInfo.top;
        // 用来做图片缩放的
        this.sourImgInfo.initWidth = this.sourImgInfo.width;
        this.sourImgInfo.initHeight = this.sourImgInfo.height;
        this.sourImgInfo.naturalWidth = img.naturalWidth;
        this.sourImgInfo.naturalHeight = img.naturalHeight;
        if (this.visible) {
          this.$nextTick(() => {
            this.createImg();
          });
        }
      };
      this.sourceImg = img;
    },
    preventDefault(e) {
      e.preventDefault();
      return false;
    },
    // 开始移动前
    startMoveImg(e) {
      this.sourImgInfo.screenX = e.screenX;
      this.sourImgInfo.screenY = e.screenY;
      this.mouseDown = true;
    },
    // 移动过程中
    moveImg(e) {
      e.preventDefault();
      if (!this.mouseDown) {
        return;
      };
      const dX = (e.screenX - this.sourImgInfo.screenX) * 0.1;
      const dY = (e.screenY - this.sourImgInfo.screenY) * 0.1;
      let rX = this.sourImgInfo.left + dX;
      let rY = this.sourImgInfo.top + dY;
      // 右移
      if (dX > 0) {
        rX = rX > this.shadeInfo.w ? this.shadeInfo.w : rX;
      } else {
        // 左移
        const maxX = this.shadeInfo.w + 2 * (this.sourImgInfo.width - this.sourceImgContainer.w) / 2;
        rX = Math.abs(rX) > maxX ? -maxX : rX;
      }
      // 下移
      if (dY > 0) {
        rY = rY > this.shadeInfo.h ? this.shadeInfo.h : rY;
      } else {
        const maxY = this.shadeInfo.h + 2 * (this.sourImgInfo.height - this.sourceImgContainer.h) / 2;
        rY = Math.abs(rY) > maxY ? -maxY : rY;
      }
      this.sourImgInfo.top = rY;
      this.sourImgInfo.left = rX;
    },
    // 图片缩放
    zoomImg(e) {
      if (e === -1 && Number(this.scalingRatio) > 0) {
        this.scalingRatio = Number(this.scalingRatio) - 1;
      }
      if (e === -2 && Number(this.scalingRatio) < 100) {
        this.scalingRatio = Number(this.scalingRatio) + 1;
      }
    },
    // 创建图片
    createImg() {
      const {
        top,
        left,
        naturalWidth,
        naturalHeight
      } = this.sourImgInfo;
      this.mouseDown = false;
      const canvas = this.$refs.canvas;
      const ctx = canvas.getContext('2d');
      const ratio = (Number(this.scalingRatio) + 100) / 100;
      const createImgWidth = {
        w: (this.sourceImgContainer.w - 2 * this.shadeInfo.w) / ratio / this.sourceZoomRadius,
        h: (this.sourceImgContainer.h - 2 * this.shadeInfo.h) / ratio / this.sourceZoomRadius,
        x: (this.shadeInfo.w - left) / ratio / this.sourceZoomRadius,
        y: (this.shadeInfo.h - top) / ratio / this.sourceZoomRadius
      };
      // 裁剪后的大小 与原图保持一致
      let canvasWidth;
      let canvasHeight;
      const sourceRadius = naturalWidth / naturalHeight;
      if (sourceRadius < this.nRatio) {
        canvasWidth = naturalWidth;
        canvasHeight = naturalWidth / this.nRatio;
      } else {
        canvasWidth = naturalHeight * this.nRatio;
        canvasHeight = naturalHeight;
      }
      // 设置画布大小
      canvas.width = canvasWidth;
      canvas.height = canvasHeight;
      // 清空画布
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(
        this.sourceImg,
        createImgWidth.x,
        createImgWidth.y,
        createImgWidth.w,
        createImgWidth.h,
        0,
        0,
        canvas.width,
        canvas.height
      );
      const size = this.data2blob(canvas.toDataURL('image/png')).size;
      if (size <= this.size * 1024 * 1024) {
        this.createImgUrl = canvas.toDataURL('image/png');
      } else {
        const quality = this.size / size * 1024 * 1024 * 0.64;
        this.createImgUrl = canvas.toDataURL('image/jpeg', quality);
      }
    },
    // 确认裁剪
    confirm() {
      if (this.confirmCount > 0) {
        return;
      }
      this.confirmCount += 1;
      if (!this.sourImgUrl || !this.createImgUrl) {
        return;
      }
      const fmData = new FormData();
      const blob = this.data2blob(this.createImgUrl);
      if (blob) {
        fmData.append('file', blob);
        this.$emit('cropUploadSuccess', fmData);
        this.handleClose();
      }
    },
    // 数据转为bold
    data2blob(data) {
      data = data.split(',')[1];
      data = window.atob(data);
      const ia = new Uint8Array(data.length);
      for (let i = 0; i < data.length; i++) {
        ia[i] = data.charCodeAt(i);
      }
      // canvas.toDataURL 返回的默认格式就是 image/png
      return new Blob([ia], {
        type: 'image/png'
      });
    },
    // 把图片的url转为file的object
    getImgFile(img) {
      const image = new Image();
      image.crossOrigin = 'Anonymous'; // 解决跨域问题
      image.src = img;
      image.onload = () =>{
        const canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;
        const context = canvas.getContext('2d');
        context.drawImage(image, 0, 0, image.width, image.height);
        const dataURL = canvas.toDataURL(); // 得到图片的base64编码数据
        const file = this.dataURLtoFile(dataURL);
        this.readAsDataURLFun(file);
      };
    },
    // base64转file
    dataURLtoFile(dataurl, filename) {
      const arr = dataurl.split(',');
      const mime = arr[0].match(/:(.*?);/)[1];
      const bstr = atob(arr[1]);
      let n = bstr.length;
      const u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new File([u8arr], filename, { type: mime });
    },
    handleClose() {
      this.$emit('update:visible', false);
    },
    clearData() {
      this.sourImgUrl = '';
      this.createImgUrl = '';
      this.sourceZoomRadius = 0;
      this.scalingRatio = 0;
      this.confirmCount = 0;
    }
  }
};
</script>
