MarkTool.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <template>
  2. <div class="mark-tool">
  3. <div>
  4. <div
  5. v-if="checkValid('allPage')"
  6. class="mark-tool-item"
  7. @click="toAllPage"
  8. >
  9. <img src="@/assets/icons/icon-all-page.svg" />
  10. <p>全卷</p>
  11. </div>
  12. <div
  13. v-if="checkValid('paper') && markStore.paperUrl"
  14. :class="[
  15. 'mark-tool-item',
  16. { 'is-active': markStore.setting.uiSetting['paper.modal'] },
  17. ]"
  18. @click="toPaper"
  19. >
  20. <img src="@/assets/icons/icon-paper.svg" />
  21. <p>试卷</p>
  22. </div>
  23. <div
  24. v-if="checkValid('answer') && markStore.answerUrl"
  25. :class="[
  26. 'mark-tool-item',
  27. { 'is-active': markStore.setting.uiSetting['answer.modal'] },
  28. ]"
  29. @click="toAnswer"
  30. >
  31. <img src="@/assets/icons/icon-answer.svg" />
  32. <p>答案</p>
  33. </div>
  34. <div
  35. v-if="checkValid('minimap')"
  36. :class="[
  37. 'mark-tool-item',
  38. { 'is-active': markStore.setting.uiSetting['minimap.modal'] },
  39. ]"
  40. @click="toThumbnail"
  41. >
  42. <img src="@/assets/icons/icon-thumbnail.svg" />
  43. <p>缩略图</p>
  44. </div>
  45. <div
  46. v-if="checkValid('issuePaper')"
  47. class="mark-tool-item"
  48. @click="toIssuePaper"
  49. >
  50. <img src="@/assets/icons/icon-issue-paper.svg" />
  51. <p>问题试卷</p>
  52. </div>
  53. <div v-if="checkValid('sizeScale')" class="mark-tool-item tool-scale">
  54. <div>
  55. <span>Aa</span>
  56. <a-slider
  57. v-model:value="markStore.setting.uiSetting['score.fontSize.scale']"
  58. :min="0.5"
  59. :step="0.1"
  60. :max="3"
  61. />
  62. </div>
  63. <p>分数/标记大小</p>
  64. </div>
  65. <div
  66. v-if="checkValid('shortCut')"
  67. :class="[
  68. 'mark-tool-item',
  69. { 'is-active': markStore.setting.uiSetting['shortCut.modal'] },
  70. ]"
  71. @click="toShortcut"
  72. >
  73. <img src="@/assets/icons/icon-shortcut.svg" />
  74. <p>快捷键</p>
  75. </div>
  76. <div v-if="checkValid('specialTag')" class="tool-special">
  77. <div
  78. :class="[
  79. 'tag-item',
  80. { 'is-current': markStore.currentSpecialTag === '√' },
  81. ]"
  82. @click="chooseSpecialTag('√', 'RIGHT')"
  83. >
  84. <img src="@/assets/icons/icon-right.svg" />
  85. </div>
  86. <div
  87. :class="[
  88. 'tag-item',
  89. {
  90. 'is-current': markStore.currentSpecialTag === '乄',
  91. },
  92. ]"
  93. @click="chooseSpecialTag('乄', 'HALF_RIGTH')"
  94. >
  95. </div>
  96. <div
  97. :class="[
  98. 'tag-item',
  99. { 'is-current': markStore.currentSpecialTag === 'X' },
  100. ]"
  101. @click="chooseSpecialTag('X', 'WRONG')"
  102. >
  103. <img src="@/assets/icons/icon-wrong.svg" />
  104. </div>
  105. <div
  106. :class="[
  107. 'tag-item',
  108. { 'is-current': markStore.currentSpecialTagType === 'CIRCLE' },
  109. ]"
  110. title="标记圆圈"
  111. @click="chooseSpecialTag('○', 'CIRCLE')"
  112. >
  113. <img src="@/assets/icons/icon-circle.svg" />
  114. </div>
  115. <div
  116. :class="[
  117. 'tag-item',
  118. { 'is-current': markStore.currentSpecialTagType === 'LINE' },
  119. ]"
  120. title="标记圆圈"
  121. @click="chooseSpecialTag('-', 'LINE')"
  122. >
  123. -
  124. </div>
  125. <div
  126. :class="[
  127. 'tag-item',
  128. { 'is-current': markStore.currentSpecialTagType === 'TEXT' },
  129. ]"
  130. title="标记文本"
  131. @click="chooseSpecialTag('', 'TEXT')"
  132. >
  133. <img src="@/assets/icons/icon-text.svg" />
  134. </div>
  135. <div class="tag-item tag-splite"></div>
  136. <div class="tag-item" title="回退" @click="clearLatestTagOfCurrentTask">
  137. <img src="@/assets/icons/icon-goback.svg" />
  138. </div>
  139. <div
  140. class="tag-item tag-danger"
  141. title="清空"
  142. @click="clearAllTagsOfCurrentTask"
  143. >
  144. <img src="@/assets/icons/icon-clear.svg" />
  145. </div>
  146. </div>
  147. </div>
  148. <div>
  149. <div
  150. v-if="checkValid('questionModel')"
  151. class="mark-tool-item"
  152. style="width: 70px"
  153. @click="toSwitchQuestionModal"
  154. >
  155. <img
  156. v-if="markStore.setting.questionModel === 'SINGLE'"
  157. src="@/assets/icons/icon-split-block.svg"
  158. />
  159. <img v-else src="@/assets/icons/icon-block.svg" />
  160. <p>{{ questionModalName }}</p>
  161. </div>
  162. <div class="mark-tool-item" @click="toEyecare">
  163. <img src="@/assets/icons/icon-eye-green.svg" />
  164. <p>护眼模式</p>
  165. </div>
  166. <div
  167. v-if="
  168. markStore.setting.enableAllZero && !markStore.setting.forceSpecialTag
  169. "
  170. class="mark-tool-item"
  171. @click="toAllZero"
  172. >
  173. <img src="@/assets/icons/icon-all-zero.svg" />
  174. <p>全部零分</p>
  175. </div>
  176. <template v-if="checkValid('imgScale')">
  177. <div
  178. :class="['mark-tool-item', { 'is-active': greaterThanOneScale }]"
  179. @click="toMagnify"
  180. >
  181. <img src="@/assets/icons/icon-magnify.svg" />
  182. <p>放大</p>
  183. </div>
  184. <div
  185. :class="['mark-tool-item', { 'is-active': lessThanOneScale }]"
  186. @click="toMinify"
  187. >
  188. <img src="@/assets/icons/icon-minify.svg" />
  189. <p>缩小</p>
  190. </div>
  191. <div
  192. :class="['mark-tool-item', { 'is-active': equalOneScale }]"
  193. @click="toOrigin"
  194. >
  195. <img src="@/assets/icons/icon-origin-size.svg" />
  196. <p>实际大小</p>
  197. </div>
  198. </template>
  199. </div>
  200. </div>
  201. <MarkProblemDialog v-if="checkValid('issuePaper')" ref="problemRef" />
  202. </template>
  203. <script setup lang="ts">
  204. import { computed, onMounted, onUnmounted } from "vue";
  205. import { useMarkStore } from "@/store";
  206. import { Modal } from "ant-design-vue";
  207. import { updateUISetting } from "@/api/markPage";
  208. import MarkProblemDialog from "./MarkProblemDialog.vue";
  209. const markStore = useMarkStore();
  210. /**
  211. * allPage:全卷
  212. * paper:试卷
  213. * answer:答案
  214. * minimap:缩略图
  215. * issuePaper:问题试卷
  216. * sizeScale:标记大小
  217. * shortCut:快捷键
  218. * specialTag:特殊标记
  219. * imgScale:图片缩放
  220. * questionModel:切换题目模式
  221. */
  222. const { actions = [] } = defineProps<{
  223. actions?: string[];
  224. }>();
  225. type ShowModalFunc = () => void;
  226. const checkValid = (name: string) => {
  227. if (!actions.length) return true;
  228. return actions.includes(name);
  229. };
  230. const emit = defineEmits(["allZeroSubmit"]);
  231. let problemRef = $ref<InstanceType<typeof MarkProblemDialog>>();
  232. const toAllPage = () => {
  233. markStore.allPaperModal = !markStore.allPaperModal;
  234. };
  235. const toThumbnail = () => {
  236. markStore.setting.uiSetting["minimap.modal"] =
  237. !markStore.setting.uiSetting["minimap.modal"];
  238. };
  239. const toAnswer = () => {
  240. markStore.setting.uiSetting["answer.modal"] =
  241. !markStore.setting.uiSetting["answer.modal"];
  242. };
  243. const toPaper = () => {
  244. markStore.setting.uiSetting["paper.modal"] =
  245. !markStore.setting.uiSetting["paper.modal"];
  246. };
  247. const toIssuePaper = () => {
  248. (problemRef.showModal as ShowModalFunc)();
  249. };
  250. const toShortcut = () => {
  251. markStore.setting.uiSetting["shortCut.modal"] =
  252. !markStore.setting.uiSetting["shortCut.modal"];
  253. };
  254. const toAllZero = () => {
  255. Modal.confirm({
  256. title: "操作警告",
  257. content: "确定给全零分?",
  258. onOk() {
  259. emit("allZeroSubmit");
  260. },
  261. });
  262. };
  263. const toMagnify = () => {
  264. const s = markStore.setting.uiSetting["answer.paper.scale"];
  265. if (s < 3)
  266. markStore.setting.uiSetting["answer.paper.scale"] = +(s + 0.2).toFixed(1);
  267. };
  268. const toMinify = () => {
  269. const s = markStore.setting.uiSetting["answer.paper.scale"];
  270. if (s > 0.2)
  271. markStore.setting.uiSetting["answer.paper.scale"] = +(s - 0.2).toFixed(1);
  272. };
  273. const toOrigin = () => {
  274. markStore.setting.uiSetting["answer.paper.scale"] = 1;
  275. };
  276. const greaterThanOneScale = computed(() => {
  277. return markStore.setting.uiSetting["answer.paper.scale"] > 1;
  278. });
  279. const lessThanOneScale = computed(() => {
  280. return markStore.setting.uiSetting["answer.paper.scale"] < 1;
  281. });
  282. const equalOneScale = computed(() => {
  283. return markStore.setting.uiSetting["answer.paper.scale"] === 1;
  284. });
  285. let eyecareMode = $ref(Number(window.localStorage.getItem("eyecareMode", "0")));
  286. eyecareMode = eyecareMode === 1 ? 0 : 1;
  287. toEyecare();
  288. function toEyecare() {
  289. const classList = Array.from(document.body.classList);
  290. const eyecareCls = "eyecare-mode";
  291. if (eyecareMode === 1) {
  292. eyecareMode = 0;
  293. document.body.classList = classList.filter((item) => item !== eyecareCls);
  294. } else {
  295. eyecareMode = 1;
  296. document.body.classList = [...classList, eyecareCls];
  297. }
  298. window.localStorage.setItem("eyecareMode", eyecareMode);
  299. }
  300. // 阅卷模式切换
  301. const questionModalName = computed(() => {
  302. return markStore.setting.questionModel === "SINGLE"
  303. ? "阅全部题目"
  304. : "按小题阅卷";
  305. });
  306. const toSwitchQuestionModal = () => {
  307. const qname =
  308. markStore.setting.questionModel === "SINGLE" ? "阅全部题目" : "按小题阅卷";
  309. Modal.confirm({
  310. title: "操作警告",
  311. content: `确定切换为${qname}吗?`,
  312. async onOk() {
  313. markStore.setting.questionModel =
  314. markStore.setting.questionModel === "SINGLE" ? "MULTI" : "SINGLE";
  315. await updateUISetting(
  316. markStore.setting.mode,
  317. markStore.setting.uiSetting,
  318. markStore.setting.questionModel
  319. );
  320. window.location.reload();
  321. },
  322. });
  323. };
  324. function clearLatestTagOfCurrentTask() {
  325. if (!markStore.currentTask?.markResult) return;
  326. markStore.currentTask.markResult.markerTagList.splice(-1);
  327. }
  328. function clearAllTagsOfCurrentTask() {
  329. if (!markStore.currentTask?.markResult) return;
  330. markStore.currentTask.markResult.markerTagList = [];
  331. }
  332. function chooseSpecialTag(tagName: string, tagType: string) {
  333. if (markStore.currentSpecialTag === tagName) {
  334. markStore.currentSpecialTag = undefined;
  335. markStore.currentSpecialTagType = undefined;
  336. } else {
  337. markStore.currentSpecialTag = tagName;
  338. markStore.currentSpecialTagType = tagType;
  339. markStore.currentScore = undefined;
  340. }
  341. }
  342. function keyListener(event: KeyboardEvent) {
  343. if (event.key === "+") {
  344. toMagnify();
  345. } else if (event.key === "-") {
  346. toMinify();
  347. }
  348. }
  349. onMounted(() => {
  350. document.addEventListener("keydown", keyListener);
  351. });
  352. onUnmounted(() => {
  353. document.removeEventListener("keydown", keyListener);
  354. });
  355. </script>