MarkBoardTrack.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <template>
  2. <div
  3. v-if="store.currentTask"
  4. :style="{ display: store.MarkBoardTrackCollapse ? 'none' : 'block' }"
  5. class="mark-board-track-container"
  6. >
  7. <div>
  8. <h1 class="tw-text-3xl tw-text-center">
  9. 总分:{{ store.currentMarkResult?.markerScore || 0 }}
  10. </h1>
  11. </div>
  12. <div class="tw-mb-2 tw-flex tw-place-content-center">
  13. <qm-button type="primary" shape="round" size="large" @click="submit">
  14. 提交
  15. </qm-button>
  16. </div>
  17. <div
  18. v-if="store.currentTask && store.currentTask.questionList"
  19. class="tw-flex tw-gap-1 tw-flex-wrap tw-justify-between"
  20. >
  21. <template
  22. v-for="(question, index) in store.currentTask?.questionList"
  23. :key="index"
  24. >
  25. <div
  26. @click="chooseQuestion(question)"
  27. class="question tw-rounded tw-p-1"
  28. :class="isCurrentQuestion(question) && 'current-question'"
  29. >
  30. <div style="border-bottom: 1px solid grey">
  31. {{ question.title }} {{ question.mainNumber }}-{{
  32. question.subNumber
  33. }}
  34. </div>
  35. <div class="tw-text-center tw-font-medium tw-text-2xl">
  36. <!-- 特殊的空格符号 -->
  37. {{ question.score ?? " " }}
  38. </div>
  39. </div>
  40. </template>
  41. </div>
  42. <div class="tw-flex tw-gap-1 tw-flex-wrap tw-mt-5">
  43. <div
  44. v-for="(s, i) in questionScoreSteps"
  45. :key="i"
  46. @click="chooseScore(s)"
  47. class="single-score"
  48. :class="isCurrentScore(s) && 'current-score'"
  49. >
  50. {{ s }}
  51. </div>
  52. </div>
  53. <div class="tw-flex tw-justify-between tw-mt-4">
  54. <qm-button
  55. type="primary"
  56. shape="round"
  57. size="large"
  58. :clickTimeout="300"
  59. @click="clearLatestMarkOfCurrentQuetion"
  60. >
  61. 回退
  62. </qm-button>
  63. <qm-button
  64. type="primary"
  65. shape="round"
  66. size="large"
  67. :clickTimeout="300"
  68. @click="clearAllMarksOfCurrentQuetion"
  69. >
  70. 清除本题
  71. </qm-button>
  72. </div>
  73. </div>
  74. </template>
  75. <script lang="ts">
  76. import { Question } from "@/types";
  77. import { isNumber } from "lodash";
  78. import { computed, defineComponent, onMounted, onUnmounted, watch } from "vue";
  79. import { store } from "./store";
  80. import { autoChooseFirstQuestion } from "./use/autoChooseFirstQuestion";
  81. import { message } from "ant-design-vue";
  82. export default defineComponent({
  83. name: "MarkBoardTrack",
  84. emits: ["submit"],
  85. setup(props, { emit }) {
  86. const { chooseQuestion } = autoChooseFirstQuestion();
  87. const questionScoreSteps = computed(() => {
  88. const question = store.currentQuestion;
  89. if (!question) return [];
  90. const remainScore =
  91. Math.round(question.maxScore * 100 - (question.score || 0) * 100) / 100;
  92. const steps = [];
  93. for (
  94. let i = 0;
  95. i <= remainScore;
  96. i = Math.round(i * 100 + question.intervalScore * 100) / 100
  97. ) {
  98. steps.push(i);
  99. }
  100. if (
  101. Math.round(remainScore * 100) %
  102. Math.round(question.intervalScore * 100) !==
  103. 0
  104. ) {
  105. steps.push(remainScore);
  106. }
  107. return steps;
  108. });
  109. function isCurrentQuestion(question: Question) {
  110. return (
  111. store.currentQuestion?.mainNumber === question.mainNumber &&
  112. store.currentQuestion?.subNumber === question.subNumber
  113. );
  114. }
  115. watch(
  116. () => store.currentQuestion,
  117. () => {
  118. store.currentScore = undefined;
  119. }
  120. );
  121. function isCurrentScore(score: number) {
  122. return store.currentScore === score;
  123. }
  124. function chooseScore(score: number) {
  125. store.currentScore = score;
  126. }
  127. let keyPressTimestamp = 0;
  128. let keys: string[] = [];
  129. function numberKeyListener(event: KeyboardEvent) {
  130. // console.log(event);
  131. if (!store.currentQuestion) return;
  132. function indexOfCurrentQuestion() {
  133. return store.currentTask?.questionList.findIndex(
  134. (q) =>
  135. q.mainNumber === store.currentQuestion?.mainNumber &&
  136. q.subNumber === store.currentQuestion.subNumber
  137. );
  138. }
  139. // tab 循环答题列表
  140. if (event.key === "Tab") {
  141. const idx = indexOfCurrentQuestion() as number;
  142. if (idx >= 0 && store.currentTask) {
  143. const len = store.currentTask.questionList.length;
  144. chooseQuestion(store.currentTask.questionList[(idx + 1) % len]);
  145. event.preventDefault();
  146. }
  147. return;
  148. }
  149. if (event.timeStamp - keyPressTimestamp > 1 * 1000) {
  150. keys = [];
  151. }
  152. keyPressTimestamp = event.timeStamp;
  153. keys.push(event.key);
  154. if (isNaN(parseFloat(keys.join("")))) {
  155. keys = [];
  156. }
  157. if (event.key === "Escape") {
  158. keys = [];
  159. }
  160. const score = parseFloat(keys.join(""));
  161. if (isNumber(score) && questionScoreSteps.value.includes(score)) {
  162. chooseScore(score);
  163. }
  164. }
  165. onMounted(() => {
  166. document.addEventListener("keydown", numberKeyListener);
  167. });
  168. onUnmounted(() => {
  169. document.removeEventListener("keydown", numberKeyListener);
  170. });
  171. function clearLatestMarkOfCurrentQuetion() {
  172. if (!store.currentMarkResult || !store.currentQuestion) return;
  173. const ts = store.currentMarkResult?.trackList.filter(
  174. (q) =>
  175. q.mainNumber === store.currentQuestion?.mainNumber &&
  176. q.subNumber === store.currentQuestion?.subNumber
  177. );
  178. if (ts.length === 0) return;
  179. const maxNumber = Math.max(...ts.map((q) => q.number));
  180. const idx = store.currentMarkResult.trackList.findIndex(
  181. (q) =>
  182. q.mainNumber === store.currentQuestion?.mainNumber &&
  183. q.subNumber === store.currentQuestion?.subNumber &&
  184. q.number === maxNumber
  185. );
  186. store.removeScoreTracks = store.currentMarkResult.trackList.splice(
  187. idx,
  188. 1
  189. );
  190. }
  191. function clearAllMarksOfCurrentQuetion() {
  192. if (!store.currentMarkResult || !store.currentQuestion) return;
  193. store.removeScoreTracks = store.currentMarkResult?.trackList.filter(
  194. (q) =>
  195. q.mainNumber === store.currentQuestion?.mainNumber &&
  196. q.subNumber === store.currentQuestion?.subNumber
  197. );
  198. store.currentMarkResult.trackList = store.currentMarkResult?.trackList.filter(
  199. (q) =>
  200. !(
  201. q.mainNumber === store.currentQuestion?.mainNumber &&
  202. q.subNumber === store.currentQuestion?.subNumber
  203. )
  204. );
  205. }
  206. function submit() {
  207. const errors: any = [];
  208. store.currentTask?.questionList.forEach((question, index) => {
  209. if (!isNumber(question.score)) {
  210. errors.push({
  211. question,
  212. index,
  213. error: `${question.mainNumber}-${question.subNumber} 没有赋分不能提交。`,
  214. });
  215. }
  216. });
  217. if (errors.length === 0) {
  218. emit("submit");
  219. } else {
  220. console.log(errors);
  221. message.error({
  222. content: errors.map((e: any) => `${e.error}`).join("\n"),
  223. duration: 10,
  224. });
  225. }
  226. }
  227. return {
  228. store,
  229. isCurrentQuestion,
  230. chooseQuestion,
  231. isCurrentScore,
  232. chooseScore,
  233. questionScoreSteps,
  234. clearLatestMarkOfCurrentQuetion,
  235. clearAllMarksOfCurrentQuetion,
  236. submit,
  237. };
  238. },
  239. });
  240. </script>
  241. <style scoped>
  242. .mark-board-track-container {
  243. max-width: 250px;
  244. min-width: 250px;
  245. border-left: 1px solid grey;
  246. padding-left: 6px;
  247. padding-right: 6px;
  248. max-height: calc(100vh - 41px);
  249. overflow: scroll;
  250. }
  251. .question {
  252. min-width: 100px;
  253. border: 1px solid grey;
  254. }
  255. .current-question {
  256. border: 1px solid yellowgreen;
  257. background-color: lightblue;
  258. }
  259. .single-score {
  260. width: 30px;
  261. height: 30px;
  262. display: grid;
  263. place-content: center;
  264. border: 1px solid black;
  265. border-radius: 5px;
  266. }
  267. .current-score {
  268. border: 1px solid yellowgreen;
  269. background-color: lightblue;
  270. }
  271. </style>