|
@@ -1,10 +1,300 @@
|
|
|
<template>
|
|
|
- <CommonMarkBody v-if="store" @error="$emit('error')" />
|
|
|
+ <CommonMarkBody
|
|
|
+ v-if="store"
|
|
|
+ :hasMarkResultToRender="true"
|
|
|
+ :makeTrack="makeTrack"
|
|
|
+ @error="$emit('error')"
|
|
|
+ />
|
|
|
+ <div class="cursor">
|
|
|
+ <div class="cursor-border">
|
|
|
+ <span class="text">{{
|
|
|
+ store.currentSpecialTag ||
|
|
|
+ (Object.is(store.currentScore, -0) ? "空" : store.currentScore)
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import CommonMarkBody from "@/features/mark/CommonMarkBody.vue";
|
|
|
import { store } from "@/store/store";
|
|
|
+import CustomCursor from "custom-cursor.js";
|
|
|
+import { onMounted, onUnmounted, watch } from "vue";
|
|
|
+import { SliceImage, SpecialTag, Track } from "@/types";
|
|
|
+
|
|
|
+const makeScoreTrack = (
|
|
|
+ event: MouseEvent,
|
|
|
+ item: SliceImage,
|
|
|
+ maxSliceWidth: number,
|
|
|
+ theFinalHeight: number
|
|
|
+) => {
|
|
|
+ // console.log(item);
|
|
|
+ if (!store.currentQuestion || typeof store.currentScore === "undefined")
|
|
|
+ return;
|
|
|
+ const target = event.target as HTMLImageElement;
|
|
|
+ const track: Track = {
|
|
|
+ mainNumber: store.currentQuestion?.mainNumber,
|
|
|
+ subNumber: store.currentQuestion?.subNumber,
|
|
|
+ score: store.currentScore,
|
|
|
+ unanswered: Object.is(store.currentScore, -0),
|
|
|
+ offsetIndex: item.indexInSliceUrls,
|
|
|
+ offsetX: Math.round(
|
|
|
+ event.offsetX * (target.naturalWidth / target.width) + item.dx
|
|
|
+ ),
|
|
|
+ offsetY: Math.round(
|
|
|
+ event.offsetY * (target.naturalHeight / target.height) + item.dy
|
|
|
+ ),
|
|
|
+ positionX: -1,
|
|
|
+ positionY: -1,
|
|
|
+ number: -1,
|
|
|
+ };
|
|
|
+ track.positionX = (track.offsetX - item.dx) / maxSliceWidth;
|
|
|
+ track.positionY =
|
|
|
+ (track.offsetY - item.dy + item.accumTopHeight) / theFinalHeight;
|
|
|
+
|
|
|
+ // const isIllegalRange = (testNum: number, min: number, max: number) => {
|
|
|
+ // return testNum < min || testNum > max;
|
|
|
+ // };
|
|
|
+
|
|
|
+ // // 检测有问题,此处没有给原图的宽高,如果有的话,要稍微修改下数据类型
|
|
|
+ // // 但其实下面也做了一个基本检测
|
|
|
+ // if (
|
|
|
+ // isIllegalRange(track.offsetX, 0, target.naturalWidth) ||
|
|
|
+ // isIllegalRange(track.offsetY, 0, target.naturalHeight) ||
|
|
|
+ // isIllegalRange(track.positionX, 0, 1) ||
|
|
|
+ // isIllegalRange(track.positionY, 0, 1)
|
|
|
+ // ) {
|
|
|
+ // console.error(
|
|
|
+ // "错误的track",
|
|
|
+ // track,
|
|
|
+ // target.naturalWidth,
|
|
|
+ // target.naturalHeight
|
|
|
+ // );
|
|
|
+ // void message.error("系统错误,请联系管理员!");
|
|
|
+ // }
|
|
|
+
|
|
|
+ if (track.offsetX > item.effectiveWidth + item.dx) {
|
|
|
+ console.log("不在有效宽度内,轨迹不生效");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ item.trackList.some((t) => {
|
|
|
+ return (
|
|
|
+ Math.pow(Math.abs(t.offsetX - track.offsetX), 2) +
|
|
|
+ Math.pow(Math.abs(t.offsetY - track.offsetY), 2) <
|
|
|
+ 500
|
|
|
+ );
|
|
|
+ })
|
|
|
+ ) {
|
|
|
+ console.log("两个轨迹相距过近");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 是否保留当前的轨迹分
|
|
|
+ const questionScore =
|
|
|
+ store.currentTask &&
|
|
|
+ store.currentQuestion &&
|
|
|
+ store.currentTask.markResult.scoreList[store.currentQuestion.__index];
|
|
|
+ const ifKeepScore =
|
|
|
+ Math.round(
|
|
|
+ store.currentQuestion.maxScore * 1000 -
|
|
|
+ (questionScore || 0) * 1000 -
|
|
|
+ store.currentScore * 2 * 1000
|
|
|
+ ) / 1000;
|
|
|
+ if (ifKeepScore < 0 && store.currentScore > 0) {
|
|
|
+ store.currentScore = undefined;
|
|
|
+ }
|
|
|
+ const markResult = store.currentTaskEnsured.markResult;
|
|
|
+ const maxNumber =
|
|
|
+ markResult.trackList.length === 0
|
|
|
+ ? 0
|
|
|
+ : Math.max(...markResult.trackList.map((t) => t.number));
|
|
|
+ track.number = maxNumber + 1;
|
|
|
+ // console.log(
|
|
|
+ // maxNumber,
|
|
|
+ // track.number,
|
|
|
+ // markResult.trackList.map((t) => t.number),
|
|
|
+ // Math.max(...markResult.trackList.map((t) => t.number))
|
|
|
+ // );
|
|
|
+ markResult.trackList = [...markResult.trackList, track];
|
|
|
+ const { __index, mainNumber, subNumber } = store.currentQuestion;
|
|
|
+ markResult.scoreList[__index] =
|
|
|
+ markResult.trackList
|
|
|
+ .filter((t) => t.mainNumber === mainNumber && t.subNumber === subNumber)
|
|
|
+ .map((t) => t.score)
|
|
|
+ .reduce((acc, v) => (acc += Math.round(v * 1000)), 0) / 1000;
|
|
|
+ item.trackList.push(track);
|
|
|
+};
|
|
|
+
|
|
|
+const makeSpecialTagTrack = (
|
|
|
+ event: MouseEvent,
|
|
|
+ item: SliceImage,
|
|
|
+ maxSliceWidth: number,
|
|
|
+ theFinalHeight: number
|
|
|
+) => {
|
|
|
+ // console.log(item);
|
|
|
+ if (!store.currentTask || typeof store.currentSpecialTag === "undefined")
|
|
|
+ return;
|
|
|
+ const target = event.target as HTMLImageElement;
|
|
|
+ const track: SpecialTag = {
|
|
|
+ tagName: store.currentSpecialTag,
|
|
|
+ offsetIndex: item.indexInSliceUrls,
|
|
|
+ offsetX: Math.round(
|
|
|
+ event.offsetX * (target.naturalWidth / target.width) + item.dx
|
|
|
+ ),
|
|
|
+ offsetY: Math.round(
|
|
|
+ event.offsetY * (target.naturalHeight / target.height) + item.dy
|
|
|
+ ),
|
|
|
+ positionX: -1,
|
|
|
+ positionY: -1,
|
|
|
+ };
|
|
|
+ track.positionX = (track.offsetX - item.dx) / maxSliceWidth;
|
|
|
+ track.positionY =
|
|
|
+ (track.offsetY - item.dy + item.accumTopHeight) / theFinalHeight;
|
|
|
+
|
|
|
+ // const isIllegalRange = (testNum: number, min: number, max: number) => {
|
|
|
+ // return testNum < min || testNum > max;
|
|
|
+ // };
|
|
|
+
|
|
|
+ // if (
|
|
|
+ // isIllegalRange(track.offsetX, 0, target.naturalWidth) ||
|
|
|
+ // isIllegalRange(track.offsetY, 0, target.naturalHeight) ||
|
|
|
+ // isIllegalRange(track.positionX, 0, 1) ||
|
|
|
+ // isIllegalRange(track.positionY, 0, 1)
|
|
|
+ // ) {
|
|
|
+ // console.error("错误的track", track);
|
|
|
+ // void message.error("系统错误,请联系管理员!");
|
|
|
+ // }
|
|
|
+ // if (track.offsetX > item.effectiveWidth + item.dx) {
|
|
|
+ // console.log("不在有效宽度内,轨迹不生效");
|
|
|
+ // return;
|
|
|
+ // }
|
|
|
+ if (
|
|
|
+ item.tagList.some((t) => {
|
|
|
+ return (
|
|
|
+ Math.pow(Math.abs(t.offsetX - track.offsetX), 2) +
|
|
|
+ Math.pow(Math.abs(t.offsetY - track.offsetY), 2) <
|
|
|
+ 500
|
|
|
+ );
|
|
|
+ })
|
|
|
+ ) {
|
|
|
+ console.log("两个轨迹相距过近");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ store.currentTaskEnsured.markResult.specialTagList.push(track);
|
|
|
+ item.tagList.push(track);
|
|
|
+};
|
|
|
+
|
|
|
+const makeTrack = (
|
|
|
+ event: MouseEvent,
|
|
|
+ item: SliceImage,
|
|
|
+ maxSliceWidth: number,
|
|
|
+ theFinalHeight: number
|
|
|
+) => {
|
|
|
+ if (store.setting.uiSetting["specialTag.modal"] && store.currentSpecialTag) {
|
|
|
+ makeSpecialTagTrack(event, item, maxSliceWidth, theFinalHeight);
|
|
|
+ } else {
|
|
|
+ makeScoreTrack(event, item, maxSliceWidth, theFinalHeight);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => store.setting.mode,
|
|
|
+ () => {
|
|
|
+ const shouldHide = store.setting.mode === "COMMON";
|
|
|
+ if (shouldHide) {
|
|
|
+ // console.log("hide cursor", theCursor);
|
|
|
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
|
+ theCursor && theCursor.destroy();
|
|
|
+ } else {
|
|
|
+ if (document.querySelector(".cursor")) {
|
|
|
+ // console.log("show cursor", theCursor);
|
|
|
+ // theCursor && theCursor.enable();
|
|
|
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
|
+ theCursor = new CustomCursor(".cursor", {
|
|
|
+ focusElements: [
|
|
|
+ {
|
|
|
+ selector: ".mark-body-container",
|
|
|
+ focusClass: "cursor--focused-view",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }).initialize();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+);
|
|
|
+let theCursor = null as any;
|
|
|
+onMounted(() => {
|
|
|
+ if (store.isTrackMode) {
|
|
|
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
|
+ theCursor = new CustomCursor(".cursor", {
|
|
|
+ focusElements: [
|
|
|
+ {
|
|
|
+ selector: ".mark-body-container",
|
|
|
+ focusClass: "cursor--focused-view",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }).initialize();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
|
+ theCursor && theCursor.destroy();
|
|
|
+});
|
|
|
|
|
|
defineEmits(["error"]);
|
|
|
</script>
|
|
|
+<style scoped>
|
|
|
+.cursor {
|
|
|
+ color: #ff5050;
|
|
|
+ display: none;
|
|
|
+ pointer-events: none;
|
|
|
+ user-select: none;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ position: fixed;
|
|
|
+ will-change: transform;
|
|
|
+ z-index: 1000;
|
|
|
+}
|
|
|
+
|
|
|
+.cursor-border {
|
|
|
+ position: absolute;
|
|
|
+ box-sizing: border-box;
|
|
|
+ align-items: center;
|
|
|
+ border: 1px solid #ff5050;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ height: 0px;
|
|
|
+ width: 0px;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ transition: all 360ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
|
+}
|
|
|
+
|
|
|
+.cursor.cursor--initialized {
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.cursor .text {
|
|
|
+ font-size: 2rem;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 80ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
|
+}
|
|
|
+
|
|
|
+.cursor.cursor--off-screen {
|
|
|
+ opacity: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.cursor.cursor--focused .cursor-border,
|
|
|
+.cursor.cursor--focused-view .cursor-border {
|
|
|
+ width: 90px;
|
|
|
+ height: 90px;
|
|
|
+}
|
|
|
+
|
|
|
+.cursor.cursor--focused-view .text {
|
|
|
+ opacity: 1;
|
|
|
+ transition: opacity 360ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
|
+}
|
|
|
+</style>
|