소스 검색

特殊标记划线和划圆

zhangjie 1 년 전
부모
커밋
cc8590e507

+ 124 - 0
src/features/mark/CommonMarkBody.vue

@@ -37,6 +37,26 @@
               :dy="item.dy"
               :dy="item.dy"
               @deleteSpecialtag="(tag) => deleteSpecialtag(item, tag)"
               @deleteSpecialtag="(tag) => deleteSpecialtag(item, tag)"
             />
             />
+            <div
+              v-if="isCustomSpecialTag"
+              class="image-canvas"
+              v-ele-move-directive.stop.prevent="{
+                moveStart: (event) => specialMouseStart(event, item),
+                moveElement: specialMouseMove,
+                moveStop: specialMouseStop,
+              }"
+            >
+              <template v-if="curSliceImagesWithTrackItem?.url === item.url">
+                <div
+                  v-if="store.currentSpecialTagType === 'LINE'"
+                  :style="specialLenStyle"
+                ></div>
+                <div
+                  v-if="store.currentSpecialTagType === 'CIRCLE'"
+                  :style="specialCircleStyle"
+                ></div>
+              </template>
+            </div>
           </div>
           </div>
           <hr class="image-seperator" />
           <hr class="image-seperator" />
         </template>
         </template>
@@ -66,6 +86,8 @@ import "viewerjs/dist/viewer.css";
 import Viewer from "viewerjs";
 import Viewer from "viewerjs";
 import { message } from "ant-design-vue";
 import { message } from "ant-design-vue";
 import EventBus from "@/plugins/eventBus";
 import EventBus from "@/plugins/eventBus";
+import { vEleMoveDirective } from "./use/eleMove";
+
 type MakeTrack = (
 type MakeTrack = (
   event: MouseEvent,
   event: MouseEvent,
   item: SliceImage,
   item: SliceImage,
@@ -629,6 +651,100 @@ const innerMakeTrack = (event: MouseEvent, item: SliceImage) => {
 };
 };
 //#endregion : 评分
 //#endregion : 评分
 
 
+//#region : 特殊标记:画线、框
+const isCustomSpecialTag = $computed(() => {
+  return ["CIRCLE", "LINE"].includes(store.currentSpecialTagType);
+});
+
+let specialPoint = $ref({ x: 0, y: 0, ex: 0, ey: 0 });
+let curImageTarget: HTMLElement = null;
+let curSliceImagesWithTrackItem: SliceImage = $ref(null);
+
+const specialLenStyle = $computed(() => {
+  const width =
+    specialPoint.ex > specialPoint.x ? specialPoint.ex - specialPoint.x : 0;
+  return {
+    top: specialPoint.y + "px",
+    left: specialPoint.x + "px",
+    width: width + "px",
+    position: "absolute",
+    borderTop: "1px solid red",
+    zIndex: 9,
+  };
+});
+const specialCircleStyle = $computed(() => {
+  const width =
+    specialPoint.ex > specialPoint.x ? specialPoint.ex - specialPoint.x : 0;
+  const height =
+    specialPoint.ey > specialPoint.y ? specialPoint.ey - specialPoint.y : 0;
+  return {
+    top: specialPoint.y + "px",
+    left: specialPoint.x + "px",
+    width: width + "px",
+    height: height + "px",
+    position: "absolute",
+    border: "1px solid red",
+    borderRadius: "50%",
+    zIndex: 9,
+  };
+});
+
+function specialMouseStart(e: MouseEvent, item: SliceImage) {
+  curImageTarget = e.target.parentElement.childNodes[0];
+  curSliceImagesWithTrackItem = item;
+  specialPoint.x = e.offsetX;
+  specialPoint.y = e.offsetY;
+}
+function specialMouseMove({ left, top }) {
+  specialPoint.ex = left + specialPoint.x;
+  specialPoint.ey = top + specialPoint.y;
+}
+function specialMouseStop(e: MouseEvent) {
+  const track: SpecialTag = {
+    tagName: "",
+    tagType: store.currentSpecialTagType,
+    offsetIndex: curSliceImagesWithTrackItem.indexInSliceUrls,
+    offsetX:
+      specialPoint.x * (curImageTarget.naturalWidth / curImageTarget.width) +
+      curSliceImagesWithTrackItem.dx,
+    offsetY:
+      specialPoint.y * (curImageTarget.naturalHeight / curImageTarget.height) +
+      curSliceImagesWithTrackItem.dy,
+    positionX: -1,
+    positionY: -1,
+  };
+  track.positionX =
+    (specialPoint.x - curSliceImagesWithTrackItem.dx) / maxSliceWidth;
+  track.positionY =
+    (specialPoint.y -
+      curSliceImagesWithTrackItem.dy +
+      curSliceImagesWithTrackItem.accumTopHeight) /
+    theFinalHeight;
+
+  if (store.currentSpecialTagType === "LINE") {
+    track.tagName = JSON.stringify({
+      len:
+        (specialPoint.ex - specialPoint.x) *
+        (curImageTarget.naturalWidth / curImageTarget.width),
+    });
+  }
+  if (store.currentSpecialTagType === "CIRCLE") {
+    track.tagName = JSON.stringify({
+      width:
+        (specialPoint.ex - specialPoint.x) *
+        (curImageTarget.naturalWidth / curImageTarget.width),
+      height:
+        (specialPoint.ey - specialPoint.y) *
+        (curImageTarget.naturalHeight / curImageTarget.height),
+    });
+  }
+
+  store.currentTaskEnsured.markResult.specialTagList.push(track);
+  curSliceImagesWithTrackItem.tagList.push(track);
+  specialPoint = { x: 0, y: 0, ex: 0, ey: 0 };
+}
+//#endregion
+
 //#region : 显示大图,供查看和翻转
 //#region : 显示大图,供查看和翻转
 const showBigImage = (event: MouseEvent) => {
 const showBigImage = (event: MouseEvent) => {
   event.preventDefault();
   event.preventDefault();
@@ -758,6 +874,14 @@ function scrollToFirstScore() {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
+.image-canvas {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 9;
+}
 .double-triangle {
 .double-triangle {
   background-color: #ef7c78;
   background-color: #ef7c78;
   width: 30px;
   width: 30px;

+ 16 - 3
src/features/mark/MarkBody.vue

@@ -7,9 +7,14 @@
   <div class="cursor">
   <div class="cursor">
     <div class="cursor-border">
     <div class="cursor-border">
       <span v-if="store.currentSpecialTagType === 'TEXT'" class="text">文</span>
       <span v-if="store.currentSpecialTagType === 'TEXT'" class="text">文</span>
-      <!-- <span v-else-if="store.currentSpecialTagType === 'RIGHT'" class="text">
-        <CheckOutlined
-      /></span> -->
+      <span
+        v-else-if="
+          store.currentSpecialTagType === 'LINE' ||
+          store.currentSpecialTagType === 'CIRCLE'
+        "
+        class="point"
+      >
+      </span>
       <span v-else-if="store.currentSpecialTag" class="text">
       <span v-else-if="store.currentSpecialTag" class="text">
         {{ store.currentSpecialTag }}
         {{ store.currentSpecialTag }}
       </span>
       </span>
@@ -275,6 +280,14 @@ onUnmounted(() => {
   opacity: 0;
   opacity: 0;
   transition: opacity 80ms cubic-bezier(0.23, 1, 0.32, 1);
   transition: opacity 80ms cubic-bezier(0.23, 1, 0.32, 1);
 }
 }
+.cursor .point {
+  display: inline-block;
+  vertical-align: middle;
+  width: 4px;
+  height: 4px;
+  border-radius: 50%;
+  background: red;
+}
 
 
 .cursor.cursor--off-screen {
 .cursor.cursor--off-screen {
   opacity: 0;
   opacity: 0;

+ 46 - 0
src/features/mark/MarkDrawTrack.vue

@@ -43,6 +43,16 @@
         @blur="specialTagBlur(tag)"
         @blur="specialTagBlur(tag)"
       />
       />
     </div>
     </div>
+    <div
+      v-else-if="tag.tagType === 'LINE'"
+      class="special-line"
+      :style="computeSpecialLineStyle(tag)"
+    ></div>
+    <div
+      v-else-if="tag.tagType === 'CIRCLE'"
+      class="special-line"
+      :style="computeSpecialCircleStyle(tag)"
+    ></div>
     <div
     <div
       v-else
       v-else
       :class="['score-container', 'no-event']"
       :class="['score-container', 'no-event']"
@@ -79,6 +89,42 @@ const emit = defineEmits(["delete-specialtag"]);
 
 
 const { trackList } = toRefs(props);
 const { trackList } = toRefs(props);
 
 
+const computeSpecialLineStyle = (track: SpecialTag) => {
+  // {"tagName":"{\"len\":241.9842519685039}","tagType":"LINE","offsetIndex":2,"offsetX":324.8193228048039,"offsetY":759.8560783391572,"positionX":0.06189180773871382,"positionY":-0.01054037309709878}
+  const tagProp = JSON.parse(track.tagName);
+
+  const topInsideSlice = track.offsetY - props.dy;
+  const leftInsideSlice = track.offsetX - props.dx;
+  const topInsideSliceRatio = topInsideSlice / props.sliceImageHeight;
+  const leftInsideSliceRatio = leftInsideSlice / props.sliceImageWidth;
+  return {
+    top: topInsideSliceRatio * 100 + "%",
+    left: leftInsideSliceRatio * 100 + "%",
+    width: (100 * tagProp.len) / props.sliceImageWidth + "%",
+    position: "absolute",
+    borderTop: "1px solid red",
+    zIndex: 9,
+  };
+};
+const computeSpecialCircleStyle = (track: SpecialTag) => {
+  // {"tagName":"{\"len\":241.9842519685039}","tagType":"LINE","offsetIndex":2,"offsetX":324.8193228048039,"offsetY":759.8560783391572,"positionX":0.06189180773871382,"positionY":-0.01054037309709878}
+  const tagProp = JSON.parse(track.tagName);
+
+  const topInsideSlice = track.offsetY - props.dy;
+  const leftInsideSlice = track.offsetX - props.dx;
+  const topInsideSliceRatio = topInsideSlice / props.sliceImageHeight;
+  const leftInsideSliceRatio = leftInsideSlice / props.sliceImageWidth;
+  return {
+    top: topInsideSliceRatio * 100 + "%",
+    left: leftInsideSliceRatio * 100 + "%",
+    width: (100 * tagProp.width) / props.sliceImageWidth + "%",
+    height: (100 * tagProp.height) / props.sliceImageHeight + "%",
+    position: "absolute",
+    border: "1px solid red",
+    borderRadius: "50%",
+    zIndex: 9,
+  };
+};
 const computeTopAndLeft = (track: Track | SpecialTag) => {
 const computeTopAndLeft = (track: Track | SpecialTag) => {
   const topInsideSlice = track.offsetY - props.dy;
   const topInsideSlice = track.offsetY - props.dy;
   const leftInsideSlice = track.offsetX - props.dx;
   const leftInsideSlice = track.offsetX - props.dx;

+ 2 - 2
src/features/mark/MarkTool.vue

@@ -95,7 +95,7 @@
         <div
         <div
           :class="[
           :class="[
             'tag-item',
             'tag-item',
-            { 'is-current': store.currentSpecialTag === '○' },
+            { 'is-current': store.currentSpecialTagType === 'CIRCLE' },
           ]"
           ]"
           title="标记圆圈"
           title="标记圆圈"
           @click="chooseSpecialTag('○', 'CIRCLE')"
           @click="chooseSpecialTag('○', 'CIRCLE')"
@@ -105,7 +105,7 @@
         <div
         <div
           :class="[
           :class="[
             'tag-item',
             'tag-item',
-            { 'is-current': store.currentSpecialTag === '-' },
+            { 'is-current': store.currentSpecialTagType === 'LINE' },
           ]"
           ]"
           title="标记圆圈"
           title="标记圆圈"
           @click="chooseSpecialTag('-', 'LINE')"
           @click="chooseSpecialTag('-', 'LINE')"

+ 49 - 0
src/features/mark/use/eleMove.ts

@@ -0,0 +1,49 @@
+export const vEleMoveDirective = {
+  mounted(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 });
+    };
+
+    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);
+    });
+  },
+};

+ 0 - 1
src/features/student/studentInspect/MarkBody.vue

@@ -262,7 +262,6 @@ const answerPaperScale = $computed(() => {
 
 
 <style scoped>
 <style scoped>
 .mark-body-container {
 .mark-body-container {
-  height: calc(100vh - 56px);
   overflow: auto;
   overflow: auto;
   background-color: var(--app-container-bg-color);
   background-color: var(--app-container-bg-color);
   background-image: linear-gradient(45deg, #e0e0e0 25%, transparent 25%),
   background-image: linear-gradient(45deg, #e0e0e0 25%, transparent 25%),

+ 3 - 3
src/types/index.ts

@@ -300,15 +300,15 @@ export interface Track {
 export interface SpecialTag {
 export interface SpecialTag {
   /** 第几张图 */
   /** 第几张图 */
   offsetIndex: number;
   offsetIndex: number;
-  /** 左上角为原点 */
+  /** 左上角为原点(原图的原点),及相对原图的位置比例 */
   offsetX: number;
   offsetX: number;
   offsetY: number;
   offsetY: number;
-  /** 相对slice的位置比例 */
+  /** 相对裁切图的位置比例 */
   positionX: number;
   positionX: number;
   positionY: number;
   positionY: number;
   /** 特殊标记的字符串,勾叉 */
   /** 特殊标记的字符串,勾叉 */
   tagName: string;
   tagName: string;
-  tagType: "TEXT" | "CIRCLE" | "RIGHT" | "WRONG" | "HALF_RIGTH";
+  tagType: "TEXT" | "CIRCLE" | "RIGHT" | "WRONG" | "HALF_RIGTH" | "LINE";
   // 分组号
   // 分组号
   groupNumber?: number;
   groupNumber?: number;
   userId?: number;
   userId?: number;