MarkBoardKeyBoard.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <template>
  2. <div v-if="store.currentTask" class="mark-board-track-container">
  3. <div>
  4. <h1 class="tw-text-3xl tw-text-center" @click="toggleKeyMouse">
  5. 键盘给分
  6. </h1>
  7. </div>
  8. <div>
  9. <h1 class="tw-text-3xl tw-text-center">
  10. 总分:{{ store.currentMarkResult?.markerScore || 0 }}
  11. </h1>
  12. </div>
  13. <div v-if="store.currentTask && store.currentTask.questionList">
  14. <template
  15. v-for="(question, index) in store.currentTask?.questionList"
  16. :key="index"
  17. >
  18. <div
  19. @click="chooseQuestion(question)"
  20. class="question tw-rounded tw-p-1 tw-mb-2"
  21. :class="isCurrentQuestion(question) && 'current-question'"
  22. >
  23. <div class="tw-flex tw-justify-between">
  24. <div>
  25. <div>
  26. {{ question.title }} {{ question.mainNumber }}-{{
  27. question.subNumber
  28. }}
  29. </div>
  30. <div class="tw-text-center tw-text-3xl">
  31. <!-- {{ showScore(question) }} -->
  32. {{ question.__updateScore }}
  33. </div>
  34. </div>
  35. <div>
  36. <div class="tw-text-center">
  37. 间隔{{ question.intervalScore }}分
  38. </div>
  39. <div class="tw-flex tw-text-3xl" style="width: 80px">
  40. <span class="tw-flex-1">{{ question.minScore }}</span>
  41. <span class="tw-flex-1">~</span>
  42. <span class="tw-flex-1 tw-text-center">{{
  43. question.maxScore
  44. }}</span>
  45. </div>
  46. </div>
  47. </div>
  48. </div>
  49. </template>
  50. </div>
  51. </div>
  52. </template>
  53. <script lang="ts">
  54. import { Question } from "@/types";
  55. import { isNumber } from "lodash";
  56. import {
  57. computed,
  58. defineComponent,
  59. onMounted,
  60. onUnmounted,
  61. reactive,
  62. watch,
  63. } from "vue";
  64. import { store } from "./store";
  65. import { keyMouse } from "./use/keyboardAndMouse";
  66. import { autoChooseFirstQuestion } from "./use/autoChooseFirstQuestion";
  67. import { message } from "ant-design-vue";
  68. export default defineComponent({
  69. name: "MarkBoardKeyBoard",
  70. emits: ["submit"],
  71. setup(props, { emit }) {
  72. const { toggleKeyMouse } = keyMouse();
  73. const { chooseQuestion } = autoChooseFirstQuestion();
  74. const questionScoreSteps = computed(() => {
  75. const question = store.currentQuestion;
  76. if (!question) return [];
  77. const remainScore = question.maxScore - (question.score || 0);
  78. const steps = [];
  79. for (
  80. let i = 0;
  81. i <= remainScore;
  82. i = (i * 10 + question.intervalScore * 10) / 10
  83. ) {
  84. steps.push(i);
  85. }
  86. if ((remainScore * 10) % (question.intervalScore * 10) !== 0) {
  87. steps.push(remainScore);
  88. }
  89. return steps;
  90. });
  91. function isCurrentQuestion(question: Question) {
  92. return (
  93. store.currentQuestion?.mainNumber === question.mainNumber &&
  94. store.currentQuestion?.subNumber === question.subNumber
  95. );
  96. }
  97. let keyPressTimestamp = 0;
  98. let keys = reactive([] as Array<String>);
  99. function numberKeyListener(event: KeyboardEvent) {
  100. // console.log(event);
  101. if (!store.currentQuestion || !store.currentTask) return;
  102. function indexOfCurrentQuestion() {
  103. return store.currentTask?.questionList.findIndex(
  104. (q) =>
  105. q.mainNumber === store.currentQuestion?.mainNumber &&
  106. q.subNumber === store.currentQuestion.subNumber
  107. );
  108. }
  109. // 处理Enter跳下一题或submit
  110. if (event.key === "Enter") {
  111. // if (!isNumber(store.currentQuestion.score)) {
  112. // // 当前题赋分不通过,Enter无效
  113. // return;
  114. // }
  115. // 有bug,移除当前题,再回来就出错了
  116. if (keys.length === 0) {
  117. message.error({ content: "请输入分数", duration: 100 });
  118. console.log("请输入分数");
  119. return;
  120. }
  121. const score = parseFloat(keys.join(""));
  122. if (!isNumber(score)) {
  123. message.error({ content: "非数字输入", duration: 10 });
  124. console.log("非数字输入");
  125. return;
  126. }
  127. if (!questionScoreSteps.value.includes(score)) {
  128. message.error({ content: "输入的分数不在有效间隔内", duration: 10 });
  129. console.log("输入的分数不在有效间隔内");
  130. return;
  131. }
  132. store.currentQuestion.score = score;
  133. const idx = indexOfCurrentQuestion() as number;
  134. if (idx + 1 === store.currentTask?.questionList.length) {
  135. submit();
  136. } else {
  137. chooseQuestion(store.currentTask.questionList[idx + 1]);
  138. }
  139. keys = [];
  140. return;
  141. }
  142. if (event.key === "ArrowLeft") {
  143. const idx = indexOfCurrentQuestion() as number;
  144. if (idx > 0) {
  145. chooseQuestion(store.currentTask.questionList[idx - 1]);
  146. }
  147. keys = [];
  148. return;
  149. }
  150. if (event.key === "ArrowRight") {
  151. const idx = indexOfCurrentQuestion() as number;
  152. if (idx < store.currentTask.questionList.length - 1) {
  153. chooseQuestion(store.currentTask.questionList[idx + 1]);
  154. }
  155. keys = [];
  156. return;
  157. }
  158. // 处理回退删除分数
  159. if (event.key === "Backspace") {
  160. if (keys.length > 0) {
  161. keys.splice(keys.length - 1, 1);
  162. } else {
  163. return;
  164. }
  165. }
  166. if (event.key === "Escape") {
  167. keys = [];
  168. }
  169. // TODO: 确认数字按键的间隔
  170. // if (event.timeStamp - keyPressTimestamp > 1.5 * 1000) {
  171. // keys = [];
  172. // }
  173. // keyPressTimestamp = event.timeStamp;
  174. // 此时不再接受任何非数字键
  175. if (".0123456789".includes(event.key)) {
  176. keys.push(event.key);
  177. }
  178. if (isNaN(parseFloat(keys.join("")))) {
  179. keys = [];
  180. }
  181. // FIXME: for update. 得想个更好的办法来解决不能更新的问题
  182. store.currentQuestion.__updateScore = keys.join("");
  183. // console.log(
  184. // keys,
  185. // keys.join(""),
  186. // isCurrentQuestion(store.currentQuestion) && keys.length > 0
  187. // );
  188. }
  189. onMounted(() => {
  190. document.addEventListener("keydown", numberKeyListener);
  191. });
  192. onUnmounted(() => {
  193. document.removeEventListener("keydown", numberKeyListener);
  194. });
  195. const showScore = (question: Question) => {
  196. return isCurrentQuestion(question) && keys.length > 0
  197. ? keys.join("")
  198. : question.score;
  199. };
  200. function submit() {
  201. const errors: any = [];
  202. store.currentTask?.questionList.forEach((question, index) => {
  203. if (!isNumber(question.score)) {
  204. errors.push({ question, index, error: "没有赋分不能提交" });
  205. }
  206. });
  207. if (errors.length === 0) {
  208. emit("submit");
  209. } else {
  210. console.log(errors);
  211. message.error({
  212. content: errors
  213. .map((e: any) => `${e.index + 1}、${e.error}`)
  214. .join("\n"),
  215. duration: 10,
  216. });
  217. }
  218. }
  219. return {
  220. store,
  221. toggleKeyMouse,
  222. isCurrentQuestion,
  223. chooseQuestion,
  224. keys,
  225. showScore,
  226. questionScoreSteps,
  227. submit,
  228. };
  229. },
  230. });
  231. </script>
  232. <style scoped>
  233. .mark-board-track-container {
  234. max-width: 250px;
  235. min-width: 250px;
  236. border-left: 1px solid grey;
  237. padding-left: 6px;
  238. padding-right: 6px;
  239. }
  240. .question {
  241. min-width: 80px;
  242. border: 1px solid grey;
  243. }
  244. .current-question {
  245. border: 1px solid yellowgreen;
  246. background-color: lightblue;
  247. }
  248. .single-score {
  249. width: 30px;
  250. height: 30px;
  251. display: grid;
  252. place-content: center;
  253. border: 1px solid black;
  254. border-radius: 5px;
  255. }
  256. .current-score {
  257. border: 1px solid yellowgreen;
  258. background-color: lightblue;
  259. }
  260. </style>