Mark.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. <template>
  2. <div class="my-container">
  3. <mark-header @should-reload-history="shouldReloadFunc" />
  4. <div class="tw-flex tw-gap-1">
  5. <mark-history :should-reload="shouldReloadHistory" />
  6. <mark-body @error="removeBrokenTask" />
  7. <mark-board-track
  8. v-if="showMarkBoardTrack"
  9. @submit="saveTaskToServer"
  10. @allZeroSubmit="allZeroSubmit"
  11. />
  12. <mark-board-key-board
  13. v-if="showMarkBoardKeyBoard"
  14. @submit="saveTaskToServer"
  15. @allZeroSubmit="allZeroSubmit"
  16. />
  17. <mark-board-mouse
  18. v-if="showMarkBoardMouse"
  19. @submit="saveTaskToServer"
  20. @allZeroSubmit="allZeroSubmit"
  21. />
  22. </div>
  23. </div>
  24. <AnswerModal />
  25. <PaperModal />
  26. <MinimapModal />
  27. <AllPaperModal />
  28. <SheetViewModal />
  29. <SpecialTagModal />
  30. </template>
  31. <script lang="ts">
  32. import { computed, defineComponent, onMounted, ref, watch } from "vue";
  33. import {
  34. clearMarkTask,
  35. getGroup,
  36. getSetting,
  37. getStatus,
  38. getTask,
  39. saveTask,
  40. updateUISetting,
  41. } from "@/api/markPage";
  42. import {
  43. findCurrentTaskMarkResult,
  44. removeCurrentMarkResult,
  45. removeOldPreviousMarkResult,
  46. store,
  47. } from "./store";
  48. import MarkHeader from "./MarkHeader.vue";
  49. import MarkBody from "./MarkBody.vue";
  50. import { useTimers } from "@/setups/useTimers";
  51. import MarkHistory from "./MarkHistory.vue";
  52. import MarkBoardTrack from "./MarkBoardTrack.vue";
  53. import { ModeEnum, Setting } from "@/types";
  54. import MarkBoardKeyBoard from "./MarkBoardKeyBoard.vue";
  55. import MarkBoardMouse from "./MarkBoardMouse.vue";
  56. import { isEmpty, isNumber } from "lodash";
  57. import { message } from "ant-design-vue";
  58. import AnswerModal from "./AnswerModal.vue";
  59. import PaperModal from "./PaperModal.vue";
  60. import MinimapModal from "./MinimapModal.vue";
  61. import AllPaperModal from "./AllPaperModal.vue";
  62. import SheetViewModal from "./SheetViewModal.vue";
  63. import SpecialTagModal from "./SpecialTagModal.vue";
  64. export default defineComponent({
  65. name: "Mark",
  66. components: {
  67. MarkHeader,
  68. MarkBody,
  69. MarkHistory,
  70. MarkBoardTrack,
  71. MarkBoardKeyBoard,
  72. MarkBoardMouse,
  73. AnswerModal,
  74. PaperModal,
  75. MinimapModal,
  76. AllPaperModal,
  77. SheetViewModal,
  78. SpecialTagModal,
  79. },
  80. setup: () => {
  81. const { addInterval } = useTimers();
  82. async function updateMarkTask() {
  83. const settingRes = await clearMarkTask();
  84. }
  85. async function updateSetting() {
  86. const settingRes = await getSetting();
  87. // 初次使用时,重置并初始化uisetting
  88. if (isEmpty(settingRes.data.uiSetting)) {
  89. settingRes.data.uiSetting = {
  90. "answer.paper.scale": 1,
  91. "score.board.collapse": false,
  92. "normal.mode": "keyboard",
  93. } as Setting["uiSetting"];
  94. }
  95. store.setting = settingRes.data;
  96. }
  97. async function updateStatus() {
  98. const res = await getStatus();
  99. store.status = res.data;
  100. }
  101. async function updateGroups() {
  102. const res = await getGroup();
  103. store.groups = res.data;
  104. }
  105. async function updateTask() {
  106. const res = await getTask();
  107. if (res.data.libraryId) {
  108. store.tasks.push(res.data);
  109. if (!store.historyOpen) {
  110. // 回评中,不能替换task
  111. // TODO: 疑似替换多次引起重新渲染
  112. if (store.currentTask?.studentId !== store.tasks[0].studentId)
  113. store.currentTask = store.tasks[0];
  114. }
  115. // 如果是评完后,再取到的任务,则此时要更新一下status
  116. if (store.status.totalCount - store.status.markedCount === 0) {
  117. await updateStatus();
  118. }
  119. } else {
  120. store.message = res.data.message;
  121. }
  122. }
  123. // 5秒更新一次tasks
  124. addInterval(() => {
  125. // console.log("get task", store.tasks);
  126. if (store.tasks.length < (store.setting.prefetchCount ?? 3)) {
  127. // 回看打开时,停止取任务
  128. if (!store.historyOpen) updateTask();
  129. }
  130. }, 5 * 1000);
  131. // TODO: 后续改掉,不需要
  132. addInterval(() => {
  133. updateStatus();
  134. }, 5 * 60 * 1000);
  135. onMounted(async () => {
  136. await updateMarkTask();
  137. await updateSetting();
  138. await updateStatus();
  139. await updateGroups();
  140. await updateTask();
  141. });
  142. watch(
  143. () => [store.setting.uiSetting, store.setting.mode],
  144. () => {
  145. updateUISetting(store.setting.mode, store.setting.uiSetting);
  146. },
  147. { deep: true }
  148. );
  149. // 切换currentTask的同时,切换currentMarkResult
  150. watch(
  151. () => store.currentTask,
  152. () => {
  153. // 回评切换任务,先删除之前回评任务的markResult
  154. if (store.historyOpen) {
  155. removeOldPreviousMarkResult();
  156. }
  157. store.currentMarkResult = findCurrentTaskMarkResult();
  158. // 重置当前选择的quesiton和score
  159. store.currentQuestion = undefined;
  160. store.currentScore = undefined;
  161. }
  162. );
  163. const showMarkBoardTrack = computed(() => {
  164. return store.setting.mode === ModeEnum.TRACK;
  165. });
  166. const showMarkBoardKeyBoard = computed(() => {
  167. return (
  168. store.setting.mode === ModeEnum.COMMON &&
  169. store.setting.uiSetting["normal.mode"] === "keyboard"
  170. );
  171. });
  172. const showMarkBoardMouse = computed(() => {
  173. return (
  174. store.setting.mode === ModeEnum.COMMON &&
  175. store.setting.uiSetting["normal.mode"] === "mouse"
  176. );
  177. });
  178. const removeBrokenTask = () => {
  179. removeCurrentMarkResult();
  180. store.currentTask = undefined;
  181. if (!store.historyOpen) {
  182. store.tasks.shift();
  183. } else {
  184. store.currentTask = store.historyTasks[0];
  185. }
  186. };
  187. const shouldReloadHistory = ref(0);
  188. const shouldReloadFunc = () => {
  189. shouldReloadHistory.value = Date.now();
  190. };
  191. const allZeroSubmit = async () => {
  192. const markResult = findCurrentTaskMarkResult();
  193. if (!markResult) return;
  194. markResult.markerScore = 0;
  195. const ss = new Array(store.currentTask?.questionList.length);
  196. markResult.scoreList = ss.fill(0);
  197. markResult.trackList = [];
  198. await saveTaskToServer();
  199. };
  200. const saveTaskToServer = async () => {
  201. const markResult = findCurrentTaskMarkResult();
  202. if (!markResult) return;
  203. if (
  204. markResult.scoreList.length !==
  205. store.currentTask?.questionList.length ||
  206. !markResult.scoreList.every((s) => isNumber(s))
  207. ) {
  208. console.error("markResult格式不正确,缺少分数");
  209. return;
  210. }
  211. if (
  212. markResult.scoreList.length !==
  213. store.currentTask?.questionList.length &&
  214. markResult.scoreList.every((s) => isNumber(s))
  215. ) {
  216. // 轨迹回评普通打分时,有分数无轨迹,导致markResult的scoreList计算不准确
  217. if (store.currentTask?.questionList.every((q) => isNumber(q.score))) {
  218. let question;
  219. for (const q of store.currentTask?.questionList) {
  220. const valid = markResult.trackList.some(
  221. (t) =>
  222. q.mainNumber === t.mainNumber && q.subNumber === t.subNumber
  223. );
  224. if (!valid) {
  225. question = q;
  226. break;
  227. }
  228. }
  229. if (question) {
  230. message.error({
  231. content: `${question.mainNumber}-${question.subNumber} 没有轨迹。`,
  232. duration: 5,
  233. });
  234. } else {
  235. message.error({ content: "少有人见过的错误" });
  236. }
  237. } else {
  238. message.error({ content: "还有题目没有评分。", duration: 5 });
  239. }
  240. return;
  241. }
  242. if (store.setting.mode !== ModeEnum.TRACK) {
  243. markResult.trackList = [];
  244. }
  245. if (store.setting.forceSpecialTag) {
  246. if (
  247. markResult.trackList.length === 0 &&
  248. markResult.specialTagList.length === 0
  249. ) {
  250. message.error({
  251. content: "强制标记已开启,请至少使用一个标记。",
  252. duration: 5,
  253. });
  254. return;
  255. }
  256. }
  257. console.log("save task to server");
  258. const mkey = "save_task_key";
  259. message.loading({ content: "保存评卷任务...", key: mkey });
  260. const res = (await saveTask()) as any;
  261. updateStatus();
  262. if (res.data.success && store.currentTask) {
  263. message.success({ content: "保存成功", key: mkey, duration: 2 });
  264. if (!store.historyOpen) {
  265. removeCurrentMarkResult();
  266. store.currentTask = undefined;
  267. store.tasks.shift();
  268. } else {
  269. shouldReloadHistory.value = Date.now();
  270. }
  271. } else {
  272. console.log(res.data.message);
  273. message.error({ content: res.data.message, key: mkey, duration: 10 });
  274. }
  275. };
  276. return {
  277. store,
  278. updateTask,
  279. allZeroSubmit,
  280. saveTaskToServer,
  281. showMarkBoardTrack,
  282. showMarkBoardKeyBoard,
  283. showMarkBoardMouse,
  284. shouldReloadHistory,
  285. shouldReloadFunc,
  286. removeBrokenTask,
  287. };
  288. },
  289. });
  290. </script>
  291. <style scoped>
  292. .my-container {
  293. width: 100%;
  294. }
  295. a {
  296. color: #42b983;
  297. }
  298. label {
  299. margin: 0 0.5em;
  300. font-weight: bold;
  301. }
  302. code {
  303. background-color: #eee;
  304. padding: 2px 4px;
  305. border-radius: 4px;
  306. color: #304455;
  307. }
  308. </style>