Explorar o código

图片覆盖区允许添加多个

zhangjie %!s(int64=2) %!d(string=hai) anos
pai
achega
9c86691f09

+ 256 - 0
src/assets/styles/pages.less

@@ -344,6 +344,9 @@
 /* cover-area */
 .cover-area {
   height: 100%;
+  background-color: transparent;
+  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
+
   .code-area-preview {
     position: absolute;
     height: 100%;
@@ -357,6 +360,259 @@
     height: 100%;
   }
 }
+/* area-cropper */
+.area-cropper {
+  position: relative;
+  height: 100%;
+  overflow: hidden;
+  user-select: none;
+
+  img {
+    display: block;
+    position: absolute;
+    max-width: 100%;
+    max-height: 100%;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    z-index: 8;
+  }
+  .cropper-areas {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 9;
+  }
+
+  .area-selection {
+    position: absolute;
+    z-index: 999;
+    border: 2px solid @sub-color;
+    background-color: rgba(0, 0, 0, 0.5);
+  }
+
+  .element-item-body {
+    position: absolute;
+    background-color: rgba(0, 0, 0, 0.5);
+  }
+  .element-delete {
+    position: absolute;
+    height: 20px;
+    width: 20px;
+    top: 0;
+    right: -20px;
+    line-height: 20px;
+    text-align: center;
+    font-size: 18px;
+    display: none;
+    z-index: 9;
+    color: @pink-color;
+    cursor: pointer;
+    &:hover {
+      color: tint(@pink-color, 20%);
+    }
+  }
+
+  .element-resize {
+    background-color: transparent;
+    > .resize-control {
+      > .control-point,
+      > .control-line {
+        display: none;
+      }
+    }
+
+    &:hover {
+      > .resize-control {
+        > .control-line {
+          display: block;
+        }
+      }
+    }
+
+    &-act {
+      > .resize-control {
+        > .control-point,
+        > .control-line {
+          display: block;
+        }
+      }
+      .element-delete {
+        display: block;
+      }
+    }
+  }
+  .element-resize-compact {
+    > .resize-control {
+      > .control-line {
+        display: block;
+      }
+    }
+
+    &:hover {
+      > .resize-control {
+        > .control-line {
+          border-color: @sub-color;
+        }
+      }
+    }
+
+    &.element-resize-act {
+      > .resize-control {
+        > .control-line {
+          border-color: @sub-color;
+          &-left,
+          &-right {
+            border-left-style: solid;
+          }
+          &-top,
+          &-bottom {
+            border-top-style: solid;
+          }
+        }
+      }
+    }
+  }
+}
+.element-resize {
+  position: static;
+  z-index: auto;
+  background: #fff;
+  box-sizing: content-box;
+
+  &-move {
+    cursor: move;
+  }
+
+  &-init {
+    > div:first-child {
+      width: 100% !important;
+      height: 100% !important;
+      position: relative !important;
+      top: 0 !important;
+      left: 0 !important;
+      overflow: hidden;
+    }
+  }
+  .control-point {
+    position: absolute;
+    background-color: rgba(51, 51, 51, 0.7);
+    border: 2px solid @sub-color;
+    border-radius: 50%;
+    opacity: 1;
+    width: 10px !important;
+    height: 10px !important;
+    z-index: 99;
+    &-l {
+      left: 0;
+      top: 50%;
+      margin-top: -5px;
+      margin-left: -5px;
+      cursor: w-resize;
+    }
+    &-lt {
+      left: 0;
+      top: 0;
+      margin-top: -5px;
+      margin-left: -5px;
+      cursor: nw-resize;
+    }
+    &-lb {
+      left: 0;
+      bottom: 0;
+      margin-bottom: -5px;
+      margin-left: -5px;
+      cursor: sw-resize;
+    }
+    &-r {
+      right: 0;
+      top: 50%;
+      margin-top: -5px;
+      margin-right: -5px;
+      cursor: e-resize;
+    }
+    &-rt {
+      right: 0;
+      top: 0;
+      margin-top: -5px;
+      margin-right: -5px;
+      cursor: ne-resize;
+    }
+    &-rb {
+      right: 0;
+      bottom: 0;
+      margin-bottom: -5px;
+      margin-right: -5px;
+      cursor: se-resize;
+    }
+    &-t {
+      left: 50%;
+      top: 0;
+      margin-top: -5px;
+      margin-left: -5px;
+      cursor: n-resize;
+    }
+    &-b {
+      left: 50%;
+      bottom: 0;
+      margin-bottom: -5px;
+      margin-left: -5px;
+      cursor: s-resize;
+    }
+  }
+  .control-line {
+    position: absolute;
+    z-index: 98;
+
+    &-left {
+      height: 100%;
+      left: -2px;
+      top: 0;
+      border-left: 2px solid @sub-color;
+    }
+    &-right {
+      height: 100%;
+      right: -2px;
+      top: 0;
+      border-left: 2px solid @sub-color;
+    }
+    &-top {
+      width: 100%;
+      left: 0;
+      top: -2px;
+      border-top: 2px solid @sub-color;
+    }
+    &-bottom {
+      width: 100%;
+      left: 0;
+      bottom: -2px;
+      border-top: 2px solid @sub-color;
+    }
+  }
+
+  &-compact {
+    .control-line {
+      &-left {
+        left: 0;
+        border-left: 2px solid @sub-color;
+      }
+      &-right {
+        right: 0;
+        border-left: 2px solid @sub-color;
+      }
+      &-top {
+        top: 0;
+        border-top: 2px solid @sub-color;
+      }
+      &-bottom {
+        bottom: 0;
+        border-top: 2px solid @sub-color;
+      }
+    }
+  }
+}
 
 /* tailor-area */
 .tailor-area {

+ 2 - 2
src/modules/client/components/ScanAreaSteps.vue

@@ -36,7 +36,7 @@ import CoverArea from "./steps/CoverArea";
 import ImageOrientation from "./steps/ImageOrientation";
 import OriginTailorArea from "./steps/OriginTailorArea";
 import TailorTailorArea from "./steps/TailorTailorArea";
-import { deepCopy } from "../../../plugins/utils";
+import { deepCopy, objTypeOf } from "../../../plugins/utils";
 
 const STEPS_LIST = [
   {
@@ -125,7 +125,7 @@ export default {
 
       Object.entries(setting).map(([key, val]) => {
         this.newSetting[key] =
-          typeof val === "object"
+          objTypeOf(val) === "object"
             ? Object.assign({}, this.newSetting[key], val)
             : val;
       });

+ 281 - 0
src/modules/client/components/areaCropper/AreaCropper.vue

@@ -0,0 +1,281 @@
+<template>
+  <div class="area-cropper">
+    <img v-if="imgUrl" ref="imgDom" :src="imgUrl" @load="imgLoad" />
+    <div
+      class="cropper-areas"
+      :style="areaStyles"
+      v-move-ele.prevent="{
+        moveStart: $event => {
+          boxMouseDown($event);
+        },
+        moveElement: (pos, $event) => {
+          boxMove($event);
+        },
+        moveStop: () => {
+          boxMoveStop();
+        }
+      }"
+    >
+      <area-item
+        v-for="area in areas"
+        :key="area.key"
+        :data="area"
+        :cur-element="curArea"
+        @del-element="removeArea"
+        @act-element="actCurArea"
+        @resize-over="modifyArea"
+      ></area-item>
+      <!-- 拖动选框 -->
+      <div
+        v-if="selectionStyles.w"
+        class="area-selection"
+        :style="{
+          width: selectionStyles.w + 'px',
+          height: selectionStyles.h + 'px',
+          top: selectionStyles.y + 'px',
+          left: selectionStyles.x + 'px'
+        }"
+      ></div>
+    </div>
+  </div>
+</template>
+
+<script>
+import MoveEle from "./move-ele";
+import AreaItem from "./ElementItem.vue";
+import { randomCode } from "@/plugins/utils";
+
+export default {
+  name: "area-cropper",
+  components: { AreaItem },
+  directives: { MoveEle },
+  props: {
+    imgUrl: {
+      type: String,
+      default: ""
+    },
+    areasData: {
+      type: Array,
+      default() {
+        return [];
+      }
+    }
+  },
+  data() {
+    return {
+      areas: [],
+      areaStyles: { top: 0, left: 0, width: "100%", height: "100%" },
+      initArea: { id: null, x: 0, y: 0, w: 0, h: 0, zIndex: 0 },
+      curArea: {},
+      selectionStartPos: { x: 0, y: 0 },
+      selectionStyles: { x: 0, y: 0, w: 0, h: 0 },
+      IS_SELECT_ACTION: false,
+      imgDisplayWidth: 0,
+      imgDisplayHeight: 0
+    };
+  },
+  mounted() {
+    window.addEventListener("resize", this.windowResizeEvent);
+    document.addEventListener("keydown", this.keyEvent);
+  },
+  methods: {
+    getMax(arr) {
+      return Math.max.apply(null, arr);
+    },
+    toFixed(num) {
+      return num.toFixed(2) * 1;
+    },
+    imgLoad() {
+      this.updateAreaStyle();
+      this.transformPicConfig(this.areasData);
+    },
+    updateAreaStyle() {
+      const {
+        clientWidth,
+        clientHeight,
+        offsetLeft,
+        offsetTop
+      } = this.$refs.imgDom;
+
+      this.areaStyles = {
+        left: offsetLeft - clientWidth / 2 + "px",
+        top: offsetTop - clientHeight / 2 + "px",
+        width: clientWidth + "px",
+        height: clientHeight + "px"
+      };
+    },
+    windowResizeEvent() {
+      this.updateAreaStyle();
+
+      const { clientWidth, clientHeight } = this.$refs.imgDom;
+      const hRate = clientHeight / this.imgDisplayHeight;
+      const wRate = clientWidth / this.imgDisplayWidth;
+      this.areas = this.areas.map(area => {
+        return Object.assign({}, area, {
+          key: `key-${randomCode()}`,
+          x: area.x * wRate,
+          y: area.y * hRate,
+          w: area.w * wRate,
+          h: area.h * hRate
+        });
+      });
+      this.imgDisplayWidth = clientWidth;
+      this.imgDisplayHeight = clientHeight;
+    },
+    keyEvent(e) {
+      if (
+        e.code === "Delete" &&
+        !e.ctrlKey &&
+        !e.altKey &&
+        !e.shiftKey &&
+        !e.repeat
+      ) {
+        if (!this.curArea.id) return;
+        e.preventDefault();
+        this.removeArea(this.curArea);
+        return;
+      }
+    },
+    transformPicConfig(areas) {
+      const {
+        naturalWidth,
+        naturalHeight,
+        clientWidth,
+        clientHeight
+      } = this.$refs.imgDom;
+      const hRate = clientHeight / naturalHeight;
+      const wRate = clientWidth / naturalWidth;
+      this.areas = areas.map((area, index) => {
+        let narea = {
+          id: `id-${randomCode()}`,
+          key: `key-${randomCode()}`,
+          zIndex: index + 99
+        };
+
+        narea = {
+          ...narea,
+          x: area.x * wRate,
+          y: area.y * hRate,
+          w: area.w * wRate,
+          h: area.h * hRate
+        };
+
+        return narea;
+      });
+      this.imgDisplayWidth = clientWidth;
+      this.imgDisplayHeight = clientHeight;
+    },
+    addArea(data) {
+      let area = Object.assign({}, this.initArea, data);
+      const maxZIndex = this.areas.length
+        ? this.getMax(this.areas.map(elem => elem.zIndex))
+        : 0;
+      area.id = `id-${randomCode()}`;
+      area.key = `key-${randomCode()}`;
+      area.zIndex = maxZIndex + 1;
+      this.areas.push(area);
+      this.actCurArea(area);
+    },
+    modifyArea(area) {
+      const pos = this.areas.findIndex(elem => elem.id === area.id);
+      this.areas.splice(pos, 1, area);
+      this.actCurArea(area);
+    },
+    removeArea(area) {
+      const pos = this.areas.findIndex(elem => elem.id === area.id);
+      this.areas.splice(pos, 1);
+      if (this.areas.length) this.actCurArea(this.areas.slice(-1)[0]);
+    },
+    actCurArea(area) {
+      this.curArea = area;
+    },
+    getData() {
+      const {
+        naturalWidth,
+        naturalHeight,
+        clientWidth,
+        clientHeight
+      } = this.$refs.imgDom;
+      const hRate = naturalHeight / clientHeight;
+      const wRate = naturalWidth / clientWidth;
+
+      const areas = this.areas.map(item => {
+        return {
+          id: item.id,
+          x: this.toFixed(item.x * wRate),
+          y: this.toFixed(item.y * hRate),
+          w: this.toFixed(item.w * wRate),
+          h: this.toFixed(item.h * hRate)
+        };
+      });
+      return areas;
+    },
+    getOffsetInfo(dom, endParentClass = "cropper-areas") {
+      let parentNode = dom;
+      let parentNodeClass = parentNode.getAttribute("class") || "";
+      let offsetTop = 0,
+        offsetLeft = 0;
+      while (!parentNodeClass.includes(endParentClass)) {
+        if (parentNode.offsetParent) {
+          offsetTop += parentNode.offsetTop;
+          offsetLeft += parentNode.offsetLeft;
+          parentNode = parentNode.offsetParent;
+        } else {
+          offsetTop += parentNode.clientTop;
+          offsetLeft += parentNode.clientLeft;
+          parentNode = parentNode.parentNode;
+        }
+        parentNodeClass = parentNode.getAttribute("class") || "";
+      }
+      return {
+        offsetLeft,
+        offsetTop
+      };
+    },
+    boxMouseDown($event) {
+      const { offsetLeft: x, offsetTop: y } = this.getOffsetInfo($event.target);
+      this.selectionStartPos.x = x + $event.offsetX;
+      this.selectionStartPos.y = y + $event.offsetY;
+    },
+    boxMove($event) {
+      if (!$event.target.className.includes("cropper-areas")) return;
+      const { offsetLeft: x, offsetTop: y } = this.getOffsetInfo($event.target);
+      const selectionEndPos = {
+        x: x + $event.offsetX,
+        y: y + $event.offsetY
+      };
+      const sPos = {
+        x: Math.min(this.selectionStartPos.x, selectionEndPos.x),
+        y: Math.min(this.selectionStartPos.y, selectionEndPos.y)
+      };
+      const ePos = {
+        x: Math.max(this.selectionStartPos.x, selectionEndPos.x),
+        y: Math.max(this.selectionStartPos.y, selectionEndPos.y)
+      };
+
+      this.selectionStyles = {
+        ...sPos,
+        w: ePos.x - sPos.x,
+        h: ePos.y - sPos.y
+      };
+      this.IS_SELECT_ACTION = true;
+    },
+    boxMoveStop() {
+      if (
+        this.IS_SELECT_ACTION &&
+        this.selectionStyles.w > 20 &&
+        this.selectionStyles.h > 20
+      )
+        this.addArea(this.selectionStyles);
+
+      this.selectionStyles = { x: 0, y: 0, w: 0, h: 0 };
+      this.selectionStartPos = { x: 0, y: 0 };
+      this.IS_SELECT_ACTION = false;
+    }
+  },
+  beforeDestroy() {
+    window.removeEventListener("resize", this.windowResizeEvent);
+    document.removeEventListener("keydown", this.keyEvent);
+  }
+};
+</script>

+ 77 - 0
src/modules/client/components/areaCropper/ElementItem.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="element-item">
+    <element-resize
+      v-model="elemData"
+      :class="{ 'element-resize-act': curElement.id === data.id }"
+      :active="active"
+      :element-pk="data.id"
+      :style="{ zIndex: data.zIndex }"
+      isCompact
+      @resize-over="resizeOver"
+      @on-click="activeCurElement"
+    >
+      <div class="element-item-body" :style="styles" :id="data.id"></div>
+      <div class="element-delete" @click="toDelete">
+        <Icon type="md-close-circle" />
+      </div>
+    </element-resize>
+  </div>
+</template>
+
+<script>
+import ElementResize from "./ElementResize";
+import { objAssign } from "@/plugins/utils";
+
+export default {
+  name: "element-item",
+  components: {
+    ElementResize
+  },
+  props: {
+    data: {
+      type: Object
+    },
+    curElement: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      elemData: {
+        x: 0,
+        y: 0,
+        w: 0,
+        h: 0
+      },
+      styles: {},
+      active: ["r", "rb", "b", "lb", "l", "lt", "t", "rt"]
+    };
+  },
+  created() {
+    this.init();
+  },
+  methods: {
+    init() {
+      this.elemData = objAssign(this.elemData, this.data);
+      this.styles = {
+        left: this.data.x + "px",
+        top: this.data.y + "px",
+        width: this.data.w + "px",
+        height: this.data.h + "px"
+      };
+    },
+    resizeOver() {
+      this.$emit("resize-over", Object.assign({}, this.data, this.elemData));
+    },
+    activeCurElement() {
+      this.$emit("act-element", this.data);
+    },
+    toDelete() {
+      this.$emit("del-element", this.data);
+    }
+  }
+};
+</script>

+ 462 - 0
src/modules/client/components/areaCropper/ElementResize.vue

@@ -0,0 +1,462 @@
+<template>
+  <div
+    :class="classes"
+    :style="styles"
+    v-move-ele.prevent.stop="{
+      moveStart,
+      moveElement,
+      moveStop: moveElementOver
+    }"
+  >
+    <slot></slot>
+    <div class="resize-control">
+      <div
+        v-for="(control, index) in controlPoints"
+        :key="index"
+        :class="control.classes"
+        v-move-ele.prevent.stop="{
+          moveElement: control.movePoint,
+          moveStop: control.movePointOver
+        }"
+      ></div>
+      <div class="control-line control-line-left"></div>
+      <div class="control-line control-line-right"></div>
+      <div class="control-line control-line-top"></div>
+      <div class="control-line control-line-bottom"></div>
+    </div>
+  </div>
+</template>
+
+<script>
+import MoveEle from "./move-ele";
+
+export default {
+  name: "element-resize",
+  directives: { MoveEle },
+  props: {
+    value: {
+      type: Object,
+      required: true
+    },
+    active: {
+      type: Array,
+      default() {
+        return ["r", "rb", "b", "lb", "l", "lt", "t", "rt"];
+      }
+    },
+    move: {
+      type: Boolean,
+      default: true
+    },
+    minWidth: {
+      type: Number,
+      default: 60,
+      validator(val) {
+        return val >= 0;
+      }
+    },
+    maxWidth: {
+      type: Number,
+      default: 0,
+      validator(val) {
+        return val >= 0;
+      }
+    },
+    minHeight: {
+      type: Number,
+      default: 40,
+      validator(val) {
+        return val >= 0;
+      }
+    },
+    maxHeight: {
+      type: Number,
+      default: 0,
+      validator(val) {
+        return val >= 0;
+      }
+    },
+    fitParent: {
+      type: Array,
+      default() {
+        return ["w", "h"];
+      }
+    },
+    isCompact: {
+      type: Boolean,
+      default: false
+    },
+    elementPk: {
+      type: String,
+      default: ""
+    }
+  },
+  data() {
+    return {
+      sizePosOrigin: {
+        x: 0,
+        y: 0,
+        w: 0,
+        h: 0
+      },
+      offsetTopOrigin: 0,
+      sizePos: {
+        x: 0,
+        y: 0,
+        w: 0,
+        h: 0
+      },
+      lastSizePos: {},
+      initOver: false,
+      controlPoints: [],
+      positionType: "static",
+      parentNodeSize: {
+        w: 0,
+        h: 0
+      },
+      validSizePos: {},
+      points: {}
+    };
+  },
+  computed: {
+    styles() {
+      return this.initOver
+        ? {
+            left: this.sizePos.x + "px",
+            top: this.sizePos.y + "px",
+            width: this.sizePos.w + "px",
+            height: this.sizePos.h + "px",
+            position: this.positionType
+          }
+        : {};
+    },
+    classes() {
+      return [
+        "element-resize",
+        {
+          "element-resize-move": this.move,
+          "element-resize-init": this.initOver,
+          "element-resize-compact": this.isCompact
+        }
+      ];
+    },
+    fitParentTypeWidth() {
+      return this.fitParent.includes("w");
+    },
+    fitParentTypeHeight() {
+      return this.fitParent.includes("h");
+    }
+  },
+  created() {
+    this.initControlPoints();
+  },
+  mounted() {
+    this.initSize();
+  },
+  methods: {
+    initControlPoints() {
+      const posName = {
+        l: "Left",
+        r: "Right",
+        t: "Top",
+        b: "Bottom"
+      };
+      this.controlPoints = this.active.map(type => {
+        const posFullName = type
+          .split("")
+          .map(item => {
+            return posName[item];
+          })
+          .join("");
+        return {
+          classes: ["control-point", `control-point-${type}`],
+          movePoint: this[`move${posFullName}Point`],
+          movePointOver: this.moveOver
+        };
+      });
+    },
+    initSize() {
+      const resizeDom = this.$el.childNodes[0];
+      this.positionType = window.getComputedStyle(resizeDom).position;
+      this.sizePos = { ...this.value };
+      this.lastSizePos = { ...this.value };
+      this.sizePosOrigin = { ...this.value };
+      if (this.positionType === "relative")
+        this.offsetTopOrigin = this.$el.offsetTop;
+      this.initValidSizePos();
+      this.initOver = true;
+    },
+    initValidSizePos() {
+      const s = this.sizePosOrigin;
+      const points = {
+        rt: {
+          x: s.x + s.w,
+          y: s.y
+        },
+        lt: {
+          x: s.x,
+          y: s.y
+        },
+        lb: {
+          x: s.x,
+          y: s.y + s.h
+        },
+        rb: {
+          x: s.x + s.w,
+          y: s.y + s.h
+        }
+      };
+      const action = {
+        rt: () => {
+          const point = points.rt;
+          return {
+            min: {
+              x: point.x - this.minWidth,
+              y: point.y
+            },
+            max: {
+              x: point.x - this.maxWidth,
+              y: point.y
+            }
+          };
+        },
+        lt: () => {
+          const point = points.lt;
+          return {
+            min: {
+              x: point.x,
+              y: point.y
+            },
+            max: {
+              x: point.x,
+              y: point.y
+            }
+          };
+        },
+        lb: () => {
+          const point = points.lb;
+          return {
+            min: {
+              x: point.x,
+              y: point.y - this.minHeight
+            },
+            max: {
+              x: point.x,
+              y: point.y - this.maxHeight
+            }
+          };
+        },
+        rb: () => {
+          const point = points.rb;
+          return {
+            min: {
+              x: point.x - this.minWidth,
+              y: point.y - this.minHeight
+            },
+            max: {
+              x: point.x - this.maxWidth,
+              y: point.y - this.maxHeight
+            }
+          };
+        }
+      };
+
+      this.validSizePos = {
+        rt: action.rt(),
+        lt: action.lt(),
+        lb: action.lb(),
+        rb: action.rb()
+      };
+    },
+    fetchValidSizePos(sizePos, actionType) {
+      const staticPointConfig = {
+        left: "rt",
+        "left-bottom": "rt",
+        bottom: "lt",
+        "right-bottom": "lt",
+        right: "lb",
+        "right-top": "lb",
+        top: "rb",
+        "left-top": "rb"
+      };
+      const validSizePos = this.validSizePos[staticPointConfig[actionType]];
+
+      if (sizePos.w <= this.minWidth) {
+        sizePos.w = this.minWidth;
+        sizePos.x = validSizePos.min.x;
+      } else if (this.maxWidth !== 0 && sizePos.w >= this.maxWidth) {
+        sizePos.w = this.maxWidth;
+        sizePos.x = validSizePos.max.x;
+      }
+
+      if (sizePos.h <= this.minHeight) {
+        sizePos.h = this.minHeight;
+        sizePos.y = validSizePos.min.y;
+      } else if (this.maxHeight !== 0 && sizePos.h >= this.maxHeight) {
+        sizePos.h = this.maxHeight;
+        sizePos.y = validSizePos.max.y;
+      }
+
+      if (!this.fitParent.length) {
+        this.lastSizePos = { ...sizePos };
+        return sizePos;
+      }
+
+      // 不同的定位方式,计算方式有差异
+      this.parentNodeSize = {
+        w: this.$el.offsetParent.offsetWidth,
+        h: this.$el.offsetParent.offsetHeight
+      };
+
+      if (this.fitParentTypeWidth) {
+        if (sizePos.x <= 0) {
+          sizePos.x = 0;
+          if (actionType.includes("left")) sizePos.w = this.lastSizePos.w;
+        }
+
+        if (sizePos.x + sizePos.w > this.parentNodeSize.w) {
+          sizePos.x = this.lastSizePos.x;
+          sizePos.w = this.parentNodeSize.w - sizePos.x;
+        }
+      }
+
+      if (this.fitParentTypeHeight) {
+        if (this.positionType === "relative") {
+          const elOffsetTop = this.$el.offsetTop;
+          if (this.sizePosOrigin.y - sizePos.y >= this.offsetTopOrigin) {
+            sizePos.h = this.lastSizePos.h;
+            sizePos.y = this.sizePosOrigin.y - this.offsetTopOrigin;
+          }
+          if (elOffsetTop + sizePos.h >= this.parentNodeSize.h) {
+            sizePos.y = this.lastSizePos.y;
+            sizePos.h = this.lastSizePos.h;
+          }
+        } else {
+          if (sizePos.y <= 0) {
+            sizePos.y = 0;
+            if (actionType.includes("top")) sizePos.h = this.lastSizePos.h;
+          }
+          if (sizePos.y + sizePos.h > this.parentNodeSize.h) {
+            sizePos.y = this.lastSizePos.y;
+            sizePos.h = this.parentNodeSize.h - sizePos.y;
+          }
+        }
+      }
+      this.lastSizePos = { ...sizePos };
+      return sizePos;
+    },
+    getLeftSize(left) {
+      return {
+        w: -left + this.sizePosOrigin.w,
+        x: left + this.sizePosOrigin.x
+      };
+    },
+    getRightSize(left) {
+      return {
+        w: left + this.sizePosOrigin.w
+      };
+    },
+    getTopSize(top) {
+      return {
+        h: -top + this.sizePosOrigin.h,
+        y: top + this.sizePosOrigin.y
+      };
+    },
+    getBottomSize(top) {
+      return {
+        h: top + this.sizePosOrigin.h
+      };
+    },
+    moveLeftPoint({ left }) {
+      console.log(this.sizePosOrigin);
+      const sp = { ...this.sizePos, ...this.getLeftSize(left) };
+      this.sizePos = { ...this.fetchValidSizePos(sp, "left") };
+      this.emitChange();
+    },
+    moveRightPoint({ left }) {
+      const sp = { ...this.sizePos, ...this.getRightSize(left) };
+      this.sizePos = { ...this.fetchValidSizePos(sp, "right") };
+      this.emitChange();
+    },
+    moveTopPoint({ top }) {
+      const sp = { ...this.sizePos, ...this.getTopSize(top) };
+      this.sizePos = { ...this.fetchValidSizePos(sp, "top") };
+      this.emitChange();
+    },
+    moveBottomPoint({ top }) {
+      const sp = { ...this.sizePos, ...this.getBottomSize(top) };
+      this.sizePos = { ...this.fetchValidSizePos(sp, "bottom") };
+      this.emitChange();
+    },
+    moveLeftTopPoint({ left, top }) {
+      const sp = {
+        ...this.sizePos,
+        ...this.getLeftSize(left),
+        ...this.getTopSize(top)
+      };
+      this.sizePos = { ...this.fetchValidSizePos(sp, "left-top") };
+      this.emitChange();
+    },
+    moveRightTopPoint({ left, top }) {
+      const sp = {
+        ...this.sizePos,
+        ...this.getRightSize(left),
+        ...this.getTopSize(top)
+      };
+      this.sizePos = { ...this.fetchValidSizePos(sp, "right-top") };
+      this.emitChange();
+    },
+    moveLeftBottomPoint({ left, top }) {
+      const sp = {
+        ...this.sizePos,
+        ...this.getLeftSize(left),
+        ...this.getBottomSize(top)
+      };
+      this.sizePos = { ...this.fetchValidSizePos(sp, "left-bottom") };
+      this.emitChange();
+    },
+    moveRightBottomPoint({ left, top }) {
+      const sp = {
+        ...this.sizePos,
+        ...this.getRightSize(left),
+        ...this.getBottomSize(top)
+      };
+      this.sizePos = { ...this.fetchValidSizePos(sp, "right-bottom") };
+      this.emitChange();
+    },
+    moveOver() {
+      this.sizePosOrigin = { ...this.sizePos };
+      this.lastSizePos = { ...this.sizePos };
+      if (this.positionType === "relative")
+        this.offsetTopOrigin = this.$el.offsetTop < 0 ? 0 : this.$el.offsetTop;
+
+      this.initValidSizePos();
+      this.$emit("resize-over", this.sizePos);
+    },
+    moveStart() {
+      this.$emit("on-click");
+    },
+    moveElement({ left, top }) {
+      if (!this.move) return;
+
+      const sp = {
+        ...this.sizePos,
+        ...{
+          x: left + this.sizePosOrigin.x,
+          y: top + this.sizePosOrigin.y
+        }
+      };
+      this.sizePos = { ...this.fetchValidSizePos(sp, "move") };
+      this.emitChange();
+    },
+    moveElementOver() {
+      if (!this.move) return;
+      this.moveOver();
+    },
+    emitChange() {
+      this.$emit("input", this.sizePos);
+      this.$emit("change", this.sizePos);
+    }
+  }
+};
+</script>

+ 49 - 0
src/modules/client/components/areaCropper/move-ele.js

@@ -0,0 +1,49 @@
+module.exports = {
+  inserted(el, { value, modifiers }) {
+    let [_x, _y] = [0, 0];
+    // 只允许鼠标左键触发
+    let moveHandle = function(e) {
+      if (e.button !== 0) return;
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+
+      let left = e.pageX - _x;
+      let top = e.pageY - _y;
+
+      value.moveElement({ left, top }, e);
+    };
+
+    let upHandle = function(e) {
+      if (e.button !== 0) return;
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+      value.moveStop && value.moveStop(e);
+      document.removeEventListener("mousemove", moveHandle);
+      document.removeEventListener("mouseup", upHandle);
+    };
+
+    el.addEventListener("mousedown", function(e) {
+      if (e.button !== 0) return;
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+      _x = e.pageX;
+      _y = e.pageY;
+      value.moveStart && value.moveStart(e);
+
+      document.addEventListener("mousemove", moveHandle);
+      document.addEventListener("mouseup", upHandle);
+    });
+  }
+};

+ 53 - 74
src/modules/client/components/steps/CoverArea.vue

@@ -1,74 +1,53 @@
-<template>
-  <div class="cover-area">
-    <div class="code-area-cont">
-      <img :src="imageUrl" ref="editImage" />
-    </div>
-    <div class="code-area-preview">
-      <div class="code-area-spin">
-        <div class="code-area-spin-img" ref="CoverAreaSpinImg"></div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import Cropper from "cropperjs";
-
-export default {
-  name: "cover-area",
-  props: {
-    imageUrl: {
-      type: String,
-      require: true
-    },
-    curSetting: {
-      type: Object,
-      default() {
-        return {};
-      }
-    }
-  },
-  data() {
-    return {
-      cropper: ""
-    };
-  },
-  mounted() {
-    this.initCropper();
-  },
-  methods: {
-    initCropper() {
-      const _this = this;
-      const defCoverArea = (this.curSetting && this.curSetting.coverArea) || {};
-
-      this.cropper = new Cropper(this.$refs.editImage, {
-        viewMode: 1,
-        checkCrossOrigin: false,
-        zoomable: false,
-        minCropBoxWidth: 10,
-        minCropBoxHeight: 10,
-        preview: _this.$refs.CoverAreaSpinImg,
-        ready() {
-          _this.cropper.setData(defCoverArea);
-          _this.$emit("on-ready");
-        }
-      });
-    },
-    checkValid() {
-      const coverArea = {
-        ...this.cropper.getData()
-      };
-      this.$emit("on-next", { coverArea });
-    },
-    pass() {
-      this.$emit("on-next", { coverArea: {} });
-    }
-  },
-  beforeDestroy() {
-    if (this.cropper) {
-      this.cropper.destroy();
-      this.cropper = false;
-    }
-  }
-};
-</script>
+<template>
+  <div class="cover-area">
+    <area-cropper
+      ref="AreaCropper"
+      :img-url="imageUrl"
+      :areas-data="defCoverArea"
+    ></area-cropper>
+  </div>
+</template>
+
+<script>
+import AreaCropper from "../areaCropper/AreaCropper";
+
+export default {
+  name: "cover-area",
+  components: { AreaCropper },
+  props: {
+    imageUrl: {
+      type: String,
+      require: true
+    },
+    curSetting: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      defCoverArea: []
+    };
+  },
+  created() {
+    this.defCoverArea = (this.curSetting && this.curSetting.coverArea) || [];
+    console.log(this.defCoverArea);
+    this.$emit("on-ready");
+  },
+  methods: {
+    checkValid() {
+      const coverArea = this.$refs.AreaCropper.getData();
+      if (!coverArea.length) {
+        this.$Message.error("请设置信息覆盖区域设置");
+        return;
+      }
+      this.$emit("on-next", { coverArea });
+    },
+    pass() {
+      this.$emit("on-next", { coverArea: {} });
+    }
+  }
+};
+</script>

+ 74 - 0
src/modules/client/components/steps/CoverAreaSimpler.vue

@@ -0,0 +1,74 @@
+<template>
+  <div class="cover-area">
+    <div class="code-area-cont">
+      <img :src="imageUrl" ref="editImage" />
+    </div>
+    <div class="code-area-preview">
+      <div class="code-area-spin">
+        <div class="code-area-spin-img" ref="CoverAreaSpinImg"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import Cropper from "cropperjs";
+
+export default {
+  name: "cover-area",
+  props: {
+    imageUrl: {
+      type: String,
+      require: true
+    },
+    curSetting: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      cropper: ""
+    };
+  },
+  mounted() {
+    this.initCropper();
+  },
+  methods: {
+    initCropper() {
+      const _this = this;
+      const defCoverArea = (this.curSetting && this.curSetting.coverArea) || {};
+
+      this.cropper = new Cropper(this.$refs.editImage, {
+        viewMode: 1,
+        checkCrossOrigin: false,
+        zoomable: false,
+        minCropBoxWidth: 10,
+        minCropBoxHeight: 10,
+        preview: _this.$refs.CoverAreaSpinImg,
+        ready() {
+          _this.cropper.setData(defCoverArea);
+          _this.$emit("on-ready");
+        }
+      });
+    },
+    checkValid() {
+      const coverArea = {
+        ...this.cropper.getData()
+      };
+      this.$emit("on-next", { coverArea });
+    },
+    pass() {
+      this.$emit("on-next", { coverArea: {} });
+    }
+  },
+  beforeDestroy() {
+    if (this.cropper) {
+      this.cropper.destroy();
+      this.cropper = false;
+    }
+  }
+};
+</script>

+ 5 - 8
src/modules/cropper-task/taskUtils.js

@@ -101,14 +101,11 @@ function saveSliceImage(paperInfo, collectConfig) {
   //   );
 
   // 保密覆盖区
-  imgObj
-    .fill("#5f5f5f")
-    .drawRectangle(
-      coverArea.x,
-      coverArea.y,
-      coverArea.x + coverArea.width,
-      coverArea.y + coverArea.height
-    );
+  coverArea.forEach(area => {
+    imgObj
+      .fill("#5f5f5f")
+      .drawRectangle(area.x, area.y, area.x + area.w, area.y + area.h);
+  });
 
   // 边缘裁切
   imgObj.crop(

+ 5 - 8
src/plugins/imageOcr.js

@@ -147,14 +147,11 @@ function saveSliceImage(imgPath, paperInfo, collectConfig) {
   //   );
 
   // 保密覆盖区
-  imgObj
-    .fill("#5f5f5f")
-    .drawRectangle(
-      coverArea.x,
-      coverArea.y,
-      coverArea.x + coverArea.width,
-      coverArea.y + coverArea.height
-    );
+  coverArea.forEach(area => {
+    imgObj
+      .fill("#5f5f5f")
+      .drawRectangle(area.x, area.y, area.x + area.w, area.y + area.h);
+  });
 
   // 边缘裁切
   imgObj.crop(