MarkBoardInspect.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. <template>
  2. <div
  3. v-if="store.currentTask"
  4. class="mark-board-track-container"
  5. :class="[store.isScoreBoardCollapsed ? 'hide' : 'show']"
  6. >
  7. <div class="top-container tw-flex-shrink-0 tw-flex tw-items-center">
  8. <div class="tw-flex tw-flex-col tw-flex-1 tw-text-center">
  9. <div class="tw-flex tw-justify-center">
  10. <img
  11. src="../../mark/images/totalscore.png"
  12. style="width: 13px; height: 16px"
  13. />
  14. </div>
  15. <div>试卷总分</div>
  16. </div>
  17. <div class="tw-flex-1" style="font-size: 40px">
  18. {{ markerScore > 0 ? markerScore : 0 }}
  19. </div>
  20. </div>
  21. <!-- <div v-if="groups" class="question-list tw-flex-grow tw-overflow-auto">
  22. <template
  23. v-for="({ groupNumber, selectiveIndex }, index) in groups"
  24. :key="index"
  25. > -->
  26. <div v-if="groups" class="question-list tw-flex-grow tw-overflow-auto">
  27. <template v-for="({ groupNumber }, index) in groups" :key="index">
  28. <div class="question-item tw-mb-4 tw-bg-white tw-p-4 tw-pl-5 tw-pr-3">
  29. <!-- <div v-if="selectiveIndex" class="question-item-select-group">
  30. {{ selectiveIndex }}
  31. </div> -->
  32. <div
  33. class="tw-flex tw-justify-between tw-place-items-center hover:tw-bg-gray-200"
  34. @mouseover="willAddFocusTrack(groupNumber, undefined, undefined)"
  35. @mouseleave="removeFocusTrack"
  36. >
  37. <span class="secondary-text">分组 {{ groupNumber }}</span>
  38. <input
  39. class="tw-my-auto"
  40. title="打回"
  41. type="checkbox"
  42. :checked="groupChecked(groupNumber)"
  43. @click="groupClicked(groupNumber)"
  44. />
  45. </div>
  46. <div v-if="questions">
  47. <template v-for="(question, index2) in questions" :key="index2">
  48. <div
  49. v-if="question.groupNumber === groupNumber"
  50. class="question tw-flex tw-place-items-center tw-mb-1 tw-font-bold hover:tw-bg-gray-200"
  51. :class="{
  52. uncalculate: question.uncalculate,
  53. 'is-rejected': question.rejected,
  54. }"
  55. @mouseover="
  56. willAddFocusTrack(
  57. undefined,
  58. question.mainNumber,
  59. question.subNumber
  60. )
  61. "
  62. @mouseleave="removeFocusTrack"
  63. >
  64. <a-tooltip placement="left">
  65. <template #title>
  66. <span>{{
  67. `选做题组${question.selectiveIndex}-方向${question.selectivePart}未计入总分`
  68. }}</span>
  69. </template>
  70. <MinusCircleFilled class="uncalculate-icon" />
  71. </a-tooltip>
  72. <span v-if="!!question.questionName" class="question-title">
  73. {{ question.questionName }}
  74. </span>
  75. <span v-else class="question-title">
  76. {{ question.title }} {{ question.mainNumber }}-{{
  77. question.subNumber
  78. }}
  79. </span>
  80. <span class="tw-text-center question-score">
  81. {{
  82. question.score === -1 || question.hasSetUnselective
  83. ? "未选做"
  84. : question.score || 0
  85. }}
  86. </span>
  87. <!-- <input
  88. :disabled="question.score === -1"
  89. title="打回"
  90. type="checkbox"
  91. :checked="questionChecked(question)"
  92. @change="questionCheckChanged(question)"
  93. /> -->
  94. <input
  95. title="打回"
  96. type="checkbox"
  97. :checked="questionChecked(question)"
  98. @change="questionCheckChanged(question)"
  99. />
  100. </div>
  101. </template>
  102. </div>
  103. </div>
  104. </template>
  105. </div>
  106. <div class="tw-flex tw-flex-shrink-0 tw-justify-center">
  107. <qm-button
  108. v-if="
  109. store.currentTask.inspectTime && store.currentTask.inspectTime > 0
  110. "
  111. type="primary"
  112. class="full-width-btn undo-btn"
  113. @click="reject"
  114. >
  115. 打回
  116. </qm-button>
  117. <template v-else-if="checkedQuestions.length === 0">
  118. <qm-button
  119. v-if="hasScrollToBottom"
  120. type="primary"
  121. class="full-width-btn"
  122. @click="inspect"
  123. >
  124. 复核
  125. </qm-button>
  126. <a-tooltip v-else placement="top">
  127. <template #title>请先浏览至试卷底部</template>
  128. <div style="width: 100%">
  129. <a-button
  130. disabled
  131. class="full-width-btn"
  132. style="display: block; width: 100%"
  133. >
  134. 复核
  135. </a-button>
  136. </div>
  137. </a-tooltip>
  138. </template>
  139. <qm-button
  140. v-else
  141. type="primary"
  142. class="full-width-btn undo-btn"
  143. @click="reject"
  144. >打回</qm-button
  145. >
  146. </div>
  147. </div>
  148. <review-return-dialog
  149. v-model:visible="reviewReturnVisible"
  150. @confirmReturn="onConfirmReturn"
  151. />
  152. </template>
  153. <script setup lang="ts">
  154. import type { Question } from "@/types";
  155. import { message, Modal } from "ant-design-vue";
  156. import { MinusCircleFilled } from "@ant-design/icons-vue";
  157. import { reactive, watch } from "vue";
  158. import { store } from "@/store/store";
  159. import {
  160. addFocusTrack,
  161. removeFocusTrack,
  162. } from "@/features/mark/use/focusTracks";
  163. import ReviewReturnDialog from "@/features/library/inspect/ReviewReturnDialog.vue";
  164. const willAddFocusTrack = (
  165. groupNumber: number | undefined,
  166. mainNumber: number | undefined,
  167. subNumber: string | undefined
  168. ) => {
  169. // if (!isMultComments) {
  170. addFocusTrack(groupNumber, mainNumber, subNumber);
  171. // }
  172. };
  173. const { hasScrollToBottom } = defineProps<{
  174. isMultComments: boolean;
  175. hasScrollToBottom: boolean;
  176. }>();
  177. const emit = defineEmits(["inspect", "reject"]);
  178. let checkedQuestions: Question[] = reactive([]);
  179. let reviewReturnVisible = $ref(false);
  180. watch(
  181. () => store.currentTask,
  182. () => {
  183. checkedQuestions.splice(0);
  184. }
  185. );
  186. const groups = $computed(() => {
  187. const gs = store.currentTaskEnsured.questionList.reduce((gs, q) => {
  188. if (!gs[q.groupNumber]) {
  189. gs[q.groupNumber] = {
  190. groupNumber: q.groupNumber,
  191. selectiveIndex: q.selectiveIndex,
  192. };
  193. }
  194. return gs;
  195. }, {} as Record<number, { groupNumber: number; selectiveIndex: number | null }>);
  196. return Object.values(gs);
  197. });
  198. const questions = $computed(() => {
  199. const qs = store.currentTaskEnsured.questionList;
  200. return qs;
  201. });
  202. const markerScore = $computed(() => store.currentTaskEnsured.markerScore || 0);
  203. function addToCheckedQuestion(question: Question) {
  204. checkedQuestions.push(question);
  205. }
  206. function removeCheckedQuestion(question: Question) {
  207. const idx = checkedQuestions.indexOf(question);
  208. checkedQuestions.splice(idx, 1);
  209. }
  210. function groupChecked(groupNumber: number) {
  211. return (
  212. checkedQuestions.filter((q) => q.groupNumber === groupNumber).length ===
  213. questions?.filter((q) => q.groupNumber === groupNumber).length
  214. );
  215. }
  216. function questionChecked(question: Question) {
  217. return checkedQuestions.includes(question);
  218. }
  219. function questionCheckChanged(question: Question) {
  220. const checked = questionChecked(question);
  221. if (checked) {
  222. removeCheckedQuestion(question);
  223. } else {
  224. addToCheckedQuestion(question);
  225. }
  226. }
  227. function groupClicked(groupNumber: number) {
  228. if (groupChecked(groupNumber)) {
  229. checkedQuestions
  230. .filter((q) => q.groupNumber === groupNumber)
  231. .forEach((q) => {
  232. const idx = checkedQuestions.indexOf(q);
  233. checkedQuestions.splice(idx, 1);
  234. });
  235. } else {
  236. questions
  237. ?.filter((q) => q.groupNumber === groupNumber)
  238. .forEach((q) => {
  239. if (!questionChecked(q)) checkedQuestions.push(q);
  240. });
  241. }
  242. }
  243. function reject() {
  244. if (checkedQuestions.length === 0) {
  245. void message.warn({ content: "请先选择试题。" });
  246. return;
  247. }
  248. reviewReturnVisible = true;
  249. // emit("reject", checkedQuestions);
  250. }
  251. function inspect() {
  252. if (!store.currentTaskEnsured.selectiveError) {
  253. emit("inspect");
  254. return;
  255. }
  256. Modal.warning({
  257. zIndex: 2000,
  258. title: "选择题合分异常",
  259. content: `该试卷选作题有异常,存在合分不够的情况,请将异常题打回!`,
  260. });
  261. }
  262. function onConfirmReturn(reason: string) {
  263. emit("reject", { questions: checkedQuestions, reason });
  264. }
  265. </script>
  266. <style lang="less" scoped>
  267. .mark-board-track-container {
  268. display: flex;
  269. flex-direction: column;
  270. max-width: 290px;
  271. min-width: 290px;
  272. max-height: calc(100vh - 56px);
  273. z-index: 1001;
  274. transition: margin-right 0.5s;
  275. color: var(--app-small-header-text-color);
  276. padding-bottom: 20px;
  277. }
  278. .mark-board-track-container.show {
  279. margin-right: 0;
  280. }
  281. .mark-board-track-container.hide {
  282. margin-right: -290px;
  283. }
  284. .top-container {
  285. background-color: var(--app-container-bg-color);
  286. height: 86px;
  287. border-radius: 5px;
  288. margin: 20px;
  289. margin-bottom: 0;
  290. color: white;
  291. background-color: var(--app-primary-button-bg-color);
  292. }
  293. .question-list {
  294. padding: 20px;
  295. }
  296. .question-item {
  297. border-radius: 5px;
  298. position: relative;
  299. }
  300. /** 选做题分组 */
  301. .question-item-select-group {
  302. position: absolute;
  303. left: -12px;
  304. top: -12px;
  305. width: 24px;
  306. height: 24px;
  307. background: #d3d9e6;
  308. border-radius: 12px 12px 4px 12px;
  309. color: #435488;
  310. text-align: center;
  311. line-height: 24px;
  312. font-size: 12px;
  313. font-weight: bold;
  314. }
  315. .total-score {
  316. color: var(--app-main-text-color);
  317. font-size: 32px;
  318. }
  319. .question {
  320. min-width: 80px;
  321. background-color: var(--app-container-bg-color);
  322. &.is-rejected {
  323. background-color: yellow;
  324. }
  325. }
  326. .question-title {
  327. flex: 1;
  328. }
  329. .question-score {
  330. flex-basis: 56px;
  331. padding: 0 3px;
  332. }
  333. .question.uncalculate {
  334. position: relative;
  335. }
  336. .question .uncalculate-icon {
  337. display: none;
  338. color: red;
  339. position: absolute;
  340. font-size: 15px;
  341. left: -16px;
  342. top: 0.3em;
  343. }
  344. .question.uncalculate .uncalculate-icon {
  345. display: block;
  346. }
  347. .full-width-btn {
  348. width: 100%;
  349. border-radius: 20px;
  350. }
  351. .undo-btn {
  352. background-color: var(--app-undo-button-bg-color);
  353. border-color: var(--app-undo-button-bg-color);
  354. }
  355. </style>