|
@@ -35,7 +35,6 @@
|
|
:sliceImageHeight="item.sliceImageHeight"
|
|
:sliceImageHeight="item.sliceImageHeight"
|
|
:dx="item.dx"
|
|
:dx="item.dx"
|
|
:dy="item.dy"
|
|
:dy="item.dy"
|
|
- @delete-specialtag="(tag) => deleteSpecialtag(item, tag)"
|
|
|
|
@click-specialtag="(event) => clickSpecialtag(event, item)"
|
|
@click-specialtag="(event) => clickSpecialtag(event, item)"
|
|
/>
|
|
/>
|
|
<div
|
|
<div
|
|
@@ -46,6 +45,7 @@
|
|
moveElement: specialMouseMove,
|
|
moveElement: specialMouseMove,
|
|
moveStop: specialMouseStop,
|
|
moveStop: specialMouseStop,
|
|
}"
|
|
}"
|
|
|
|
+ @click="(event) => canvasClick(event, item)"
|
|
>
|
|
>
|
|
<template v-if="curSliceImagesWithTrackItem?.url === item.url">
|
|
<template v-if="curSliceImagesWithTrackItem?.url === item.url">
|
|
<div
|
|
<div
|
|
@@ -53,9 +53,25 @@
|
|
:style="specialLenStyle"
|
|
:style="specialLenStyle"
|
|
></div>
|
|
></div>
|
|
<div
|
|
<div
|
|
- v-if="store.currentSpecialTagType === 'CIRCLE'"
|
|
|
|
|
|
+ v-else-if="store.currentSpecialTagType === 'CIRCLE'"
|
|
:style="specialCircleStyle"
|
|
:style="specialCircleStyle"
|
|
></div>
|
|
></div>
|
|
|
|
+ <div
|
|
|
|
+ v-else-if="store.currentSpecialTagType === 'TEXT'"
|
|
|
|
+ v-show="cacheTextTrack.id"
|
|
|
|
+ :id="`text-edit-box-${cacheTextTrack.id}`"
|
|
|
|
+ :key="cacheTextTrack.id"
|
|
|
|
+ class="text-edit-box"
|
|
|
|
+ contenteditable
|
|
|
|
+ :style="specialTextStyle"
|
|
|
|
+ @input="textTrackInput"
|
|
|
|
+ @blur="textTrackBlur"
|
|
|
|
+ @keypress.stop
|
|
|
|
+ @keydown.stop
|
|
|
|
+ @mousedown.stop
|
|
|
|
+ @mousemove.stop
|
|
|
|
+ @mouseup.stop
|
|
|
|
+ ></div>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@@ -71,7 +87,14 @@
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
-import { onMounted, onUnmounted, reactive, watch, watchEffect } from "vue";
|
|
|
|
|
|
+import {
|
|
|
|
+ nextTick,
|
|
|
|
+ onMounted,
|
|
|
|
+ onUnmounted,
|
|
|
|
+ reactive,
|
|
|
|
+ watch,
|
|
|
|
+ watchEffect,
|
|
|
|
+} from "vue";
|
|
import { store } from "@/store/store";
|
|
import { store } from "@/store/store";
|
|
import MarkDrawTrack from "./MarkDrawTrack.vue";
|
|
import MarkDrawTrack from "./MarkDrawTrack.vue";
|
|
import type { SliceImage } from "@/types";
|
|
import type { SliceImage } from "@/types";
|
|
@@ -80,6 +103,7 @@ import {
|
|
getDataUrlForSliceConfig,
|
|
getDataUrlForSliceConfig,
|
|
getDataUrlForSplitConfig,
|
|
getDataUrlForSplitConfig,
|
|
loadImage,
|
|
loadImage,
|
|
|
|
+ randomCode,
|
|
} from "@/utils/utils";
|
|
} from "@/utils/utils";
|
|
import { dragImage } from "./use/draggable";
|
|
import { dragImage } from "./use/draggable";
|
|
import MultiMediaMarkBody from "./MultiMediaMarkBody.vue";
|
|
import MultiMediaMarkBody from "./MultiMediaMarkBody.vue";
|
|
@@ -454,27 +478,6 @@ async function processSplitConfig() {
|
|
}, 300);
|
|
}, 300);
|
|
}
|
|
}
|
|
|
|
|
|
-const deleteSpecialtag = (item, tag) => {
|
|
|
|
- const findInd = (tagList, curTag) => {
|
|
|
|
- return tagList.findIndex(
|
|
|
|
- (itemTag) =>
|
|
|
|
- itemTag.tagName === curTag.tagName &&
|
|
|
|
- itemTag.offsetX === curTag.offsetX &&
|
|
|
|
- itemTag.offsetY === curTag.offsetY
|
|
|
|
- );
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const tagIndex = findInd(item.tagList, tag);
|
|
|
|
- if (tagIndex === -1) return;
|
|
|
|
- item.tagList.splice(tagIndex, 1);
|
|
|
|
-
|
|
|
|
- const stagIndex = findInd(
|
|
|
|
- store.currentTaskEnsured.markResult.specialTagList,
|
|
|
|
- tag
|
|
|
|
- );
|
|
|
|
- if (stagIndex === -1) return;
|
|
|
|
- store.currentTaskEnsured.markResult.specialTagList.splice(tagIndex, 1);
|
|
|
|
-};
|
|
|
|
const clickSpecialtag = (event: MouseEvent, item: SliceImage) => {
|
|
const clickSpecialtag = (event: MouseEvent, item: SliceImage) => {
|
|
// console.log(event);
|
|
// console.log(event);
|
|
const e = {
|
|
const e = {
|
|
@@ -486,14 +489,6 @@ const clickSpecialtag = (event: MouseEvent, item: SliceImage) => {
|
|
makeTrack(e as MouseEvent, item, maxSliceWidth, theFinalHeight);
|
|
makeTrack(e as MouseEvent, item, maxSliceWidth, theFinalHeight);
|
|
};
|
|
};
|
|
|
|
|
|
-const clearEmptySpecialTag = (item) => {
|
|
|
|
- item.tagList
|
|
|
|
- .filter((item) => !item.tagName.trim().replace("\n", ""))
|
|
|
|
- .forEach((tag) => {
|
|
|
|
- deleteSpecialtag(item, tag);
|
|
|
|
- });
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
// should not render twice at the same time
|
|
// should not render twice at the same time
|
|
let renderLock = false;
|
|
let renderLock = false;
|
|
const renderPaperAndMark = async () => {
|
|
const renderPaperAndMark = async () => {
|
|
@@ -657,19 +652,26 @@ const innerMakeTrack = (event: MouseEvent, item: SliceImage) => {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- clearEmptySpecialTag(item);
|
|
|
|
makeTrack(event, item, maxSliceWidth, theFinalHeight);
|
|
makeTrack(event, item, maxSliceWidth, theFinalHeight);
|
|
};
|
|
};
|
|
//#endregion : 评分
|
|
//#endregion : 评分
|
|
|
|
|
|
-//#region : 特殊标记:画线、框
|
|
|
|
|
|
+//#region : 特殊标记:画线、框、文字
|
|
const isCustomSpecialTag = $computed(() => {
|
|
const isCustomSpecialTag = $computed(() => {
|
|
- return ["CIRCLE", "LINE"].includes(store.currentSpecialTagType);
|
|
|
|
|
|
+ return ["CIRCLE", "LINE", "TEXT"].includes(store.currentSpecialTagType);
|
|
});
|
|
});
|
|
|
|
|
|
let specialPoint = $ref({ x: 0, y: 0, ex: 0, ey: 0 });
|
|
let specialPoint = $ref({ x: 0, y: 0, ex: 0, ey: 0 });
|
|
let curImageTarget: HTMLElement = null;
|
|
let curImageTarget: HTMLElement = null;
|
|
let curSliceImagesWithTrackItem: SliceImage = $ref(null);
|
|
let curSliceImagesWithTrackItem: SliceImage = $ref(null);
|
|
|
|
+let cacheTextTrack = $ref({
|
|
|
|
+ id: "",
|
|
|
|
+ x: 0,
|
|
|
|
+ y: 0,
|
|
|
|
+ maxW: 0,
|
|
|
|
+ maxY: 0,
|
|
|
|
+ content: "",
|
|
|
|
+});
|
|
|
|
|
|
const specialLenStyle = $computed(() => {
|
|
const specialLenStyle = $computed(() => {
|
|
if (specialPoint.ex <= specialPoint.x) return { display: "none" };
|
|
if (specialPoint.ex <= specialPoint.x) return { display: "none" };
|
|
@@ -704,28 +706,46 @@ const specialCircleStyle = $computed(() => {
|
|
zIndex: 9,
|
|
zIndex: 9,
|
|
};
|
|
};
|
|
});
|
|
});
|
|
|
|
+const specialTextStyle = $computed(() => {
|
|
|
|
+ return {
|
|
|
|
+ top: cacheTextTrack.y + "px",
|
|
|
|
+ left: cacheTextTrack.x + "px",
|
|
|
|
+ minWidth: "30px",
|
|
|
|
+ minHeight: "30px",
|
|
|
|
+ maxWidth: curImageTarget.width - cacheTextTrack.x + "px",
|
|
|
|
+ maxHeight: curImageTarget.height - cacheTextTrack.y + "px",
|
|
|
|
+ };
|
|
|
|
+});
|
|
|
|
|
|
function specialMouseStart(e: MouseEvent, item: SliceImage) {
|
|
function specialMouseStart(e: MouseEvent, item: SliceImage) {
|
|
|
|
+ if (store.currentSpecialTagType === "TEXT") return;
|
|
|
|
+
|
|
curImageTarget = e.target.parentElement.childNodes[0];
|
|
curImageTarget = e.target.parentElement.childNodes[0];
|
|
curSliceImagesWithTrackItem = item;
|
|
curSliceImagesWithTrackItem = item;
|
|
specialPoint.x = e.offsetX;
|
|
specialPoint.x = e.offsetX;
|
|
specialPoint.y = e.offsetY;
|
|
specialPoint.y = e.offsetY;
|
|
}
|
|
}
|
|
function specialMouseMove({ left, top }) {
|
|
function specialMouseMove({ left, top }) {
|
|
|
|
+ if (store.currentSpecialTagType === "TEXT") return;
|
|
|
|
+
|
|
specialPoint.ex = left + specialPoint.x;
|
|
specialPoint.ex = left + specialPoint.x;
|
|
specialPoint.ey = top + specialPoint.y;
|
|
specialPoint.ey = top + specialPoint.y;
|
|
}
|
|
}
|
|
function specialMouseStop(e: MouseEvent) {
|
|
function specialMouseStop(e: MouseEvent) {
|
|
|
|
+ if (store.currentSpecialTagType === "TEXT") return;
|
|
|
|
+
|
|
if (
|
|
if (
|
|
store.currentSpecialTagType === "LINE" &&
|
|
store.currentSpecialTagType === "LINE" &&
|
|
specialPoint.ex <= specialPoint.x
|
|
specialPoint.ex <= specialPoint.x
|
|
) {
|
|
) {
|
|
|
|
+ specialPoint = { x: 0, y: 0, ex: 0, ey: 0 };
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
if (
|
|
if (
|
|
store.currentSpecialTagType === "CIRCLE" &&
|
|
store.currentSpecialTagType === "CIRCLE" &&
|
|
(specialPoint.ex <= specialPoint.x || specialPoint.ey <= specialPoint.y)
|
|
(specialPoint.ex <= specialPoint.x || specialPoint.ey <= specialPoint.y)
|
|
) {
|
|
) {
|
|
|
|
+ specialPoint = { x: 0, y: 0, ex: 0, ey: 0 };
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -741,11 +761,12 @@ function specialMouseStop(e: MouseEvent) {
|
|
curSliceImagesWithTrackItem.dy,
|
|
curSliceImagesWithTrackItem.dy,
|
|
positionX: -1,
|
|
positionX: -1,
|
|
positionY: -1,
|
|
positionY: -1,
|
|
|
|
+ groupNumber: store.currentQuestion.groupNumber,
|
|
};
|
|
};
|
|
track.positionX =
|
|
track.positionX =
|
|
- (specialPoint.x - curSliceImagesWithTrackItem.dx) / maxSliceWidth;
|
|
|
|
|
|
+ (track.offsetX - curSliceImagesWithTrackItem.dx) / maxSliceWidth;
|
|
track.positionY =
|
|
track.positionY =
|
|
- (specialPoint.y -
|
|
|
|
|
|
+ (track.offsetY -
|
|
curSliceImagesWithTrackItem.dy +
|
|
curSliceImagesWithTrackItem.dy +
|
|
curSliceImagesWithTrackItem.accumTopHeight) /
|
|
curSliceImagesWithTrackItem.accumTopHeight) /
|
|
theFinalHeight;
|
|
theFinalHeight;
|
|
@@ -772,6 +793,89 @@ function specialMouseStop(e: MouseEvent) {
|
|
curSliceImagesWithTrackItem.tagList.push(track);
|
|
curSliceImagesWithTrackItem.tagList.push(track);
|
|
specialPoint = { x: 0, y: 0, ex: 0, ey: 0 };
|
|
specialPoint = { x: 0, y: 0, ex: 0, ey: 0 };
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+function canvasClick(e: Event, item: SliceImage) {
|
|
|
|
+ if (cacheTextTrack.id) {
|
|
|
|
+ textTrackBlur();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ curImageTarget = e.target.parentElement.childNodes[0];
|
|
|
|
+ curSliceImagesWithTrackItem = item;
|
|
|
|
+
|
|
|
|
+ cacheTextTrack.x = e.offsetX;
|
|
|
|
+ cacheTextTrack.y = e.offsetY;
|
|
|
|
+ cacheTextTrack.id = randomCode();
|
|
|
|
+ cacheTextTrack.content = "";
|
|
|
|
+
|
|
|
|
+ nextTick(() => {
|
|
|
|
+ document.getElementById(`text-edit-box-${cacheTextTrack.id}`).focus();
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function textTrackInput(e: Event) {
|
|
|
|
+ cacheTextTrack.content = e.target.outerText;
|
|
|
|
+}
|
|
|
|
+function initCacheTextTrack() {
|
|
|
|
+ cacheTextTrack = {
|
|
|
|
+ x: 0,
|
|
|
|
+ y: 0,
|
|
|
|
+ maxW: 0,
|
|
|
|
+ maxY: 0,
|
|
|
|
+ content: "",
|
|
|
|
+ id: "",
|
|
|
|
+ };
|
|
|
|
+}
|
|
|
|
+function textTrackBlur() {
|
|
|
|
+ if (!cacheTextTrack.content) {
|
|
|
|
+ initCacheTextTrack();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const textBoxDom = document.getElementById(
|
|
|
|
+ `text-edit-box-${cacheTextTrack.id}`
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // 减去内边距所占宽高
|
|
|
|
+ const tagName = JSON.stringify({
|
|
|
|
+ width: textBoxDom.offsetWidth - 10,
|
|
|
|
+ height: textBoxDom.offsetHeight - 10,
|
|
|
|
+ content: cacheTextTrack.content,
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ const track: SpecialTag = {
|
|
|
|
+ tagName,
|
|
|
|
+ tagType: store.currentSpecialTagType,
|
|
|
|
+ offsetIndex: curSliceImagesWithTrackItem.indexInSliceUrls,
|
|
|
|
+ offsetX:
|
|
|
|
+ cacheTextTrack.x * (curImageTarget.naturalWidth / curImageTarget.width) +
|
|
|
|
+ curSliceImagesWithTrackItem.dx,
|
|
|
|
+ offsetY:
|
|
|
|
+ cacheTextTrack.y *
|
|
|
|
+ (curImageTarget.naturalHeight / curImageTarget.height) +
|
|
|
|
+ curSliceImagesWithTrackItem.dy,
|
|
|
|
+ positionX: -1,
|
|
|
|
+ positionY: -1,
|
|
|
|
+ groupNumber: store.currentQuestion.groupNumber,
|
|
|
|
+ };
|
|
|
|
+ track.positionX =
|
|
|
|
+ (track.offsetX - curSliceImagesWithTrackItem.dx) / maxSliceWidth;
|
|
|
|
+ track.positionY =
|
|
|
|
+ (track.offsetY -
|
|
|
|
+ curSliceImagesWithTrackItem.dy +
|
|
|
|
+ curSliceImagesWithTrackItem.accumTopHeight) /
|
|
|
|
+ theFinalHeight;
|
|
|
|
+
|
|
|
|
+ store.currentTaskEnsured.markResult.specialTagList.push(track);
|
|
|
|
+ curSliceImagesWithTrackItem.tagList.push(track);
|
|
|
|
+ initCacheTextTrack();
|
|
|
|
+}
|
|
|
|
+watch(
|
|
|
|
+ () => store.currentSpecialTagType,
|
|
|
|
+ () => {
|
|
|
|
+ if (cacheTextTrack.id) {
|
|
|
|
+ initCacheTextTrack();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+);
|
|
//#endregion
|
|
//#endregion
|
|
|
|
|
|
//#region : 显示大图,供查看和翻转
|
|
//#region : 显示大图,供查看和翻转
|
|
@@ -911,14 +1015,21 @@ function scrollToFirstScore() {
|
|
bottom: 0;
|
|
bottom: 0;
|
|
z-index: 9;
|
|
z-index: 9;
|
|
}
|
|
}
|
|
-.double-triangle {
|
|
|
|
- background-color: #ef7c78;
|
|
|
|
- width: 30px;
|
|
|
|
- height: 6px;
|
|
|
|
- clip-path: polygon(0 0, 0 6px, 50% 0, 100% 0, 100% 6px, 50% 0);
|
|
|
|
-
|
|
|
|
|
|
+.text-edit-box {
|
|
position: absolute;
|
|
position: absolute;
|
|
- bottom: -5px;
|
|
|
|
|
|
+ border: 1px solid #ff0000;
|
|
|
|
+ line-height: 24px;
|
|
|
|
+ padding: 5px;
|
|
|
|
+ font-size: 20px;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ margin: -15px 0 0 -5px;
|
|
|
|
+ outline: none;
|
|
|
|
+ z-index: 9;
|
|
|
|
+ font-family: 黑体, arial, sans-serif;
|
|
|
|
+ color: #ff0000;
|
|
|
|
+}
|
|
|
|
+.text-edit-box:focus {
|
|
|
|
+ border-color: #ff5050;
|
|
}
|
|
}
|
|
|
|
|
|
@keyframes rotate {
|
|
@keyframes rotate {
|