Bläddra i källkod

Merge remote-tracking branch 'origin/develop' into develop

scorpioyq 2 år sedan
förälder
incheckning
a1659879e7

+ 2 - 0
package.json

@@ -25,6 +25,8 @@
     "vite-plugin-pages": "^0.25.0",
     "vite-plugin-vue-layouts": "^0.7.0",
     "vue": "^3.2.37",
+    "vue-advanced-cropper": "^2.8.8",
+    "vue-cropper": "^1.0.9",
     "vue-qr": "^4.0.9",
     "vue-router": "^4.1.2",
     "vue3-eventbus": "^2.0.0"

+ 7 - 0
src/api/dash/index.js

@@ -36,5 +36,12 @@ export default {
    */
   mpList() {
     return fetch('/blade-project-manage-v2/index/v2/v2/mp-list')
+  },
+  /**
+   * 头像更新
+   * @returns {Promise | Promise<unknown>}
+   */
+  updateAvatar(params) {
+    return fetch('/blade-user/update', params, 'post', 'json')
   }
 }

+ 6 - 1
src/components/upload-office/index.vue

@@ -21,7 +21,7 @@
         class="padding-left padding-right radius-5 white grey-9-bg ml-5"
       ></div>
     </div>
-    <div class="upload" v-if="drawer">
+    <div class="upload" v-if="drawer && showProgress">
       <div>
         <div class="full-width flex flex-justify-end">
           <el-icon @click="drawer = false">
@@ -59,6 +59,10 @@ export default {
       type: String,
       default: '.pdf, .doc,.docx,.ppt, .pptx, .xls, .xlsx,.PDF'
     },
+    showProgress: {
+      type: Boolean,
+      default: true
+    },
     max: {
       type: Number,
       default: 9
@@ -90,6 +94,7 @@ export default {
     success(res) {
       if (res.code === 200) {
         this.resultList.push(res.data)
+        this.$emit('upload', this.resultList)
       }
       if (this.resultList.length === this.fileList.length) {
         this.saveLibrary()

+ 159 - 0
src/components/vue-cropper/exif-js-min.js

@@ -0,0 +1,159 @@
+const Exif = {};
+
+Exif.getData = (img) => new Promise((reslove, reject) => {
+  let obj = {};
+  getImageData(img).then(data => {
+    obj.arrayBuffer = data;
+    obj.orientation = getOrientation(data);
+    reslove(obj)
+  }).catch(error => {
+    reject(error)
+  })
+})
+
+// 这里的获取exif要将图片转ArrayBuffer对象,这里假设获取了图片的baes64
+// 步骤一
+// base64转ArrayBuffer对象
+function getImageData(img) {
+  let data = null;
+  return new Promise((reslove, reject) => {
+    if (img.src) {
+      if (/^data\:/i.test(img.src)) { // Data URI
+        data = base64ToArrayBuffer(img.src);
+        reslove(data)
+      } else if (/^blob\:/i.test(img.src)) { // Object URL
+        var fileReader = new FileReader();
+        fileReader.onload = function (e) {
+          data = e.target.result;
+          reslove(data)
+        };
+        objectURLToBlob(img.src, function (blob) {
+          fileReader.readAsArrayBuffer(blob);
+        });
+      } else {
+        var http = new XMLHttpRequest();
+        http.onload = function () {
+          if (this.status == 200 || this.status === 0) {
+            data = http.response
+            reslove(data)
+          } else {
+            throw "Could not load image";
+          }
+          http = null;
+        };
+        http.open("GET", img.src, true);
+        http.responseType = "arraybuffer";
+        http.send(null);
+      }
+    } else {
+      reject('img error')
+    }
+  })
+}
+
+function objectURLToBlob(url, callback) {
+  var http = new XMLHttpRequest();
+  http.open("GET", url, true);
+  http.responseType = "blob";
+  http.onload = function (e) {
+    if (this.status == 200 || this.status === 0) {
+      callback(this.response);
+    }
+  };
+  http.send();
+}
+
+
+
+function base64ToArrayBuffer(base64) {
+  base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
+  var binary = atob(base64);
+  var len = binary.length;
+  var buffer = new ArrayBuffer(len);
+  var view = new Uint8Array(buffer);
+  for (var i = 0; i < len; i++) {
+    view[i] = binary.charCodeAt(i);
+  }
+  return buffer;
+}
+// 步骤二,Unicode码转字符串
+// ArrayBuffer对象 Unicode码转字符串
+function getStringFromCharCode(dataView, start, length) {
+  var str = '';
+  var i;
+  for (i = start, length += start; i < length; i++) {
+    str += String.fromCharCode(dataView.getUint8(i));
+  }
+  return str;
+}
+
+// 步骤三,获取jpg图片的exif的角度(在ios体现最明显)
+function getOrientation(arrayBuffer) {
+  var dataView = new DataView(arrayBuffer);
+  var length = dataView.byteLength;
+  var orientation;
+  var exifIDCode;
+  var tiffOffset;
+  var firstIFDOffset;
+  var littleEndian;
+  var endianness;
+  var app1Start;
+  var ifdStart;
+  var offset;
+  var i;
+  // Only handle JPEG image (start by 0xFFD8)
+  if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
+    offset = 2;
+    while (offset < length) {
+      if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
+        app1Start = offset;
+        break;
+      }
+      offset++;
+    }
+  }
+  if (app1Start) {
+    exifIDCode = app1Start + 4;
+    tiffOffset = app1Start + 10;
+    if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
+      endianness = dataView.getUint16(tiffOffset);
+      littleEndian = endianness === 0x4949;
+
+      if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
+        if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
+          firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
+
+          if (firstIFDOffset >= 0x00000008) {
+            ifdStart = tiffOffset + firstIFDOffset;
+          }
+        }
+      }
+    }
+  }
+  if (ifdStart) {
+    length = dataView.getUint16(ifdStart, littleEndian);
+
+    for (i = 0; i < length; i++) {
+      offset = ifdStart + i * 12 + 2;
+      if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
+
+        // 8 is the offset of the current tag's value
+        offset += 8;
+
+        // Get the original orientation value
+        orientation = dataView.getUint16(offset, littleEndian);
+
+        // Override the orientation with its default value for Safari (#120)
+        // if (IS_SAFARI_OR_UIWEBVIEW) {
+        //   dataView.setUint16(offset, 1, littleEndian);
+        // }
+        break;
+      }
+    }
+  }
+  return orientation;
+}
+
+
+
+export default Exif

+ 2257 - 0
src/components/vue-cropper/index.vue

@@ -0,0 +1,2257 @@
+<template>
+  <div
+    class="vue-cropper"
+    ref="cropper"
+    @mouseover="scaleImg"
+    @mouseout="cancelScale"
+  >
+    <div class="cropper-box" v-if="imgs">
+      <div
+        class="cropper-box-canvas"
+        v-show="!loading"
+        :style="{
+          width: trueWidth + 'px',
+          height: trueHeight + 'px',
+          transform:
+            'scale(' +
+            scale +
+            ',' +
+            scale +
+            ') ' +
+            'translate3d(' +
+            x / scale +
+            'px,' +
+            y / scale +
+            'px,' +
+            '0)' +
+            'rotateZ(' +
+            rotate * 90 +
+            'deg)'
+        }"
+      >
+        <img :src="imgs" alt="cropper-img" ref="cropperImg" />
+      </div>
+    </div>
+    <div
+      class="cropper-drag-box"
+      :class="{
+        'cropper-move': move && !crop,
+        'cropper-crop': crop,
+        'cropper-modal': cropping
+      }"
+      @mousedown="startMove"
+      @touchstart="startMove"
+    ></div>
+    <div
+      v-show="cropping"
+      class="cropper-crop-box"
+      :style="{
+        width: cropW + 'px',
+        height: cropH + 'px',
+        transform:
+          'translate3d(' + cropOffsertX + 'px,' + cropOffsertY + 'px,' + '0)'
+      }"
+    >
+      <span class="cropper-view-box">
+        <img
+          :style="{
+            width: trueWidth + 'px',
+            height: trueHeight + 'px',
+            transform:
+              'scale(' +
+              scale +
+              ',' +
+              scale +
+              ') ' +
+              'translate3d(' +
+              (x - cropOffsertX) / scale +
+              'px,' +
+              (y - cropOffsertY) / scale +
+              'px,' +
+              '0)' +
+              'rotateZ(' +
+              rotate * 90 +
+              'deg)'
+          }"
+          :src="imgs"
+          alt="cropper-img"
+        />
+      </span>
+      <span
+        class="cropper-face cropper-move"
+        @mousedown="cropMove"
+        @touchstart="cropMove"
+      ></span>
+      <span class="crop-info" v-if="info" :style="{ top: cropInfo.top }"
+        >{{ cropInfo.width }} × {{ cropInfo.height }}</span
+      >
+      <span v-if="!fixedBox">
+        <span
+          class="crop-line line-w"
+          @mousedown="changeCropSize($event, false, true, 0, 1)"
+          @touchstart="changeCropSize($event, false, true, 0, 1)"
+        ></span>
+        <span
+          class="crop-line line-a"
+          @mousedown="changeCropSize($event, true, false, 1, 0)"
+          @touchstart="changeCropSize($event, true, false, 1, 0)"
+        ></span>
+        <span
+          class="crop-line line-s"
+          @mousedown="changeCropSize($event, false, true, 0, 2)"
+          @touchstart="changeCropSize($event, false, true, 0, 2)"
+        ></span>
+        <span
+          class="crop-line line-d"
+          @mousedown="changeCropSize($event, true, false, 2, 0)"
+          @touchstart="changeCropSize($event, true, false, 2, 0)"
+        ></span>
+        <span
+          class="crop-point point1"
+          @mousedown="changeCropSize($event, true, true, 1, 1)"
+          @touchstart="changeCropSize($event, true, true, 1, 1)"
+        ></span>
+        <span
+          class="crop-point point2"
+          @mousedown="changeCropSize($event, false, true, 0, 1)"
+          @touchstart="changeCropSize($event, false, true, 0, 1)"
+        ></span>
+        <span
+          class="crop-point point3"
+          @mousedown="changeCropSize($event, true, true, 2, 1)"
+          @touchstart="changeCropSize($event, true, true, 2, 1)"
+        ></span>
+        <span
+          class="crop-point point4"
+          @mousedown="changeCropSize($event, true, false, 1, 0)"
+          @touchstart="changeCropSize($event, true, false, 1, 0)"
+        ></span>
+        <span
+          class="crop-point point5"
+          @mousedown="changeCropSize($event, true, false, 2, 0)"
+          @touchstart="changeCropSize($event, true, false, 2, 0)"
+        ></span>
+        <span
+          class="crop-point point6"
+          @mousedown="changeCropSize($event, true, true, 1, 2)"
+          @touchstart="changeCropSize($event, true, true, 1, 2)"
+        ></span>
+        <span
+          class="crop-point point7"
+          @mousedown="changeCropSize($event, false, true, 0, 2)"
+          @touchstart="changeCropSize($event, false, true, 0, 2)"
+        ></span>
+        <span
+          class="crop-point point8"
+          @mousedown="changeCropSize($event, true, true, 2, 2)"
+          @touchstart="changeCropSize($event, true, true, 2, 2)"
+        ></span>
+      </span>
+    </div>
+  </div>
+</template>
+
+<script>
+import exifmin from './exif-js-min'
+
+export default {
+  data: function () {
+    return {
+      // 容器高宽
+      w: 0,
+      h: 0,
+      // 图片缩放比例
+      scale: 1,
+      // 图片偏移x轴
+      x: 0,
+      // 图片偏移y轴
+      y: 0,
+      // 图片加载
+      loading: true,
+      // 图片真实宽度
+      trueWidth: 0,
+      // 图片真实高度
+      trueHeight: 0,
+      move: true,
+      // 移动的x
+      moveX: 0,
+      // 移动的y
+      moveY: 0,
+      // 开启截图
+      crop: false,
+      // 正在截图
+      cropping: false,
+      // 裁剪框大小
+      cropW: 0,
+      cropH: 0,
+      cropOldW: 0,
+      cropOldH: 0,
+      // 判断是否能够改变
+      canChangeX: false,
+      canChangeY: false,
+      // 改变的基准点
+      changeCropTypeX: 1,
+      changeCropTypeY: 1,
+      // 裁剪框的坐标轴
+      cropX: 0,
+      cropY: 0,
+      cropChangeX: 0,
+      cropChangeY: 0,
+      cropOffsertX: 0,
+      cropOffsertY: 0,
+      // 支持的滚动事件
+      support: '',
+      // 移动端手指缩放
+      touches: [],
+      touchNow: false,
+      // 图片旋转
+      rotate: 0,
+      isIos: false,
+      orientation: 0,
+      imgs: '',
+      // 图片缩放系数
+      coe: 0.2,
+      // 是否正在多次缩放
+      scaling: false,
+      scalingSet: '',
+      coeStatus: '',
+      // 控制emit触发频率
+      isCanShow: true
+    }
+  },
+  props: {
+    img: {
+      type: [String, Blob, null, File],
+      default: ''
+    },
+    // 输出图片压缩比
+    outputSize: {
+      type: Number,
+      default: 1
+    },
+    outputType: {
+      type: String,
+      default: 'jpeg'
+    },
+    info: {
+      type: Boolean,
+      default: true
+    },
+    // 是否开启滚轮放大缩小
+    canScale: {
+      type: Boolean,
+      default: true
+    },
+    // 是否自成截图框
+    autoCrop: {
+      type: Boolean,
+      default: false
+    },
+    autoCropWidth: {
+      type: [Number, String],
+      default: 0
+    },
+    autoCropHeight: {
+      type: [Number, String],
+      default: 0
+    },
+    // 是否开启固定宽高比
+    fixed: {
+      type: Boolean,
+      default: false
+    },
+    // 宽高比 w/h
+    fixedNumber: {
+      type: Array,
+      default: () => {
+        return [1, 1]
+      }
+    },
+    // 固定大小 禁止改变截图框大小
+    fixedBox: {
+      type: Boolean,
+      default: false
+    },
+    // 输出截图是否缩放
+    full: {
+      type: Boolean,
+      default: false
+    },
+    // 是否可以拖动图片
+    canMove: {
+      type: Boolean,
+      default: true
+    },
+    // 是否可以拖动截图框
+    canMoveBox: {
+      type: Boolean,
+      default: true
+    },
+    // 上传图片按照原始比例显示
+    original: {
+      type: Boolean,
+      default: false
+    },
+    // 截图框能否超过图片
+    centerBox: {
+      type: Boolean,
+      default: false
+    },
+    // 是否根据dpr输出高清图片
+    high: {
+      type: Boolean,
+      default: true
+    },
+    // 截图框展示宽高类型
+    infoTrue: {
+      type: Boolean,
+      default: false
+    },
+    // 可以压缩图片宽高  默认不超过200
+    maxImgSize: {
+      type: [Number, String],
+      default: 2000
+    },
+    // 倍数  可渲染当前截图框的n倍 0 - 1000;
+    enlarge: {
+      type: [Number, String],
+      default: 1
+    },
+
+    // 自动预览的固定宽度
+    preW: {
+      type: [Number, String],
+      default: 0
+    },
+    /*
+      图片布局方式 mode 实现和css背景一样的效果
+      contain  居中布局 默认不会缩放 保证图片在容器里面 mode: 'contain'
+      cover    拉伸布局 填充整个容器  mode: 'cover'
+      如果仅有一个数值被给定,这个数值将作为宽度值大小,高度值将被设定为auto。 mode: '50px'
+      如果有两个数值被给定,第一个将作为宽度值大小,第二个作为高度值大小。 mode: '50px 60px'
+    */
+    mode: {
+      type: String,
+      default: 'contain'
+    },
+    // 限制最小区域,可传1以上的数字和字符串,限制长宽都是这么大
+    // 也可以传数组[90,90]
+    limitMinSize: {
+      type: [Number, Array, String],
+      default: () => {
+        return 10
+      },
+      validator: function (value) {
+        if (Array.isArray(value)) {
+          return Number(value[0]) >= 0 && Number(value[1]) >= 0
+        } else {
+          return Number(value) >= 0
+        }
+      }
+    },
+    // 导出时,填充背景颜色
+    fillColor: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    cropInfo() {
+      const obj = {}
+      obj.top = this.cropOffsertY > 21 ? '-21px' : '0px'
+      obj.width = this.cropW > 0 ? this.cropW : 0
+      obj.height = this.cropH > 0 ? this.cropH : 0
+      if (this.infoTrue) {
+        let dpr = 1
+        if (this.high && !this.full) {
+          dpr = window.devicePixelRatio
+        }
+        if ((this.enlarge !== 1) & !this.full) {
+          dpr = Math.abs(Number(this.enlarge))
+        }
+        obj.width = obj.width * dpr
+        obj.height = obj.height * dpr
+        if (this.full) {
+          obj.width = obj.width / this.scale
+          obj.height = obj.height / this.scale
+        }
+      }
+      obj.width = obj.width.toFixed(0)
+      obj.height = obj.height.toFixed(0)
+      return obj
+    },
+
+    isIE() {
+      const userAgent = navigator.userAgent // 取得浏览器的userAgent字符串
+      const isIE = !!window.ActiveXObject || 'ActiveXObject' in window // 判断是否IE浏览器
+      return isIE
+    },
+
+    passive() {
+      return this.isIE
+        ? null
+        : {
+            passive: false
+          }
+    }
+  },
+  watch: {
+    // 如果图片改变, 重新布局
+    img() {
+      // 当传入图片时, 读取图片信息同时展示
+      console.log('img')
+      this.checkedImg()
+    },
+    imgs(val) {
+      if (val === '') {
+        return
+      }
+      this.reload()
+    },
+    cropW() {
+      this.showPreview()
+    },
+    cropH() {
+      this.showPreview()
+    },
+    cropOffsertX() {
+      this.showPreview()
+    },
+    cropOffsertY() {
+      this.showPreview()
+    },
+    scale(val, oldVal) {
+      this.showPreview()
+    },
+    x() {
+      this.showPreview()
+    },
+    y() {
+      this.showPreview()
+    },
+    autoCrop(val) {
+      if (val) {
+        this.goAutoCrop()
+      }
+    },
+    // 修改了自动截图框
+    autoCropWidth() {
+      if (this.autoCrop) {
+        this.goAutoCrop()
+      }
+    },
+    autoCropHeight() {
+      if (this.autoCrop) {
+        this.goAutoCrop()
+      }
+    },
+    mode() {
+      this.checkedImg()
+    },
+    rotate() {
+      this.showPreview()
+      if (this.autoCrop) {
+        this.goAutoCrop(this.cropW, this.cropH)
+      } else {
+        if (this.cropW > 0 || this.cropH > 0) {
+          this.goAutoCrop(this.cropW, this.cropH)
+        }
+      }
+    }
+  },
+  methods: {
+    getVersion(name) {
+      const arr = navigator.userAgent.split(' ')
+      let chromeVersion = ''
+      let result = 0
+      const reg = new RegExp(name, 'i')
+      for (let i = 0; i < arr.length; i++) {
+        if (reg.test(arr[i])) chromeVersion = arr[i]
+      }
+      if (chromeVersion) {
+        result = chromeVersion.split('/')[1].split('.')
+      } else {
+        result = ['0', '0', '0']
+      }
+      return result
+    },
+    checkOrientationImage(img, orientation, width, height) {
+      // 如果是 chrome内核版本在81 safari 在 605 以上不处理图片旋转
+      // alert(navigator.userAgent)
+      if (this.getVersion('chrome')[0] >= 81) {
+        orientation = -1
+      } else {
+        if (this.getVersion('safari')[0] >= 605) {
+          const safariVersion = this.getVersion('version')
+          if (safariVersion[0] > 13 && safariVersion[1] > 1) {
+            orientation = -1
+          }
+        } else {
+          //  判断 ios 版本进行处理
+          // 针对 ios 版本大于 13.4的系统不做图片旋转
+          const isIos = navigator.userAgent
+            .toLowerCase()
+            .match(/cpu iphone os (.*?) like mac os/)
+          if (isIos) {
+            let version = isIos[1]
+            version = version.split('_')
+            if (version[0] > 13 || (version[0] >= 13 && version[1] >= 4)) {
+              orientation = -1
+            }
+          }
+        }
+      }
+
+      // alert(`当前处理的orientation${orientation}`)
+      const canvas = document.createElement('canvas')
+      const ctx = canvas.getContext('2d')
+      ctx.save()
+
+      switch (orientation) {
+        case 2:
+          canvas.width = width
+          canvas.height = height
+          // horizontal flip
+          ctx.translate(width, 0)
+          ctx.scale(-1, 1)
+          break
+        case 3:
+          canvas.width = width
+          canvas.height = height
+          // 180 graus
+          ctx.translate(width / 2, height / 2)
+          ctx.rotate((180 * Math.PI) / 180)
+          ctx.translate(-width / 2, -height / 2)
+          break
+        case 4:
+          canvas.width = width
+          canvas.height = height
+          // vertical flip
+          ctx.translate(0, height)
+          ctx.scale(1, -1)
+          break
+        case 5:
+          // vertical flip + 90 rotate right
+          canvas.height = width
+          canvas.width = height
+          ctx.rotate(0.5 * Math.PI)
+          ctx.scale(1, -1)
+          break
+        case 6:
+          canvas.width = height
+          canvas.height = width
+          // 90 graus
+          ctx.translate(height / 2, width / 2)
+          ctx.rotate((90 * Math.PI) / 180)
+          ctx.translate(-width / 2, -height / 2)
+          break
+        case 7:
+          // horizontal flip + 90 rotate right
+          canvas.height = width
+          canvas.width = height
+          ctx.rotate(0.5 * Math.PI)
+          ctx.translate(width, -height)
+          ctx.scale(-1, 1)
+          break
+        case 8:
+          canvas.height = width
+          canvas.width = height
+          // -90 graus
+          ctx.translate(height / 2, width / 2)
+          ctx.rotate((-90 * Math.PI) / 180)
+          ctx.translate(-width / 2, -height / 2)
+          break
+        default:
+          canvas.width = width
+          canvas.height = height
+      }
+
+      ctx.drawImage(img, 0, 0, width, height)
+      ctx.restore()
+      canvas.toBlob(
+        blob => {
+          const data = URL.createObjectURL(blob)
+          URL.revokeObjectURL(this.imgs)
+          this.imgs = data
+        },
+        'image/' + this.outputType,
+        1
+      )
+    },
+
+    // checkout img
+    checkedImg() {
+      if (this.img === null || this.img === '') {
+        this.imgs = ''
+        this.clearCrop()
+        return
+      }
+      this.loading = true
+      this.scale = 1
+      this.rotate = 0
+      this.clearCrop()
+      const img = new Image()
+      img.onload = () => {
+        if (this.img === '') {
+          this.$emit('imgLoad', 'error')
+          this.$emit('img-load', 'error')
+          return false
+        }
+
+        let width = img.width
+        let height = img.height
+        exifmin.getData(img).then(data => {
+          this.orientation = data.orientation || 1
+          const max = Number(this.maxImgSize)
+          if (!this.orientation && (width < max) & (height < max)) {
+            this.imgs = this.img
+            return
+          }
+
+          if (width > max) {
+            height = (height / width) * max
+            width = max
+          }
+
+          if (height > max) {
+            width = (width / height) * max
+            height = max
+          }
+          this.checkOrientationImage(img, this.orientation, width, height)
+        })
+      }
+
+      img.onerror = () => {
+        this.$emit('imgLoad', 'error')
+        this.$emit('img-load', 'error')
+      }
+
+      // 判断如果不是base64图片 再添加crossOrigin属性,否则会导致iOS低版本(10.2)无法显示图片
+      if (this.img.substr(0, 4) !== 'data') {
+        img.crossOrigin = ''
+      }
+
+      if (this.isIE) {
+        const xhr = new XMLHttpRequest()
+        xhr.onload = function () {
+          const url = URL.createObjectURL(this.response)
+          img.src = url
+        }
+        xhr.open('GET', this.img, true)
+        xhr.responseType = 'blob'
+        xhr.send()
+      } else {
+        img.src = this.img
+      }
+    },
+    // 当按下鼠标键
+    startMove(e) {
+      e.preventDefault()
+      // 如果move 为true 表示当前可以拖动
+      if (this.move && !this.crop) {
+        if (!this.canMove) {
+          return false
+        }
+        // 开始移动
+        this.moveX =
+          ('clientX' in e ? e.clientX : e.touches[0].clientX) - this.x
+        this.moveY =
+          ('clientY' in e ? e.clientY : e.touches[0].clientY) - this.y
+        if (e.touches) {
+          window.addEventListener('touchmove', this.moveImg)
+          window.addEventListener('touchend', this.leaveImg)
+          if (e.touches.length == 2) {
+            // 记录手指刚刚放上去
+            this.touches = e.touches
+            window.addEventListener('touchmove', this.touchScale)
+            window.addEventListener('touchend', this.cancelTouchScale)
+          }
+        } else {
+          window.addEventListener('mousemove', this.moveImg)
+          window.addEventListener('mouseup', this.leaveImg)
+        }
+        // 触发图片移动事件
+        this.$emit('imgMoving', {
+          moving: true,
+          axis: this.getImgAxis()
+        })
+        this.$emit('img-moving', {
+          moving: true,
+          axis: this.getImgAxis()
+        })
+      } else {
+        // 截图ing
+        this.cropping = true
+        // 绑定截图事件
+        window.addEventListener('mousemove', this.createCrop)
+        window.addEventListener('mouseup', this.endCrop)
+        window.addEventListener('touchmove', this.createCrop)
+        window.addEventListener('touchend', this.endCrop)
+        this.cropOffsertX = e.offsetX
+          ? e.offsetX
+          : e.touches[0].pageX - this.$refs.cropper.offsetLeft
+        this.cropOffsertY = e.offsetY
+          ? e.offsetY
+          : e.touches[0].pageY - this.$refs.cropper.offsetTop
+        this.cropX = 'clientX' in e ? e.clientX : e.touches[0].clientX
+        this.cropY = 'clientY' in e ? e.clientY : e.touches[0].clientY
+        this.cropChangeX = this.cropOffsertX
+        this.cropChangeY = this.cropOffsertY
+        this.cropW = 0
+        this.cropH = 0
+      }
+    },
+
+    // 移动端缩放
+    touchScale(e) {
+      e.preventDefault()
+      let scale = this.scale
+      // 记录变化量
+      // 第一根手指
+      const oldTouch1 = {
+        x: this.touches[0].clientX,
+        y: this.touches[0].clientY
+      }
+      const newTouch1 = {
+        x: e.touches[0].clientX,
+        y: e.touches[0].clientY
+      }
+      // 第二根手指
+      const oldTouch2 = {
+        x: this.touches[1].clientX,
+        y: this.touches[1].clientY
+      }
+      const newTouch2 = {
+        x: e.touches[1].clientX,
+        y: e.touches[1].clientY
+      }
+      const oldL = Math.sqrt(
+        Math.pow(oldTouch1.x - oldTouch2.x, 2) +
+          Math.pow(oldTouch1.y - oldTouch2.y, 2)
+      )
+      const newL = Math.sqrt(
+        Math.pow(newTouch1.x - newTouch2.x, 2) +
+          Math.pow(newTouch1.y - newTouch2.y, 2)
+      )
+      const cha = newL - oldL
+      // 根据图片本身大小 决定每次改变大小的系数, 图片越大系数越小
+      // 1px - 0.2
+      let coe = 1
+      coe =
+        coe / this.trueWidth > coe / this.trueHeight
+          ? coe / this.trueHeight
+          : coe / this.trueWidth
+      coe = coe > 0.1 ? 0.1 : coe
+      const num = coe * cha
+      if (!this.touchNow) {
+        this.touchNow = true
+        if (cha > 0) {
+          scale += Math.abs(num)
+        } else if (cha < 0) {
+          scale > Math.abs(num) ? (scale -= Math.abs(num)) : scale
+        }
+        this.touches = e.touches
+        setTimeout(() => {
+          this.touchNow = false
+        }, 8)
+        if (!this.checkoutImgAxis(this.x, this.y, scale)) {
+          return false
+        }
+        this.scale = scale
+      }
+    },
+
+    cancelTouchScale(e) {
+      window.removeEventListener('touchmove', this.touchScale)
+    },
+
+    // 移动图片
+    moveImg(e) {
+      e.preventDefault()
+      if (e.touches && e.touches.length === 2) {
+        this.touches = e.touches
+        window.addEventListener('touchmove', this.touchScale)
+        window.addEventListener('touchend', this.cancelTouchScale)
+        window.removeEventListener('touchmove', this.moveImg)
+        return false
+      }
+      const nowX = 'clientX' in e ? e.clientX : e.touches[0].clientX
+      const nowY = 'clientY' in e ? e.clientY : e.touches[0].clientY
+
+      let changeX, changeY
+      changeX = nowX - this.moveX
+      changeY = nowY - this.moveY
+
+      this.$nextTick(() => {
+        if (this.centerBox) {
+          const axis = this.getImgAxis(changeX, changeY, this.scale)
+          const cropAxis = this.getCropAxis()
+          const imgW = this.trueHeight * this.scale
+          const imgH = this.trueWidth * this.scale
+          let maxLeft, maxTop, maxRight, maxBottom
+          switch (this.rotate) {
+            case 1:
+            case -1:
+            case 3:
+            case -3:
+              maxLeft =
+                this.cropOffsertX -
+                (this.trueWidth * (1 - this.scale)) / 2 +
+                (imgW - imgH) / 2
+              maxTop =
+                this.cropOffsertY -
+                (this.trueHeight * (1 - this.scale)) / 2 +
+                (imgH - imgW) / 2
+              maxRight = maxLeft - imgW + this.cropW
+              maxBottom = maxTop - imgH + this.cropH
+              break
+            default:
+              maxLeft =
+                this.cropOffsertX - (this.trueWidth * (1 - this.scale)) / 2
+              maxTop =
+                this.cropOffsertY - (this.trueHeight * (1 - this.scale)) / 2
+              maxRight = maxLeft - imgH + this.cropW
+              maxBottom = maxTop - imgW + this.cropH
+              break
+          }
+
+          // 图片左边 图片不能超过截图框
+          if (axis.x1 >= cropAxis.x1) {
+            changeX = maxLeft
+          }
+
+          // 图片上边 图片不能超过截图框
+          if (axis.y1 >= cropAxis.y1) {
+            changeY = maxTop
+          }
+
+          // 图片右边
+          if (axis.x2 <= cropAxis.x2) {
+            changeX = maxRight
+          }
+
+          // 图片下边
+          if (axis.y2 <= cropAxis.y2) {
+            changeY = maxBottom
+          }
+        }
+        this.x = changeX
+        this.y = changeY
+        // 触发图片移动事件
+        this.$emit('imgMoving', {
+          moving: true,
+          axis: this.getImgAxis()
+        })
+        this.$emit('img-moving', {
+          moving: true,
+          axis: this.getImgAxis()
+        })
+      })
+    },
+    // 移动图片结束
+    leaveImg(e) {
+      window.removeEventListener('mousemove', this.moveImg)
+      window.removeEventListener('touchmove', this.moveImg)
+      window.removeEventListener('mouseup', this.leaveImg)
+      window.removeEventListener('touchend', this.leaveImg)
+      // 触发图片移动事件
+      this.$emit('imgMoving', {
+        moving: false,
+        axis: this.getImgAxis()
+      })
+      this.$emit('img-moving', {
+        moving: false,
+        axis: this.getImgAxis()
+      })
+    },
+    // 缩放图片
+    scaleImg() {
+      if (this.canScale) {
+        window.addEventListener(this.support, this.changeSize, this.passive)
+      }
+    },
+    // 移出框
+    cancelScale() {
+      if (this.canScale) {
+        window.removeEventListener(this.support, this.changeSize)
+      }
+    },
+    // 改变大小函数
+    changeSize(e) {
+      e.preventDefault()
+      let scale = this.scale
+      let change = e.deltaY || e.wheelDelta
+      // 根据图片本身大小 决定每次改变大小的系数, 图片越大系数越小
+      const isFirefox = navigator.userAgent.indexOf('Firefox')
+      change = isFirefox > 0 ? change * 30 : change
+      // 修复ie的滚动缩放
+      if (this.isIE) {
+        change = -change
+      }
+      // 1px - 0.2
+      let coe = this.coe
+      coe =
+        coe / this.trueWidth > coe / this.trueHeight
+          ? coe / this.trueHeight
+          : coe / this.trueWidth
+      const num = coe * change
+      num < 0
+        ? (scale += Math.abs(num))
+        : scale > Math.abs(num)
+        ? (scale -= Math.abs(num))
+        : scale
+      // 延迟0.1s 每次放大大或者缩小的范围
+      const status = num < 0 ? 'add' : 'reduce'
+      if (status !== this.coeStatus) {
+        this.coeStatus = status
+        this.coe = 0.2
+      }
+      if (!this.scaling) {
+        this.scalingSet = setTimeout(() => {
+          this.scaling = false
+          this.coe = this.coe += 0.01
+        }, 50)
+      }
+      this.scaling = true
+      if (!this.checkoutImgAxis(this.x, this.y, scale)) {
+        return false
+      }
+      this.scale = scale
+    },
+
+    // 修改图片大小函数
+    changeScale(num) {
+      let scale = this.scale
+      num = num || 1
+      let coe = 20
+      coe =
+        coe / this.trueWidth > coe / this.trueHeight
+          ? coe / this.trueHeight
+          : coe / this.trueWidth
+      num = num * coe
+      num > 0
+        ? (scale += Math.abs(num))
+        : scale > Math.abs(num)
+        ? (scale -= Math.abs(num))
+        : scale
+      if (!this.checkoutImgAxis(this.x, this.y, scale)) {
+        return false
+      }
+      this.scale = scale
+    },
+    // 创建截图框
+    createCrop(e) {
+      e.preventDefault()
+      // 移动生成大小
+      const nowX =
+        'clientX' in e ? e.clientX : e.touches ? e.touches[0].clientX : 0
+      const nowY =
+        'clientY' in e ? e.clientY : e.touches ? e.touches[0].clientY : 0
+      this.$nextTick(() => {
+        const fw = nowX - this.cropX
+        const fh = nowY - this.cropY
+        if (fw > 0) {
+          this.cropW =
+            fw + this.cropChangeX > this.w ? this.w - this.cropChangeX : fw
+          this.cropOffsertX = this.cropChangeX
+        } else {
+          this.cropW =
+            this.w - this.cropChangeX + Math.abs(fw) > this.w
+              ? this.cropChangeX
+              : Math.abs(fw)
+          this.cropOffsertX =
+            this.cropChangeX + fw > 0 ? this.cropChangeX + fw : 0
+        }
+
+        if (!this.fixed) {
+          if (fh > 0) {
+            this.cropH =
+              fh + this.cropChangeY > this.h ? this.h - this.cropChangeY : fh
+            this.cropOffsertY = this.cropChangeY
+          } else {
+            this.cropH =
+              this.h - this.cropChangeY + Math.abs(fh) > this.h
+                ? this.cropChangeY
+                : Math.abs(fh)
+            this.cropOffsertY =
+              this.cropChangeY + fh > 0 ? this.cropChangeY + fh : 0
+          }
+        } else {
+          const fixedHeight =
+            (this.cropW / this.fixedNumber[0]) * this.fixedNumber[1]
+          if (fixedHeight + this.cropOffsertY > this.h) {
+            this.cropH = this.h - this.cropOffsertY
+            this.cropW =
+              (this.cropH / this.fixedNumber[1]) * this.fixedNumber[0]
+            if (fw > 0) {
+              this.cropOffsertX = this.cropChangeX
+            } else {
+              this.cropOffsertX = this.cropChangeX - this.cropW
+            }
+          } else {
+            this.cropH = fixedHeight
+          }
+          this.cropOffsertY = this.cropOffsertY
+        }
+      })
+    },
+
+    // 改变截图框大小
+    changeCropSize(e, w, h, typeW, typeH) {
+      e.preventDefault()
+      window.addEventListener('mousemove', this.changeCropNow)
+      window.addEventListener('mouseup', this.changeCropEnd)
+      window.addEventListener('touchmove', this.changeCropNow)
+      window.addEventListener('touchend', this.changeCropEnd)
+      this.canChangeX = w
+      this.canChangeY = h
+      this.changeCropTypeX = typeW
+      this.changeCropTypeY = typeH
+      this.cropX = 'clientX' in e ? e.clientX : e.touches[0].clientX
+      this.cropY = 'clientY' in e ? e.clientY : e.touches[0].clientY
+      this.cropOldW = this.cropW
+      this.cropOldH = this.cropH
+      this.cropChangeX = this.cropOffsertX
+      this.cropChangeY = this.cropOffsertY
+      if (this.fixed) {
+        if (this.canChangeX && this.canChangeY) {
+          this.canChangeY = 0
+        }
+      }
+      this.$emit('changeCropSize', {
+        width: this.cropW,
+        height: this.cropH
+      })
+      this.$emit('change-crop-size', {
+        width: this.cropW,
+        height: this.cropH
+      })
+    },
+
+    // 正在改变
+    changeCropNow(e) {
+      e.preventDefault()
+      const nowX =
+        'clientX' in e ? e.clientX : e.touches ? e.touches[0].clientX : 0
+      const nowY =
+        'clientY' in e ? e.clientY : e.touches ? e.touches[0].clientY : 0
+      // 容器的宽高
+      let wrapperW = this.w
+      let wrapperH = this.h
+
+      // 不能超过的坐标轴
+      let minX = 0
+      let minY = 0
+
+      if (this.centerBox) {
+        const axis = this.getImgAxis()
+        const imgW = axis.x2
+        const imgH = axis.y2
+        minX = axis.x1 > 0 ? axis.x1 : 0
+        minY = axis.y1 > 0 ? axis.y1 : 0
+        if (wrapperW > imgW) {
+          wrapperW = imgW
+        }
+
+        if (wrapperH > imgH) {
+          wrapperH = imgH
+        }
+      }
+      const [minCropW, minCropH] = this.checkCropLimitSize()
+      this.$nextTick(() => {
+        const fw = nowX - this.cropX
+        const fh = nowY - this.cropY
+        if (this.canChangeX) {
+          if (this.changeCropTypeX === 1) {
+            if (this.cropOldW - fw < minCropW) {
+              this.cropW = minCropW
+              this.cropOffsertX =
+                this.cropOldW + this.cropChangeX - minX - minCropW
+            } else if (this.cropOldW - fw > 0) {
+              this.cropW =
+                wrapperW - this.cropChangeX - fw <= wrapperW - minX
+                  ? this.cropOldW - fw
+                  : this.cropOldW + this.cropChangeX - minX
+              this.cropOffsertX =
+                wrapperW - this.cropChangeX - fw <= wrapperW - minX
+                  ? this.cropChangeX + fw
+                  : minX
+            } else {
+              this.cropW =
+                Math.abs(fw) + this.cropChangeX <= wrapperW
+                  ? Math.abs(fw) - this.cropOldW
+                  : wrapperW - this.cropOldW - this.cropChangeX
+              this.cropOffsertX = this.cropChangeX + this.cropOldW
+            }
+          } else if (this.changeCropTypeX === 2) {
+            if (this.cropOldW + fw < minCropW) {
+              this.cropW = minCropW
+            } else if (this.cropOldW + fw > 0) {
+              this.cropW =
+                this.cropOldW + fw + this.cropOffsertX <= wrapperW
+                  ? this.cropOldW + fw
+                  : wrapperW - this.cropOffsertX
+              this.cropOffsertX = this.cropChangeX
+            } else {
+              // 右侧坐标抽 超过左侧
+              this.cropW =
+                wrapperW - this.cropChangeX + Math.abs(fw + this.cropOldW) <=
+                wrapperW - minX
+                  ? Math.abs(fw + this.cropOldW)
+                  : this.cropChangeX - minX
+              this.cropOffsertX =
+                wrapperW - this.cropChangeX + Math.abs(fw + this.cropOldW) <=
+                wrapperW - minX
+                  ? this.cropChangeX - Math.abs(fw + this.cropOldW)
+                  : minX
+            }
+          }
+        }
+
+        if (this.canChangeY) {
+          if (this.changeCropTypeY === 1) {
+            if (this.cropOldH - fh < minCropH) {
+              this.cropH = minCropH
+              this.cropOffsertY =
+                this.cropOldH + this.cropChangeY - minY - minCropH
+            } else if (this.cropOldH - fh > 0) {
+              this.cropH =
+                wrapperH - this.cropChangeY - fh <= wrapperH - minY
+                  ? this.cropOldH - fh
+                  : this.cropOldH + this.cropChangeY - minY
+              this.cropOffsertY =
+                wrapperH - this.cropChangeY - fh <= wrapperH - minY
+                  ? this.cropChangeY + fh
+                  : minY
+            } else {
+              this.cropH =
+                Math.abs(fh) + this.cropChangeY <= wrapperH
+                  ? Math.abs(fh) - this.cropOldH
+                  : wrapperH - this.cropOldH - this.cropChangeY
+              this.cropOffsertY = this.cropChangeY + this.cropOldH
+            }
+          } else if (this.changeCropTypeY === 2) {
+            if (this.cropOldH + fh < minCropH) {
+              this.cropH = minCropH
+            } else if (this.cropOldH + fh > 0) {
+              this.cropH =
+                this.cropOldH + fh + this.cropOffsertY <= wrapperH
+                  ? this.cropOldH + fh
+                  : wrapperH - this.cropOffsertY
+              this.cropOffsertY = this.cropChangeY
+            } else {
+              this.cropH =
+                wrapperH - this.cropChangeY + Math.abs(fh + this.cropOldH) <=
+                wrapperH - minY
+                  ? Math.abs(fh + this.cropOldH)
+                  : this.cropChangeY - minY
+              this.cropOffsertY =
+                wrapperH - this.cropChangeY + Math.abs(fh + this.cropOldH) <=
+                wrapperH - minY
+                  ? this.cropChangeY - Math.abs(fh + this.cropOldH)
+                  : minY
+            }
+          }
+        }
+
+        if (this.canChangeX && this.fixed) {
+          const fixedHeight =
+            (this.cropW / this.fixedNumber[0]) * this.fixedNumber[1]
+          if (fixedHeight < minCropH) {
+            this.cropH = minCropH
+            this.cropW = (this.fixedNumber[0] * minCropH) / this.fixedNumber[1]
+            // 这里需要去修改 offsetX的值,去调整因为高度变化而导致的宽度变化
+            if (this.changeCropTypeX === 1) {
+              this.cropOffsertX =
+                this.cropChangeX + (this.cropOldW - this.cropW)
+            }
+          } else if (fixedHeight + this.cropOffsertY > wrapperH) {
+            this.cropH = wrapperH - this.cropOffsertY
+            this.cropW =
+              (this.cropH / this.fixedNumber[1]) * this.fixedNumber[0]
+            if (this.changeCropTypeX === 1) {
+              this.cropOffsertX =
+                this.cropChangeX + (this.cropOldW - this.cropW)
+            }
+          } else {
+            this.cropH = fixedHeight
+          }
+        }
+
+        if (this.canChangeY && this.fixed) {
+          const fixedWidth =
+            (this.cropH / this.fixedNumber[1]) * this.fixedNumber[0]
+          if (fixedWidth < minCropW) {
+            this.cropW = minCropW
+            this.cropH = (this.fixedNumber[1] * minCropW) / this.fixedNumber[0]
+          } else if (fixedWidth + this.cropOffsertX > wrapperW) {
+            this.cropW = wrapperW - this.cropOffsertX
+            this.cropH =
+              (this.cropW / this.fixedNumber[0]) * this.fixedNumber[1]
+          } else {
+            this.cropW = fixedWidth
+          }
+        }
+        // 触发截图框改变大小事件
+        this.$emit('cropSizing', { cropW: this.cropW, cropH: this.cropH })
+        this.$emit('crop-sizing', { cropW: this.cropW, cropH: this.cropH })
+      })
+    },
+
+    checkCropLimitSize() {
+      let { cropW, cropH, limitMinSize } = this
+
+      let limitMinNum = new Array()
+      if (!Array.isArray(limitMinSize)) {
+        limitMinNum = [limitMinSize, limitMinSize]
+      } else {
+        limitMinNum = limitMinSize
+      }
+
+      // 限制最小宽度和高度
+      cropW = parseFloat(limitMinNum[0])
+      cropH = parseFloat(limitMinNum[1])
+      return [cropW, cropH]
+    },
+    // 结束改变
+    changeCropEnd(e) {
+      window.removeEventListener('mousemove', this.changeCropNow)
+      window.removeEventListener('mouseup', this.changeCropEnd)
+      window.removeEventListener('touchmove', this.changeCropNow)
+      window.removeEventListener('touchend', this.changeCropEnd)
+    },
+    // 根据比例x/y,最小宽度,最小高度,现有宽度,现有高度,得到应该有的宽度和高度
+    calculateSize(x, y, minX, minY, w, h) {
+      const ratio = x / y
+      let width = w
+      let height = h
+      // 先根据最小宽度来计算高度
+      if (width < minX) {
+        width = minX
+        height = Math.ceil(width / ratio)
+      }
+      // 如果计算出来的高度小于最小高度,则根据最小高度来重新计算宽度和高度
+      if (height < minY) {
+        height = minY
+        width = Math.ceil(height * ratio)
+        // 如果重新计算的宽度仍然小于最小宽度,则使用最小宽度,并重新计算高度
+        if (width < minX) {
+          width = minX
+          height = Math.ceil(width / ratio)
+        }
+      }
+      // 如果计算出来的宽度或高度小于输入的宽度或高度,则分别使用输入的宽度或高度
+      if (width < w) {
+        width = w
+        height = Math.ceil(width / ratio)
+      }
+      if (height < h) {
+        height = h
+        width = Math.ceil(height * ratio)
+      }
+      return { width, height }
+    },
+    // 创建完成
+    endCrop() {
+      if (this.cropW === 0 && this.cropH === 0) {
+        this.cropping = false
+      }
+      const [minCropW, minCropH] = this.checkCropLimitSize()
+      const { width, height } = this.fixed
+        ? this.calculateSize(
+            this.fixedNumber[0],
+            this.fixedNumber[1],
+            minCropW,
+            minCropH,
+            this.cropW,
+            this.cropH
+          )
+        : { width: minCropW, height: minCropH }
+      if (width > this.cropW) {
+        this.cropW = width
+        if (this.cropOffsertX + width > this.w) {
+          this.cropOffsertX = this.w - width
+        }
+      }
+      if (height > this.cropH) {
+        this.cropH = height
+        if (this.cropOffsertY + height > this.h) {
+          this.cropOffsertY = this.h - height
+        }
+      }
+      window.removeEventListener('mousemove', this.createCrop)
+      window.removeEventListener('mouseup', this.endCrop)
+      window.removeEventListener('touchmove', this.createCrop)
+      window.removeEventListener('touchend', this.endCrop)
+    },
+    // 开始截图
+    startCrop() {
+      this.crop = true
+    },
+    // 停止截图
+    stopCrop() {
+      this.crop = false
+    },
+    // 清除截图
+    clearCrop() {
+      this.cropping = false
+      this.cropW = 0
+      this.cropH = 0
+    },
+    // 截图移动
+    cropMove(e) {
+      e.preventDefault()
+      if (!this.canMoveBox) {
+        this.crop = false
+        this.startMove(e)
+        return false
+      }
+
+      if (e.touches && e.touches.length === 2) {
+        this.crop = false
+        this.startMove(e)
+        this.leaveCrop()
+        return false
+      }
+      window.addEventListener('mousemove', this.moveCrop)
+      window.addEventListener('mouseup', this.leaveCrop)
+      window.addEventListener('touchmove', this.moveCrop)
+      window.addEventListener('touchend', this.leaveCrop)
+      const x = 'clientX' in e ? e.clientX : e.touches[0].clientX
+      const y = 'clientY' in e ? e.clientY : e.touches[0].clientY
+      let newX, newY
+      newX = x - this.cropOffsertX
+      newY = y - this.cropOffsertY
+      this.cropX = newX
+      this.cropY = newY
+      // 触发截图框移动事件
+      this.$emit('cropMoving', {
+        moving: true,
+        axis: this.getCropAxis()
+      })
+      this.$emit('crop-moving', {
+        moving: true,
+        axis: this.getCropAxis()
+      })
+    },
+
+    moveCrop(e, isMove) {
+      let nowX = 0
+      let nowY = 0
+      if (e) {
+        e.preventDefault()
+        nowX = 'clientX' in e ? e.clientX : e.touches[0].clientX
+        nowY = 'clientY' in e ? e.clientY : e.touches[0].clientY
+      }
+      this.$nextTick(() => {
+        let cx, cy
+        let fw = nowX - this.cropX
+        let fh = nowY - this.cropY
+        if (isMove) {
+          fw = this.cropOffsertX
+          fh = this.cropOffsertY
+        }
+        // 不能超过外层容器
+        if (fw <= 0) {
+          cx = 0
+        } else if (fw + this.cropW > this.w) {
+          cx = this.w - this.cropW
+        } else {
+          cx = fw
+        }
+
+        if (fh <= 0) {
+          cy = 0
+        } else if (fh + this.cropH > this.h) {
+          cy = this.h - this.cropH
+        } else {
+          cy = fh
+        }
+
+        // 不能超过图片
+        if (this.centerBox) {
+          const axis = this.getImgAxis()
+          // 横坐标判断
+          if (cx <= axis.x1) {
+            cx = axis.x1
+          }
+
+          if (cx + this.cropW > axis.x2) {
+            cx = axis.x2 - this.cropW
+          }
+
+          // 纵坐标纵轴
+          if (cy <= axis.y1) {
+            cy = axis.y1
+          }
+
+          if (cy + this.cropH > axis.y2) {
+            cy = axis.y2 - this.cropH
+          }
+        }
+
+        this.cropOffsertX = cx
+        this.cropOffsertY = cy
+
+        // 触发截图框移动事件
+        this.$emit('cropMoving', {
+          moving: true,
+          axis: this.getCropAxis()
+        })
+        this.$emit('crop-moving', {
+          moving: true,
+          axis: this.getCropAxis()
+        })
+      })
+    },
+
+    // 算出不同场景下面 图片相对于外层容器的坐标轴
+    getImgAxis(x, y, scale) {
+      x = x || this.x
+      y = y || this.y
+      scale = scale || this.scale
+      // 如果设置了截图框在图片内, 那么限制截图框不能超过图片的坐标
+      // 图片的坐标
+      const obj = {
+        x1: 0,
+        x2: 0,
+        y1: 0,
+        y2: 0
+      }
+      const imgW = this.trueWidth * scale
+      const imgH = this.trueHeight * scale
+      switch (this.rotate) {
+        case 0:
+          obj.x1 = x + (this.trueWidth * (1 - scale)) / 2
+          obj.x2 = obj.x1 + this.trueWidth * scale
+          obj.y1 = y + (this.trueHeight * (1 - scale)) / 2
+          obj.y2 = obj.y1 + this.trueHeight * scale
+          break
+        case 1:
+        case -1:
+        case 3:
+        case -3:
+          obj.x1 = x + (this.trueWidth * (1 - scale)) / 2 + (imgW - imgH) / 2
+          obj.x2 = obj.x1 + this.trueHeight * scale
+          obj.y1 = y + (this.trueHeight * (1 - scale)) / 2 + (imgH - imgW) / 2
+          obj.y2 = obj.y1 + this.trueWidth * scale
+          break
+        default:
+          obj.x1 = x + (this.trueWidth * (1 - scale)) / 2
+          obj.x2 = obj.x1 + this.trueWidth * scale
+          obj.y1 = y + (this.trueHeight * (1 - scale)) / 2
+          obj.y2 = obj.y1 + this.trueHeight * scale
+          break
+      }
+      return obj
+    },
+
+    // 获取截图框的坐标轴
+    getCropAxis() {
+      const obj = {
+        x1: 0,
+        x2: 0,
+        y1: 0,
+        y2: 0
+      }
+      obj.x1 = this.cropOffsertX
+      obj.x2 = obj.x1 + this.cropW
+      obj.y1 = this.cropOffsertY
+      obj.y2 = obj.y1 + this.cropH
+      return obj
+    },
+
+    leaveCrop(e) {
+      window.removeEventListener('mousemove', this.moveCrop)
+      window.removeEventListener('mouseup', this.leaveCrop)
+      window.removeEventListener('touchmove', this.moveCrop)
+      window.removeEventListener('touchend', this.leaveCrop)
+      // 触发截图框移动事件
+      this.$emit('cropMoving', {
+        moving: false,
+        axis: this.getCropAxis()
+      })
+      this.$emit('crop-moving', {
+        moving: false,
+        axis: this.getCropAxis()
+      })
+    },
+
+    getCropChecked(cb) {
+      const canvas = document.createElement('canvas')
+      const img = new Image()
+      const rotate = this.rotate
+      const trueWidth = this.trueWidth
+      const trueHeight = this.trueHeight
+      const cropOffsertX = this.cropOffsertX
+      const cropOffsertY = this.cropOffsertY
+      img.onload = () => {
+        if (this.cropW !== 0) {
+          const ctx = canvas.getContext('2d')
+          let dpr = 1
+          if (this.high & !this.full) {
+            dpr = window.devicePixelRatio
+          }
+          if ((this.enlarge !== 1) & !this.full) {
+            dpr = Math.abs(Number(this.enlarge))
+          }
+          const width = this.cropW * dpr
+          const height = this.cropH * dpr
+          const imgW = trueWidth * this.scale * dpr
+          const imgH = trueHeight * this.scale * dpr
+          // 图片x轴偏移
+          let dx =
+            (this.x - cropOffsertX + (this.trueWidth * (1 - this.scale)) / 2) *
+            dpr
+          // 图片y轴偏移
+          let dy =
+            (this.y - cropOffsertY + (this.trueHeight * (1 - this.scale)) / 2) *
+            dpr
+          // 保存状态
+          setCanvasSize(width, height)
+          ctx.save()
+          // 填充背景颜色
+          if (this.fillColor) {
+            ctx.fillStyle = this.fillColor
+            ctx.fillRect(0, 0, canvas.width, canvas.height)
+          }
+          switch (rotate) {
+            case 0:
+              if (!this.full) {
+                ctx.drawImage(img, dx, dy, imgW, imgH)
+              } else {
+                // 输出原图比例截图
+                setCanvasSize(width / this.scale, height / this.scale)
+                ctx.drawImage(
+                  img,
+                  dx / this.scale,
+                  dy / this.scale,
+                  imgW / this.scale,
+                  imgH / this.scale
+                )
+              }
+              break
+            case 1:
+            case -3:
+              if (!this.full) {
+                // 换算图片旋转后的坐标弥补
+                dx = dx + (imgW - imgH) / 2
+                dy = dy + (imgH - imgW) / 2
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                ctx.drawImage(img, dy, -dx - imgH, imgW, imgH)
+              } else {
+                setCanvasSize(width / this.scale, height / this.scale)
+                // 换算图片旋转后的坐标弥补
+                dx =
+                  dx / this.scale + (imgW / this.scale - imgH / this.scale) / 2
+                dy =
+                  dy / this.scale + (imgH / this.scale - imgW / this.scale) / 2
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                ctx.drawImage(
+                  img,
+                  dy,
+                  -dx - imgH / this.scale,
+                  imgW / this.scale,
+                  imgH / this.scale
+                )
+              }
+              break
+            case 2:
+            case -2:
+              if (!this.full) {
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                ctx.drawImage(img, -dx - imgW, -dy - imgH, imgW, imgH)
+              } else {
+                setCanvasSize(width / this.scale, height / this.scale)
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                dx = dx / this.scale
+                dy = dy / this.scale
+                ctx.drawImage(
+                  img,
+                  -dx - imgW / this.scale,
+                  -dy - imgH / this.scale,
+                  imgW / this.scale,
+                  imgH / this.scale
+                )
+              }
+              break
+            case 3:
+            case -1:
+              if (!this.full) {
+                // 换算图片旋转后的坐标弥补
+                dx = dx + (imgW - imgH) / 2
+                dy = dy + (imgH - imgW) / 2
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                ctx.drawImage(img, -dy - imgW, dx, imgW, imgH)
+              } else {
+                setCanvasSize(width / this.scale, height / this.scale)
+                // 换算图片旋转后的坐标弥补
+                dx =
+                  dx / this.scale + (imgW / this.scale - imgH / this.scale) / 2
+                dy =
+                  dy / this.scale + (imgH / this.scale - imgW / this.scale) / 2
+                ctx.rotate((rotate * 90 * Math.PI) / 180)
+                ctx.drawImage(
+                  img,
+                  -dy - imgW / this.scale,
+                  dx,
+                  imgW / this.scale,
+                  imgH / this.scale
+                )
+              }
+              break
+            default:
+              if (!this.full) {
+                ctx.drawImage(img, dx, dy, imgW, imgH)
+              } else {
+                // 输出原图比例截图
+                setCanvasSize(width / this.scale, height / this.scale)
+                ctx.drawImage(
+                  img,
+                  dx / this.scale,
+                  dy / this.scale,
+                  imgW / this.scale,
+                  imgH / this.scale
+                )
+              }
+          }
+          ctx.restore()
+        } else {
+          const width = trueWidth * this.scale
+          const height = trueHeight * this.scale
+          const ctx = canvas.getContext('2d')
+          ctx.save()
+          // 填充背景颜色
+          if (this.fillColor) {
+            ctx.fillStyle = this.fillColor
+            ctx.fillRect(0, 0, canvas.width, canvas.height)
+          }
+          switch (rotate) {
+            case 0:
+              setCanvasSize(width, height)
+              ctx.drawImage(img, 0, 0, width, height)
+              break
+            case 1:
+            case -3:
+              // 旋转90度 或者-270度 宽度和高度对调
+              setCanvasSize(height, width)
+              ctx.rotate((rotate * 90 * Math.PI) / 180)
+              ctx.drawImage(img, 0, -height, width, height)
+              break
+            case 2:
+            case -2:
+              setCanvasSize(width, height)
+              ctx.rotate((rotate * 90 * Math.PI) / 180)
+              ctx.drawImage(img, -width, -height, width, height)
+              break
+            case 3:
+            case -1:
+              setCanvasSize(height, width)
+              ctx.rotate((rotate * 90 * Math.PI) / 180)
+              ctx.drawImage(img, -width, 0, width, height)
+              break
+            default:
+              setCanvasSize(width, height)
+              ctx.drawImage(img, 0, 0, width, height)
+          }
+          ctx.restore()
+        }
+        cb(canvas)
+      }
+      // 判断图片是否是base64
+      const s = this.img.substr(0, 4)
+      if (s !== 'data') {
+        img.crossOrigin = 'Anonymous'
+      }
+      img.src = this.imgs
+
+      function setCanvasSize(width, height) {
+        canvas.width = Math.round(width)
+        canvas.height = Math.round(height)
+      }
+    },
+
+    // 获取转换成base64 的图片信息
+    getCropData(cb) {
+      this.getCropChecked(data => {
+        cb(data.toDataURL('image/' + this.outputType, this.outputSize))
+      })
+    },
+
+    // canvas获取为blob对象
+    getCropBlob(cb) {
+      this.getCropChecked(data => {
+        data.toBlob(
+          blob => cb(blob),
+          'image/' + this.outputType,
+          this.outputSize
+        )
+      })
+    },
+
+    // 自动预览函数
+    showPreview() {
+      // 优化不要多次触发
+      if (this.isCanShow) {
+        this.isCanShow = false
+        setTimeout(() => {
+          this.isCanShow = true
+        }, 16)
+      } else {
+        return false
+      }
+      const w = this.cropW
+      const h = this.cropH
+      const scale = this.scale
+      const obj = {}
+      obj.div = {
+        width: `${w}px`,
+        height: `${h}px`
+      }
+      const transformX = (this.x - this.cropOffsertX) / scale
+      const transformY = (this.y - this.cropOffsertY) / scale
+      const transformZ = 0
+      obj.w = w
+      obj.h = h
+      obj.url = this.imgs
+      obj.img = {
+        width: `${this.trueWidth}px`,
+        height: `${this.trueHeight}px`,
+        transform: `scale(${scale})translate3d(${transformX}px, ${transformY}px, ${transformZ}px)rotateZ(${
+          this.rotate * 90
+        }deg)`
+      }
+      obj.html = `
+      <div class="show-preview" style="width: ${obj.w}px; height: ${
+        obj.h
+      }px; overflow: hidden">
+        <div style="width: ${w}px; height: ${h}px">
+          <img src=${obj.url} style="width: ${this.trueWidth}px; height: ${
+        this.trueHeight
+      }px; transform:
+          scale(${scale})translate3d(${transformX}px, ${transformY}px, ${transformZ}px)rotateZ(${
+        this.rotate * 90
+      }deg)">
+        </div>
+      </div>`
+      this.$emit('realTime', obj)
+      this.$emit('real-time', obj)
+    },
+    // reload 图片布局函数
+    reload() {
+      const img = new Image()
+      img.onload = () => {
+        // 读取图片的信息原始信息, 解析是否需要旋转
+        // 读取图片的旋转信息
+        // 得到外层容器的宽度高度
+        this.w = parseFloat(window.getComputedStyle(this.$refs.cropper).width)
+        this.h = parseFloat(window.getComputedStyle(this.$refs.cropper).height)
+
+        // 存入图片真实高度
+        this.trueWidth = img.width
+        this.trueHeight = img.height
+
+        // 判断是否需要压缩大图
+        if (!this.original) {
+          // 判断布局方式 mode
+          this.scale = this.checkedMode()
+        } else {
+          this.scale = 1
+        }
+
+        this.$nextTick(() => {
+          this.x =
+            -(this.trueWidth - this.trueWidth * this.scale) / 2 +
+            (this.w - this.trueWidth * this.scale) / 2
+          this.y =
+            -(this.trueHeight - this.trueHeight * this.scale) / 2 +
+            (this.h - this.trueHeight * this.scale) / 2
+          this.loading = false
+          // // 获取是否开启了自动截图
+          if (this.autoCrop) {
+            this.goAutoCrop()
+          }
+          // 图片加载成功的回调
+          this.$emit('img-load', 'success')
+          this.$emit('imgLoad', 'success')
+          setTimeout(() => {
+            this.showPreview()
+          }, 20)
+        })
+      }
+      img.onerror = () => {
+        this.$emit('imgLoad', 'error')
+        this.$emit('img-load', 'error')
+      }
+      img.src = this.imgs
+    },
+    // 背景布局的函数
+    checkedMode() {
+      let scale = 1
+      // 通过字符串分割
+      let imgW = this.trueWidth
+      let imgH = this.trueHeight
+      const arr = this.mode.split(' ')
+      switch (arr[0]) {
+        case 'contain':
+          if (this.trueWidth > this.w) {
+            // 如果图片宽度大于容器宽度
+            scale = this.w / this.trueWidth
+          }
+
+          if (this.trueHeight * scale > this.h) {
+            scale = this.h / this.trueHeight
+          }
+          break
+        case 'cover':
+          // 扩展布局 默认填充满整个容器
+          // 图片宽度大于容器
+          imgW = this.w
+          scale = imgW / this.trueWidth
+          imgH = imgH * scale
+          // 如果扩展之后高度小于容器的外层高度 继续扩展高度
+          if (imgH < this.h) {
+            imgH = this.h
+            scale = imgH / this.trueHeight
+          }
+          break
+        default:
+          try {
+            let str = arr[0]
+            if (str.search('px') !== -1) {
+              str = str.replace('px', '')
+              imgW = parseFloat(str)
+              const scaleX = imgW / this.trueWidth
+              let scaleY = 1
+              let strH = arr[1]
+              if (strH.search('px') !== -1) {
+                strH = strH.replace('px', '')
+                imgH = parseFloat(strH)
+                scaleY = imgH / this.trueHeight
+              }
+              scale = Math.min(scaleX, scaleY)
+            }
+            if (str.search('%') !== -1) {
+              str = str.replace('%', '')
+              imgW = (parseFloat(str) / 100) * this.w
+              scale = imgW / this.trueWidth
+            }
+
+            if (arr.length === 2 && str === 'auto') {
+              let str2 = arr[1]
+              if (str2.search('px') !== -1) {
+                str2 = str2.replace('px', '')
+                imgH = parseFloat(str2)
+                scale = imgH / this.trueHeight
+              }
+              if (str2.search('%') !== -1) {
+                str2 = str2.replace('%', '')
+                imgH = (parseFloat(str2) / 100) * this.h
+                scale = imgH / this.trueHeight
+              }
+            }
+          } catch (error) {
+            scale = 1
+          }
+      }
+      return scale
+    },
+    // 自动截图函数
+    goAutoCrop(cw, ch) {
+      if (this.imgs === '' || this.imgs === null) return
+      this.clearCrop()
+      this.cropping = true
+      let maxWidth = this.w
+      let maxHeight = this.h
+      if (this.centerBox) {
+        const switchWH = Math.abs(this.rotate) % 2 > 0
+        const imgW = (switchWH ? this.trueHeight : this.trueWidth) * this.scale
+        const imgH = (switchWH ? this.trueWidth : this.trueHeight) * this.scale
+        maxWidth = imgW < maxWidth ? imgW : maxWidth
+        maxHeight = imgH < maxHeight ? imgH : maxHeight
+      }
+      // 截图框默认大小
+      // 如果为0 那么计算容器大小 默认为80%
+      let w = cw || parseFloat(this.autoCropWidth)
+      let h = ch || parseFloat(this.autoCropHeight)
+      if (w === 0 || h === 0) {
+        w = maxWidth * 0.8
+        h = maxHeight * 0.8
+      }
+      w = w > maxWidth ? maxWidth : w
+      h = h > maxHeight ? maxHeight : h
+      if (this.fixed) {
+        h = (w / this.fixedNumber[0]) * this.fixedNumber[1]
+      }
+      // 如果比例之后 高度大于h
+      if (h > this.h) {
+        h = this.h
+        w = (h / this.fixedNumber[1]) * this.fixedNumber[0]
+      }
+      this.changeCrop(w, h)
+    },
+    // 手动改变截图框大小函数
+    changeCrop(w, h) {
+      if (this.centerBox) {
+        // 修复初始化时候在centerBox=true情况下
+        const axis = this.getImgAxis()
+        if (w > axis.x2 - axis.x1) {
+          // 宽度超标
+          w = axis.x2 - axis.x1
+          h = (w / this.fixedNumber[0]) * this.fixedNumber[1]
+        }
+        if (h > axis.y2 - axis.y1) {
+          // 高度超标
+          h = axis.y2 - axis.y1
+          w = (h / this.fixedNumber[1]) * this.fixedNumber[0]
+        }
+      }
+      // 判断是否大于容器
+      this.cropW = w
+      this.cropH = h
+      this.checkCropLimitSize()
+      this.$nextTick(() => {
+        // 居中走一走
+        this.cropOffsertX = (this.w - this.cropW) / 2
+        this.cropOffsertY = (this.h - this.cropH) / 2
+        if (this.centerBox) {
+          this.moveCrop(null, true)
+        }
+      })
+    },
+    // 重置函数, 恢复组件置初始状态
+    refresh() {
+      const img = this.img
+      this.imgs = ''
+      this.scale = 1
+      this.crop = false
+      this.rotate = 0
+      this.w = 0
+      this.h = 0
+      this.trueWidth = 0
+      this.trueHeight = 0
+      this.clearCrop()
+      this.$nextTick(() => {
+        this.checkedImg()
+      })
+    },
+
+    // 向左边旋转
+    rotateLeft() {
+      this.rotate = this.rotate <= -3 ? 0 : this.rotate - 1
+    },
+
+    // 向右边旋转
+    rotateRight() {
+      this.rotate = this.rotate >= 3 ? 0 : this.rotate + 1
+    },
+
+    // 清除旋转
+    rotateClear() {
+      this.rotate = 0
+    },
+
+    // 图片坐标点校验
+    checkoutImgAxis(x, y, scale) {
+      x = x || this.x
+      y = y || this.y
+      scale = scale || this.scale
+      let canGo = true
+      // 开始校验 如果说缩放之后的坐标在截图框外 则阻止缩放
+      if (this.centerBox) {
+        const axis = this.getImgAxis(x, y, scale)
+        const cropAxis = this.getCropAxis()
+        // 左边的横坐标 图片不能超过截图框
+        if (axis.x1 >= cropAxis.x1) {
+          canGo = false
+        }
+
+        // 右边横坐标
+        if (axis.x2 <= cropAxis.x2) {
+          canGo = false
+        }
+
+        // 纵坐标上面
+        if (axis.y1 >= cropAxis.y1) {
+          canGo = false
+        }
+
+        // 纵坐标下面
+        if (axis.y2 <= cropAxis.y2) {
+          canGo = false
+        }
+      }
+      return canGo
+    }
+  },
+  mounted() {
+    this.support =
+      'onwheel' in document.createElement('div')
+        ? 'wheel'
+        : document.onmousewheel !== undefined
+        ? 'mousewheel'
+        : 'DOMMouseScroll'
+    const that = this
+    const u = navigator.userAgent
+    this.isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
+    // 兼容blob
+    if (!HTMLCanvasElement.prototype.toBlob) {
+      Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
+        value: function (callback, type, quality) {
+          const binStr = atob(this.toDataURL(type, quality).split(',')[1])
+          const len = binStr.length
+          const arr = new Uint8Array(len)
+          for (let i = 0; i < len; i++) {
+            arr[i] = binStr.charCodeAt(i)
+          }
+          callback(new Blob([arr], { type: that.type || 'image/png' }))
+        }
+      })
+    }
+    this.showPreview()
+    this.checkedImg()
+  },
+  unmounted() {
+    window.removeEventListener('mousemove', this.moveCrop)
+    window.removeEventListener('mouseup', this.leaveCrop)
+    window.removeEventListener('touchmove', this.moveCrop)
+    window.removeEventListener('touchend', this.leaveCrop)
+    this.cancelScale()
+  }
+}
+</script>
+
+<style scoped lang="css">
+.vue-cropper {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+  user-select: none;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  direction: ltr;
+  touch-action: none;
+  text-align: left;
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
+}
+
+.cropper-box,
+.cropper-box-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-face {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  background-color: red;
+  user-select: none;
+}
+
+.cropper-box-canvas img {
+  position: relative;
+  text-align: left;
+  user-select: none;
+  transform: none;
+  max-width: none;
+  max-height: none;
+}
+
+.cropper-box {
+  overflow: hidden;
+}
+
+.cropper-move {
+  cursor: move;
+}
+
+.cropper-crop {
+  cursor: crosshair;
+}
+
+.cropper-modal {
+  background: rgba(0, 0, 0, 0.5);
+}
+
+.cropper-crop-box {
+  /*border: 2px solid #39f;*/
+}
+
+.cropper-view-box {
+  display: block;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+  outline: 1px solid #39f;
+  outline-color: rgba(51, 153, 255, 0.75);
+  user-select: none;
+}
+
+.cropper-view-box img {
+  user-select: none;
+  text-align: left;
+  max-width: none;
+  max-height: none;
+}
+
+.cropper-face {
+  top: 0;
+  left: 0;
+  background-color: #fff;
+  opacity: 0.1;
+}
+
+.crop-info {
+  position: absolute;
+  left: 0px;
+  min-width: 65px;
+  text-align: center;
+  color: white;
+  line-height: 20px;
+  background-color: rgba(0, 0, 0, 0.8);
+  font-size: 12px;
+}
+
+.crop-line {
+  position: absolute;
+  display: block;
+  width: 100%;
+  height: 100%;
+  opacity: 0.1;
+}
+
+.line-w {
+  top: -3px;
+  left: 0;
+  height: 5px;
+  cursor: n-resize;
+}
+
+.line-a {
+  top: 0;
+  left: -3px;
+  width: 5px;
+  cursor: w-resize;
+}
+
+.line-s {
+  bottom: -3px;
+  left: 0;
+  height: 5px;
+  cursor: s-resize;
+}
+
+.line-d {
+  top: 0;
+  right: -3px;
+  width: 5px;
+  cursor: e-resize;
+}
+
+.crop-point {
+  position: absolute;
+  width: 8px;
+  height: 8px;
+  opacity: 0.75;
+  background-color: #39f;
+  border-radius: 100%;
+}
+
+.point1 {
+  top: -4px;
+  left: -4px;
+  cursor: nw-resize;
+}
+
+.point2 {
+  top: -5px;
+  left: 50%;
+  margin-left: -3px;
+  cursor: n-resize;
+}
+
+.point3 {
+  top: -4px;
+  right: -4px;
+  cursor: ne-resize;
+}
+
+.point4 {
+  top: 50%;
+  left: -4px;
+  margin-top: -3px;
+  cursor: w-resize;
+}
+
+.point5 {
+  top: 50%;
+  right: -4px;
+  margin-top: -3px;
+  cursor: e-resize;
+}
+
+.point6 {
+  bottom: -5px;
+  left: -4px;
+  cursor: sw-resize;
+}
+
+.point7 {
+  bottom: -5px;
+  left: 50%;
+  margin-left: -3px;
+  cursor: s-resize;
+}
+
+.point8 {
+  bottom: -5px;
+  right: -4px;
+  cursor: se-resize;
+}
+
+@media screen and (max-width: 500px) {
+  .crop-point {
+    position: absolute;
+    width: 20px;
+    height: 20px;
+    opacity: 0.45;
+    background-color: #39f;
+    border-radius: 100%;
+  }
+
+  .point1 {
+    top: -10px;
+    left: -10px;
+  }
+
+  .point2,
+  .point4,
+  .point5,
+  .point7 {
+    display: none;
+  }
+
+  .point3 {
+    top: -10px;
+    right: -10px;
+  }
+
+  .point4 {
+    top: 0;
+    left: 0;
+  }
+
+  .point6 {
+    bottom: -10px;
+    left: -10px;
+  }
+
+  .point8 {
+    bottom: -10px;
+    right: -10px;
+  }
+}
+</style>

+ 184 - 8
src/views/dash/compoents/profile.vue

@@ -2,8 +2,11 @@
   <div class="full-width flex">
     <div class="flex flex-col flex-justify-around flex-center full-height">
       <div class="bold font-16" style="margin-top: -10px">我的信息</div>
-      <div class="mt-10 ml-20">
+      <div class="mt-10 ml-20" @click="show = true">
         <el-avatar :src="user.info.avatarUrl" :size="100"></el-avatar>
+        <el-icon :size="25" style="margin-left: -30px">
+          <EditPen />
+        </el-icon>
       </div>
       <div class="mt-10 font-16 main-color bold ml-15">
         {{ user.info.nickName }}
@@ -16,31 +19,115 @@
       <el-input
         :placeholder="placeholder"
         size="large"
+        maxlength="6"
         v-model="status"
         class="input"
       ></el-input>
       <div
         class="flex flex-center flex-justify-between mt-10 padding-left padding-right"
       >
-        <img
+        <el-tooltip
           v-for="icon in icons"
           :key="icon.value"
-          :src="icon.icon"
-          class="icon-task"
-        />
+          :content="icon.tips"
+        >
+          <img
+            :src="icon.icon"
+            class="icon-task pointer"
+            @click="changeWorkStatus(icon)"
+          />
+        </el-tooltip>
       </div>
     </div>
+
+    <el-dialog title="修改头像" v-model="show" width="780px" @close="close">
+      <div>
+        <div>
+          <span
+            class="full-width text-left flex flex-justify-start padding-top padding-bottom"
+          >
+            新头像不允许涉及政治敏感与色情;<br />
+            图片格式必须为:png,jpeg,jpg;不可大于2M;<br />
+            建议使用png格式图片,以保持最佳效果;建议图片尺寸为144px*144px
+          </span>
+          <div class="flex flex-justify-start padding-bottom">
+            <upload-office
+              accept=".jpg,.png,.jpeg"
+              :max="1"
+              :show-progress="false"
+              @upload="upload"
+            />
+          </div>
+        </div>
+        <el-divider />
+        <div class="flex flex-justify-start">
+          <div class="ml-20 flex flex-justify-start flex-col">
+            <div class="bold text-left">头像裁剪</div>
+            <div class="mt-20 upload flex flex-center">
+              <cropper
+                ref="cropper"
+                class="cropper"
+                :src="img"
+                :stencil-props="{
+                  aspectRatio: 1 / 1,
+                  resizable: false
+                }"
+                @change="changeAvatar"
+              ></cropper>
+            </div>
+          </div>
+          <div class="ml-20 flex flex-justify-start flex-col">
+            <div class="bold text-left">头像预览</div>
+            <div class="mt-20 preview flex flex-center">
+              <el-avatar
+                v-if="previewImg"
+                :size="114"
+                fit="fill"
+                :src="previewImg"
+              ></el-avatar>
+
+              <el-avatar
+                v-if="previewImg"
+                :size="114"
+                shape="square"
+                fit="fill"
+                class="ml-20"
+                :src="previewImg"
+              ></el-avatar>
+            </div>
+          </div>
+        </div>
+        <div class="mt-20 full-width flex flex-justify-end">
+          <el-button type="primary" plain @click="show = false"
+            >取 消
+          </el-button>
+          <el-button type="primary" @click="uploadImage">确 定</el-button>
+        </div>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
 import { useStore } from '@/store/user.js'
+import { Cropper } from 'vue-advanced-cropper'
+import 'vue-advanced-cropper/dist/style.css'
+import api from '@/api/index.js'
+import website from '@/config/website.js'
+import { getToken } from '@/utils/auth.js'
+import { Base64 } from 'js-base64'
+import uploadOffice from '@/components/upload-office/index.vue'
 
 export default {
+  components: {
+    Cropper,
+    uploadOffice
+  },
   setup() {
     const user = useStore()
     return { user }
   },
+
   watch: {
     status: {
       handler(val) {
@@ -55,8 +142,18 @@ export default {
   },
   data() {
     return {
+      show: false,
       status: '',
+      img: '',
+      previewImg: '',
       placeholder: '请填写状态(最长6个字符)',
+      id: '',
+      headers: {
+        Authorization: `Basic ${Base64.encode(
+          `${website.clientId}:${website.clientSecret}`
+        )}`,
+        'Blade-Auth': 'bearer ' + getToken()
+      },
       icons: [
         {
           icon: new URL('../../../assets/svg/task/work.svg', import.meta.url)
@@ -93,31 +190,110 @@ export default {
       this.$api.dash.workInfo().then(res => {
         if (res.code === 200) {
           this.placeholder = res.data.workStatusDescribe
+            ? res.data.workStatusDescribe
+            : '请填写状态(最长6个字符)'
+          this.id = res.data.id === -1 ? '' : res.data.id
         } else {
           console.log(res)
         }
       })
     },
     update() {
-      this.$api.dash.submit({ workStatusDescribe: this.status }).then(res => {
+      if (this.status.length === 0) {
+        return
+      }
+      this.$api.dash
+        .submit({ id: this.id, workStatusDescribe: this.status })
+        .then(res => {
+          if (res.code === 200) {
+            console.log(res)
+          } else {
+            console.log(res)
+          }
+        })
+    },
+    changeWorkStatus(res) {
+      this.status = res.tips
+    },
+    upload(list) {
+      this.previewImg = list[0].filePath
+      this.img = list[0].filePath
+    },
+    changeAvatar({ coordinates, image, canvas }) {
+      this.previewImg = canvas.toDataURL()
+    },
+    uploadImage() {
+      const { canvas } = this.$refs.cropper.getResult()
+      if (canvas) {
+        const form = new FormData()
+        canvas.toBlob(blob => {
+          form.append('file', blob)
+          // You can use axios, superagent and other libraries instead here
+          fetch(api.uploadPath, {
+            method: 'POST',
+            headers: this.headers,
+            body: form
+          })
+            .then(response => response.json())
+            .then(body => {
+              if (body.code === 200) {
+                this.updateAvatar(body.data.filePath)
+              }
+            })
+        }, 'image/png')
+      }
+    },
+    updateAvatar(avatar) {
+      const data = {
+        id: this.user.info.userId,
+        deptId: this.user.info.deptId,
+        avatar
+      }
+      this.$api.dash.updateAvatar(data).then(res => {
         if (res.code === 200) {
-          console.log(res)
+          this.$message.success(res.msg)
+          this.user.info.avatarUrl = avatar
+          this.user.setUserInfo(this.user.info)
+          this.show = false
         } else {
-          console.log(res)
+          this.$message.error(res.msg)
         }
       })
+    },
+    close() {
+      this.img = ''
+      this.previewImg = ''
     }
   }
 }
 </script>
 
 <style lang="scss" scoped>
+.preview {
+  background-color: #eeeeee;
+  height: 240px;
+  width: 360px;
+  border-radius: 8px;
+}
+
+.upload {
+  background-color: #eeeeee;
+  height: 240px;
+  width: 320px;
+  border-radius: 8px;
+}
+
 .icon-task {
   width: 36px;
   height: 45px;
   object-fit: fill;
 }
 
+.cropper {
+  width: 280px;
+  height: 280px;
+}
+
 .input {
   height: 100px;
 

+ 10 - 4
src/views/task/index.vue

@@ -29,15 +29,15 @@
       <div class="full-width flex flex-justify-start">
         <el-dropdown @command="dropDown">
           <span class="flex flex-center">
-            按时间排序
+            {{ isGroup === '0' ? '按时间排序' : '按项目排序' }}
             <el-icon class="el-icon--right">
               <arrow-down />
             </el-icon>
           </span>
           <template #dropdown>
             <el-dropdown-menu>
-              <el-dropdown-item command="info">按时间排序</el-dropdown-item>
-              <el-dropdown-item command="logout">按项目排序</el-dropdown-item>
+              <el-dropdown-item command="0">按时间排序</el-dropdown-item>
+              <el-dropdown-item command="1">按项目排序</el-dropdown-item>
             </el-dropdown-menu>
           </template>
         </el-dropdown>
@@ -93,6 +93,7 @@ export default {
       task: [],
       taskId: '',
       total: 0,
+      isGroup: '0',
       option: {
         showCheckBox: false,
         folderChecked: true,
@@ -134,7 +135,7 @@ export default {
       this.getTaskList()
     },
     getTaskList() {
-      const data = { type: this.type, ...this.page }
+      const data = { type: this.type, isGroup: this.isGroup, ...this.page }
       this.$api.task.taskList(data).then(res => {
         if (res.code === 200) {
           this.data = res.data.records
@@ -148,6 +149,11 @@ export default {
     pageChange(current) {
       this.page.current = current
       this.getTaskList()
+    },
+    dropDown(res) {
+      this.isGroup = res
+      this.page.current = 1
+      this.getTaskList()
     }
   }
 }

+ 29 - 5
yarn.lock

@@ -927,6 +927,11 @@ cidr-regex@^3.1.1:
   dependencies:
     ip-regex "^4.1.0"
 
+classnames@^2.2.6:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
+  integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
+
 clean-stack@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
@@ -1059,6 +1064,11 @@ dayjs@^1.10.4, dayjs@^1.11.3:
   resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.8.tgz#4282f139c8c19dd6d0c7bd571e30c2d0ba7698ea"
   integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ==
 
+debounce@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
+  integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
+
 debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
@@ -1188,6 +1198,11 @@ eastasianwidth@^0.2.0:
   resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
   integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
 
+easy-bem@^1.0.2:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/easy-bem/-/easy-bem-1.1.1.tgz#1bfcc10425498090bcfddc0f9c000aba91399e03"
+  integrity sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A==
+
 echarts@^5.4.1:
   version "5.4.2"
   resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.4.2.tgz#9f38781c9c6ae323e896956178f6956952c77a48"
@@ -4032,6 +4047,20 @@ vite@^3.0.0:
   optionalDependencies:
     fsevents "~2.3.2"
 
+vue-advanced-cropper@^2.8.8:
+  version "2.8.8"
+  resolved "https://registry.yarnpkg.com/vue-advanced-cropper/-/vue-advanced-cropper-2.8.8.tgz#af0e8324312be5a1a92ce9fd3aff8264d28a5b33"
+  integrity sha512-yDM7Jb/gnxcs//JdbOogBUoHr1bhCQSto7/ohgETKAe4wvRpmqIkKSppMm1huVQr+GP1YoVlX/fkjKxvYzwwDQ==
+  dependencies:
+    classnames "^2.2.6"
+    debounce "^1.2.0"
+    easy-bem "^1.0.2"
+
+vue-cropper@^1.0.9:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/vue-cropper/-/vue-cropper-1.0.9.tgz#de402c57cadc221e9a2063399ff35bb04220ef22"
+  integrity sha512-JhQwxmjqmQohzI7sAp5O/Rfdxuw5HOEYkKjnp/De7iCi6c8Mv6M3N9HpMt9xgWCFchX3/DfXBv2axCZOCg3G8Q==
+
 vue-demi@*, vue-demi@>=0.14.5:
   version "0.14.5"
   resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.5.tgz#676d0463d1a1266d5ab5cba932e043d8f5f2fbd9"
@@ -4063,11 +4092,6 @@ vue-eslint-parser@^9.3.0:
     lodash "^4.17.21"
     semver "^7.3.6"
 
-vue-infinite-auto-scroll@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/vue-infinite-auto-scroll/-/vue-infinite-auto-scroll-1.2.1.tgz#6e498670cdbe0840dfe5b216073726d4d7f6e47f"
-  integrity sha512-2olo5FfDXDe+CjJH0SjTz4Y7Rlo3vm4BfxBL+jp88OpjC09rbLE/xLyC1xr7hUHTlfIiHBLrHDYcNoG1u7WofQ==
-
 vue-qr@^4.0.9:
   version "4.0.9"
   resolved "https://registry.yarnpkg.com/vue-qr/-/vue-qr-4.0.9.tgz#6cb965dd0c5a0dff947e6ef582ef149b0780b986"