|
@@ -0,0 +1,286 @@
|
|
|
|
+<template>
|
|
|
|
+ <div
|
|
|
|
+ v-if="isCustomSpecialTag"
|
|
|
|
+ v-ele-move-directive.stop.prevent="{
|
|
|
|
+ moveStart: (event) => specialMouseStart(event),
|
|
|
|
+ moveElement: specialMouseMove,
|
|
|
|
+ moveStop: specialMouseStop,
|
|
|
|
+ }"
|
|
|
|
+ class="image-canvas"
|
|
|
|
+ @click="(event) => canvasClick(event)"
|
|
|
|
+ >
|
|
|
|
+ <template v-if="curSliceImagesWithTrackItem?.url === sliceImageItem.url">
|
|
|
|
+ <div
|
|
|
|
+ v-if="store.currentSpecialTagType === 'LINE'"
|
|
|
|
+ :style="specialLenStyle"
|
|
|
|
+ ></div>
|
|
|
|
+ <div
|
|
|
|
+ v-else-if="store.currentSpecialTagType === 'CIRCLE'"
|
|
|
|
+ :style="specialCircleStyle"
|
|
|
|
+ ></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>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script setup lang="ts">
|
|
|
|
+import { nextTick, watch } from "vue";
|
|
|
|
+import type { SliceImage, SpecialTag } from "@/types";
|
|
|
|
+import { store } from "@/store/store";
|
|
|
|
+import { randomCode } from "@/utils/utils";
|
|
|
|
+import { vEleMoveDirective } from "@/directives/eleMove";
|
|
|
|
+
|
|
|
|
+const { sliceImageItem } = defineProps<{
|
|
|
|
+ sliceImageItem: SliceImage;
|
|
|
|
+}>();
|
|
|
|
+
|
|
|
|
+const isCustomSpecialTag = $computed(() => {
|
|
|
|
+ return ["CIRCLE", "LINE", "TEXT"].includes(store.currentSpecialTagType);
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+let specialPoint = $ref({ x: 0, y: 0, ex: 0, ey: 0 });
|
|
|
|
+let curImageTarget: HTMLElement = null;
|
|
|
|
+let curSliceImagesWithTrackItem: SliceImage = $ref(null);
|
|
|
|
+let cacheTextTrack = $ref({
|
|
|
|
+ id: "",
|
|
|
|
+ x: 0,
|
|
|
|
+ y: 0,
|
|
|
|
+ maxW: 0,
|
|
|
|
+ maxY: 0,
|
|
|
|
+ content: "",
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const specialLenStyle = $computed(() => {
|
|
|
|
+ if (specialPoint.ex <= specialPoint.x) return { display: "none" };
|
|
|
|
+
|
|
|
|
+ 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(() => {
|
|
|
|
+ if (specialPoint.ex <= specialPoint.x || specialPoint.ey <= specialPoint.y)
|
|
|
|
+ return { display: "none" };
|
|
|
|
+
|
|
|
|
+ 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,
|
|
|
|
+ };
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+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) {
|
|
|
|
+ if (store.currentSpecialTagType === "TEXT") return;
|
|
|
|
+
|
|
|
|
+ curImageTarget = e.target.parentElement.childNodes[0];
|
|
|
|
+ curSliceImagesWithTrackItem = sliceImageItem;
|
|
|
|
+ specialPoint.x = e.offsetX;
|
|
|
|
+ specialPoint.y = e.offsetY;
|
|
|
|
+}
|
|
|
|
+function specialMouseMove({ left, top }) {
|
|
|
|
+ if (store.currentSpecialTagType === "TEXT") return;
|
|
|
|
+
|
|
|
|
+ specialPoint.ex = left + specialPoint.x;
|
|
|
|
+ specialPoint.ey = top + specialPoint.y;
|
|
|
|
+}
|
|
|
|
+function specialMouseStop() {
|
|
|
|
+ if (store.currentSpecialTagType === "TEXT") return;
|
|
|
|
+
|
|
|
|
+ if (
|
|
|
|
+ store.currentSpecialTagType === "LINE" &&
|
|
|
|
+ specialPoint.ex <= specialPoint.x
|
|
|
|
+ ) {
|
|
|
|
+ specialPoint = { x: 0, y: 0, ex: 0, ey: 0 };
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (
|
|
|
|
+ store.currentSpecialTagType === "CIRCLE" &&
|
|
|
|
+ (specialPoint.ex <= specialPoint.x || specialPoint.ey <= specialPoint.y)
|
|
|
|
+ ) {
|
|
|
|
+ specialPoint = { x: 0, y: 0, ex: 0, ey: 0 };
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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,
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ 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 };
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function canvasClick(e: MouseEvent) {
|
|
|
|
+ if (cacheTextTrack.id) {
|
|
|
|
+ textTrackBlur();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const target = e.target as HTMLElement;
|
|
|
|
+ curImageTarget = target.parentElement?.childNodes[0] as HTMLElement;
|
|
|
|
+ curSliceImagesWithTrackItem = sliceImageItem;
|
|
|
|
+
|
|
|
|
+ cacheTextTrack.x = e.offsetX;
|
|
|
|
+ cacheTextTrack.y = e.offsetY;
|
|
|
|
+ cacheTextTrack.id = randomCode();
|
|
|
|
+ cacheTextTrack.content = "";
|
|
|
|
+
|
|
|
|
+ void nextTick(() => {
|
|
|
|
+ const element = document.getElementById(
|
|
|
|
+ `text-edit-box-${cacheTextTrack.id}`
|
|
|
|
+ );
|
|
|
|
+ element?.focus();
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function textTrackInput(e: Event) {
|
|
|
|
+ const target = e.target as HTMLElement;
|
|
|
|
+ cacheTextTrack.content = target.outerText;
|
|
|
|
+}
|
|
|
|
+function initCacheTextTrack() {
|
|
|
|
+ cacheTextTrack = {
|
|
|
|
+ x: 0,
|
|
|
|
+ y: 0,
|
|
|
|
+ maxW: 0,
|
|
|
|
+ maxY: 0,
|
|
|
|
+ content: "",
|
|
|
|
+ id: "",
|
|
|
|
+ };
|
|
|
|
+}
|
|
|
|
+function textTrackBlur() {
|
|
|
|
+ if (!cacheTextTrack.content) {
|
|
|
|
+ store.currentSpecialTagType = undefined;
|
|
|
|
+ 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,
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ store.currentTaskEnsured.markResult.specialTagList.push(track);
|
|
|
|
+ curSliceImagesWithTrackItem.tagList.push(track);
|
|
|
|
+ initCacheTextTrack();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+watch(
|
|
|
|
+ () => store.currentSpecialTagType,
|
|
|
|
+ () => {
|
|
|
|
+ if (cacheTextTrack.id) {
|
|
|
|
+ initCacheTextTrack();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+);
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+.image-canvas {
|
|
|
|
+ position: absolute;
|
|
|
|
+ top: 0;
|
|
|
|
+ left: 0;
|
|
|
|
+ right: 0;
|
|
|
|
+ bottom: 0;
|
|
|
|
+ z-index: 9;
|
|
|
|
+}
|
|
|
|
+.text-edit-box {
|
|
|
|
+ position: absolute;
|
|
|
|
+ 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;
|
|
|
|
+}
|
|
|
|
+</style>
|