瀏覽代碼

feat: 切片图

zhangjie 8 月之前
父節點
當前提交
c61cf18d8a

+ 80 - 10
src/render/styles/pages.less

@@ -882,10 +882,10 @@
 .recog-edit-dialog {
   // left: calc(277px + (100% - 278px - 382px) / 2);
   top: 60px !important;
-  left: calc(50% - 52px) !important;
-  transform: translateX(-50%);
-  width: 718px !important;
-  height: 216px !important;
+  left: 302px !important;
+  right: 406px !important;
+  bottom: auto !important;
+  min-height: 216px;
   overflow: hidden !important;
   border-radius: 12px;
   box-shadow: 0px 10px 10px 0px rgba(54, 61, 89, 0.2);
@@ -894,27 +894,55 @@
     width: auto !important;
     transform-origin: 0 !important;
     top: 0 !important;
+    padding: 0 !important;
   }
   .ant-modal-body {
     padding: 0;
   }
 }
 .recog-edit {
-  width: 718px;
-  height: 216px;
+  width: 100%;
   background: #f2f3f5;
   box-shadow: 0px 10px 10px 0px rgba(54, 61, 89, 0.2);
   border-radius: 12px;
   border: 1px solid @background-color;
   padding: 16px;
+  .recog-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: stretch;
+    margin: 0 -4px;
+    min-height: 88px;
+
+    &:not(:first-child) {
+      margin-top: 8px;
+    }
+  }
+  .recog-col {
+    padding: 0 4px;
+    width: 100%;
+
+    &.is-static {
+      flex-grow: 0;
+      flex-shrink: 0;
+    }
+
+    &.is-grow {
+      flex-grow: 2;
+    }
+    &.is-col1 {
+      width: 122px;
+    }
+  }
 
   .modal-box {
-    height: 88px;
+    height: 100%;
     background: #ffffff;
     border-radius: 6px;
     border: 1px solid @border-color1;
-    padding: 16px;
+    padding: 10px 16px;
     color: @text-color1;
+    overflow: hidden;
 
     .box-title {
       height: 22px;
@@ -923,6 +951,7 @@
       color: @text-color3;
       line-height: 22px;
       margin-bottom: 6px;
+      margin-top: 6px;
     }
     .box-cont {
       height: 28px;
@@ -930,17 +959,58 @@
       font-size: 20px;
       line-height: 28px;
     }
+
+    &.is-btn {
+      cursor: pointer;
+
+      &:hover {
+        background: #e8f3ff;
+        color: @brand-color;
+      }
+    }
   }
 
   .modal-origin {
     background-color: @background-color;
+    padding: 10px;
+    height: 88px;
+    position: relative;
+
+    &-body {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      transform-origin: 0 0;
+
+      .select-option {
+        position: absolute;
+        top: 0;
+        border: 2px solid @brand-color;
+        cursor: pointer;
+
+        &:hover {
+          opacity: 0.5;
+        }
+
+        &.is-active {
+          background-color: rgba(22, 93, 255, 0.3);
+          opacity: 1;
+        }
+      }
+    }
   }
 
   .modal-options {
     line-height: 54px;
-    text-align: center;
+    padding: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-wrap: wrap;
+
     .ant-btn {
-      margin: 0 8px;
+      margin: 4px 8px;
       padding-left: 8px;
       padding-right: 8px;
       text-align: center;

+ 12 - 7
src/render/utils/recog/recog.ts

@@ -115,25 +115,30 @@ export function parseDetailSize(
   };
 
   if (!data) return result;
+  result.fillSize = {
+    w: data.fill_size[0],
+    h: data.fill_size[1],
+  };
+  const offsetX = data.fill_size[0] / 2;
+  const offsetY = data.fill_size[1] / 2;
 
   result.fillPosition = data.fill_position.map((item) => {
     const size = item.split(",");
+    const x = size[0] ? Number(size[0]) : 0;
+    const y = size[1] ? Number(size[1]) : 0;
     return {
-      x: size[0] ? Number(size[0]) : 0,
-      y: size[1] ? Number(size[1]) : 0,
+      x: x - offsetX,
+      y: y - offsetY,
     };
   });
-  result.fillSize = {
-    w: data.fill_size[0],
-    h: data.fill_size[1],
-  };
+
   const xs = result.fillPosition.map((item) => item.x);
   const maxX = maxNum(xs);
   const minX = minNum(xs);
   result.fillArea = {
     x: minX,
     y: result.fillPosition[0].y,
-    w: maxX - minX,
+    w: maxX - minX + result.fillSize.w,
     h: result.fillSize.h,
   };
 

+ 45 - 0
src/render/utils/tool.ts

@@ -362,3 +362,48 @@ export function getSliceFileUrl(
     };
   });
 }
+
+interface AreaSize {
+  width: number;
+  height: number;
+}
+export function getBoxImageSize({
+  box,
+  img,
+  rotate,
+}: {
+  box: AreaSize;
+  img: AreaSize;
+  rotate: number;
+}) {
+  const imageSize = {
+    width: 0,
+    height: 0,
+    top: 0,
+    left: 0,
+  };
+  const isHorizontal = !!(rotate % 180);
+
+  const rateWin = isHorizontal
+    ? box.height / box.width
+    : box.width / box.height;
+  const hbox = isHorizontal
+    ? {
+        width: box.height,
+        height: box.width,
+      }
+    : box;
+
+  const rateImg = img.width / img.height;
+
+  if (rateImg <= rateWin) {
+    imageSize.height = Math.min(hbox.height, img.height);
+    imageSize.width = Math.floor((imageSize.height * img.width) / img.height);
+  } else {
+    imageSize.width = Math.min(hbox.width, img.width);
+    imageSize.height = Math.floor((imageSize.width * img.height) / img.width);
+  }
+  imageSize.left = (box.width - imageSize.width) / 2;
+  imageSize.top = (box.height - imageSize.height) / 2;
+  return imageSize;
+}

+ 4 - 1
src/render/views/DataCheck/CheckAction.vue

@@ -343,7 +343,10 @@ watch(
   () => dataCheckStore.curPageIndex,
   (val, oldval) => {
     if (val !== oldval) {
-      if (!dataCheckStore.curPage || !dataCheckStore.curPage.question) return;
+      if (!dataCheckStore.curPage || !dataCheckStore.curPage.question) {
+        questions.value = [];
+        return;
+      }
       questions.value = [...dataCheckStore.curPage.question.result];
     }
   }

+ 77 - 20
src/render/views/DataCheck/ScanImage/RecogEditDialog.vue

@@ -1,7 +1,7 @@
 <template>
   <a-modal
     v-model:open="visible"
-    :width="718"
+    width="100%"
     :footer="false"
     :closable="false"
     :mask="false"
@@ -9,33 +9,51 @@
     wrapClassName="recog-edit-dialog"
   >
     <div class="recog-edit">
-      <a-row align="top" :gutter="8" class="m-b-8px">
-        <a-col :span="4">
+      <div class="recog-row">
+        <div class="recog-col is-static is-col1">
           <div class="modal-box">
             <p class="box-title">{{ recogTitle }}</p>
             <p class="box-cont">{{ recogTitleDesc }}</p>
           </div>
-        </a-col>
-        <a-col :span="16">
+        </div>
+        <div class="recog-col is-grow">
           <div class="modal-box modal-origin">
-            <img v-if="recogData.areaSrc" :src="recogData.areaSrc" alt="截图" />
+            <div class="modal-origin-body" :style="areaImgStyle">
+              <img
+                v-if="recogData.areaImg"
+                ref="areaImgRef"
+                :src="recogData.areaImg"
+                alt="截图"
+                @load="areaImgLoad"
+              />
+              <div
+                v-for="(option, index) in recogData.options.slice(1)"
+                :key="option"
+                :class="[
+                  'select-option',
+                  { 'is-active': selectResult.includes(option) },
+                ]"
+                :style="getOptionStyle(index)"
+                @click="selectOption(option)"
+              ></div>
+            </div>
           </div>
-        </a-col>
-        <a-col :span="4">
-          <div class="modal-box" @click="close">
+        </div>
+        <div class="recog-col is-static is-col1">
+          <div class="modal-box is-btn" @click="close">
             <p class="box-title">Esc键</p>
             <p class="box-cont">关闭</p>
           </div>
-        </a-col>
-      </a-row>
-      <a-row align="top" :gutter="8">
-        <a-col :span="4">
+        </div>
+      </div>
+      <div class="recog-row">
+        <div class="recog-col is-static is-col1">
           <div class="modal-box">
             <p class="box-title">识别结果</p>
             <p class="box-cont">{{ recogResult }}</p>
           </div>
-        </a-col>
-        <a-col :span="16">
+        </div>
+        <div class="recog-col is-grow">
           <div class="modal-box modal-options">
             <a-button
               v-for="option in recogData.options"
@@ -45,14 +63,14 @@
               >{{ option }}</a-button
             >
           </div>
-        </a-col>
-        <a-col :span="4">
-          <div class="modal-box" @click="onConfirm">
+        </div>
+        <div class="recog-col is-static is-col1">
+          <div class="modal-box is-btn" @click="onConfirm">
             <p class="box-title">Enter键</p>
             <p class="box-cont">保存</p>
           </div>
-        </a-col>
-      </a-row>
+        </div>
+      </div>
     </div>
   </a-modal>
 </template>
@@ -62,6 +80,8 @@ import { ref, computed, watch } from "vue";
 import { message } from "ant-design-vue";
 import useModal from "@/hooks/useModal";
 import { RecogBlock } from "@/utils/recog/recog";
+import { getBoxImageSize } from "@/utils/tool";
+import { transform } from "lodash-es";
 
 defineOptions({
   name: "RecogEditDialog",
@@ -103,6 +123,43 @@ const recogResult = computed(() => {
   return "";
 });
 
+function getOptionStyle(index: number): Record<string, any> {
+  const offTop = props.recogData.fillSize.h;
+  const offLeft = props.recogData.fillSize.w;
+  const optionSize = props.recogData.optionSizes[index];
+  return {
+    width: `${optionSize.w}px`,
+    height: `${optionSize.h}px`,
+    left: `${optionSize.x + offLeft}px`,
+    top: `${offTop}px`,
+  };
+}
+
+const areaImgRef = ref();
+const areaImgStyle = ref({});
+function areaImgLoad() {
+  const areaImgDom = areaImgRef.value as HTMLImageElement;
+  const boxDom = areaImgDom.parentNode?.parentNode as HTMLDivElement;
+
+  const imgSize = getBoxImageSize({
+    box: {
+      width: boxDom.offsetWidth - 22,
+      height: boxDom.offsetHeight - 22,
+    },
+    img: {
+      width: areaImgDom.naturalWidth,
+      height: areaImgDom.naturalHeight,
+    },
+    rotate: 0,
+  });
+  const rate = imgSize.width / areaImgDom.naturalWidth;
+  areaImgStyle.value = {
+    width: `${areaImgDom.naturalWidth}px`,
+    height: `${areaImgDom.naturalHeight}px`,
+    transform: `scale(${rate}) translate(-50%, -50%)`,
+  };
+}
+
 function selectOption(option: string) {
   if (!props.recogData) return;
 

二進制
src/render/views/DataCheck/ScanImage/data/paper.jpg


+ 21 - 52
src/render/views/DataCheck/ScanImage/index.vue

@@ -78,7 +78,12 @@ import {
   PictureFilled,
 } from "@ant-design/icons-vue";
 import { computed, nextTick, ref, unref } from "vue";
-import { objAssign, getFileUrl } from "@/utils/tool";
+import {
+  objAssign,
+  getFileUrl,
+  getSliceFileUrl,
+  getBoxImageSize,
+} from "@/utils/tool";
 import { vEleMoveDirective } from "@/directives/eleMove";
 import {
   parseRecogData,
@@ -128,7 +133,7 @@ const imageStyle = computed(() => {
     height: `${imageSize.value.height}px`,
     top: `${imageSize.value.top}px`,
     left: `${imageSize.value.left}px`,
-    transform: `scale(${imageSize.value.scale}, ${imageSize.value.scale})`,
+    transform: `scale(${imageSize.value.scale})`,
   };
 });
 
@@ -136,8 +141,8 @@ function initImageSize() {
   const imgDom = imgRef.value as HTMLImageElement;
   const elDom = elRef.value as HTMLDivElement;
 
-  const imgSize = getImageSizePos({
-    win: {
+  const imgSize = getBoxImageSize({
+    box: {
       width: elDom.clientWidth,
       height: elDom.clientHeight,
     },
@@ -154,51 +159,6 @@ function initImageSize() {
   });
 }
 
-interface AreaSize {
-  width: number;
-  height: number;
-}
-function getImageSizePos({
-  win,
-  img,
-  rotate,
-}: {
-  win: AreaSize;
-  img: AreaSize;
-  rotate: number;
-}) {
-  const imageSize = {
-    width: 0,
-    height: 0,
-    top: 0,
-    left: 0,
-  };
-  const isHorizontal = !!(rotate % 180);
-
-  const rateWin = isHorizontal
-    ? win.height / win.width
-    : win.width / win.height;
-  const hwin = isHorizontal
-    ? {
-        width: win.height,
-        height: win.width,
-      }
-    : win;
-
-  const rateImg = img.width / img.height;
-
-  if (rateImg <= rateWin) {
-    imageSize.height = Math.min(hwin.height, img.height);
-    imageSize.width = Math.floor((imageSize.height * img.width) / img.height);
-  } else {
-    imageSize.width = Math.min(hwin.width, img.width);
-    imageSize.height = Math.floor((imageSize.width * img.height) / img.width);
-  }
-  imageSize.left = (win.width - imageSize.width) / 2;
-  imageSize.top = (win.height - imageSize.height) / 2;
-  return imageSize;
-}
-
 function getNumberResult(
   result: Array<string | boolean>,
   sources: Array<string | boolean>
@@ -237,7 +197,6 @@ function updateRecogList() {
       recogList.value.push(recogItem);
     });
   });
-  // TODO: 解析其他数据
 
   parseRecogBlocks();
 }
@@ -295,9 +254,19 @@ function parseRecogBlocks() {
 
 // area click
 const recogEditDialogRef = ref();
-function onAreaClick(data: RecogBlock) {
+async function onAreaClick(data: RecogBlock) {
   curRecogBlock.value = data;
-  // TODO:build area src img
+  // 基于 fillArea 四周扩展一个 fillSize 尺寸
+  const area = {
+    x: data.fillArea.x - data.fillSize.w,
+    y: data.fillArea.y - data.fillSize.h,
+    w: data.fillArea.w + data.fillSize.w * 2,
+    h: data.fillArea.h + data.fillSize.h * 2,
+  };
+  curRecogBlock.value.areaImg = await getSliceFileUrl(
+    curPage.value?.sheetUri,
+    area
+  );
   nextTick(() => {
     recogEditDialogRef.value?.open();
   });

+ 3 - 49
src/render/views/DataCheck/SliceImage/CutImageDialog.vue

@@ -38,7 +38,7 @@
 import { computed, ref, watch } from "vue";
 import { SaveOutlined, CloseOutlined } from "@ant-design/icons-vue";
 import useModal from "@/hooks/useModal";
-import { getFileUrl, objAssign } from "@/utils/tool";
+import { getFileUrl, objAssign, getBoxImageSize } from "@/utils/tool";
 
 import ElementResize from "@/components/ElementResize/index.vue";
 
@@ -102,8 +102,8 @@ function initImageSize() {
   const imgDom = imgRef.value as HTMLImageElement;
   const elDom = imgContainRef.value as HTMLDivElement;
 
-  const imgSize = getImageSizePos({
-    win: {
+  const imgSize = getBoxImageSize({
+    box: {
       width: elDom.clientWidth,
       height: elDom.clientHeight,
     },
@@ -125,51 +125,6 @@ function initImageSize() {
   };
 }
 
-interface AreaSize {
-  width: number;
-  height: number;
-}
-function getImageSizePos({
-  win,
-  img,
-  rotate,
-}: {
-  win: AreaSize;
-  img: AreaSize;
-  rotate: number;
-}) {
-  const imageSize = {
-    width: 0,
-    height: 0,
-    top: 0,
-    left: 0,
-  };
-  const isHorizontal = !!(rotate % 180);
-
-  const rateWin = isHorizontal
-    ? win.height / win.width
-    : win.width / win.height;
-  const hwin = isHorizontal
-    ? {
-        width: win.height,
-        height: win.width,
-      }
-    : win;
-
-  const rateImg = img.width / img.height;
-
-  if (rateImg <= rateWin) {
-    imageSize.height = Math.min(hwin.height, img.height);
-    imageSize.width = Math.floor((imageSize.height * img.width) / img.height);
-  } else {
-    imageSize.width = Math.min(hwin.width, img.width);
-    imageSize.height = Math.floor((imageSize.width * img.height) / img.width);
-  }
-  imageSize.left = (win.width - imageSize.width) / 2;
-  imageSize.top = (win.height - imageSize.height) / 2;
-  return imageSize;
-}
-
 async function confirm() {
   const imgDom = imgRef.value as HTMLImageElement;
   const rate = imageSize.value.width / imgDom.naturalWidth;
@@ -185,7 +140,6 @@ async function confirm() {
     console.error(e);
   });
   if (!file) return;
-  console.log(file);
   emit("confirm", file);
   close();
 }