index.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <template>
  2. <div class="review">
  3. <div class="review-head">
  4. <a-row align="center">
  5. <a-col :span="9">
  6. <h2 class="review-title">419872222786601 - 考生2 - CET4</h2>
  7. </a-col>
  8. <a-col :span="6">
  9. <div class="review-stat">
  10. <p class="color-success">
  11. <CheckCircleFilled />已处理:{{ progress.finishCount }}
  12. </p>
  13. <p class="color-error">
  14. <CloseCircleFilled />未处理:{{ progress.todoCount }}
  15. </p>
  16. </div>
  17. </a-col>
  18. <a-col :span="9">
  19. <a-button v-if="reviewStore.tabKey === 'review'" :disabled="loading">
  20. <template #icon><ArrowLeftOutlined /></template> 上一个
  21. </a-button>
  22. </a-col>
  23. </a-row>
  24. </div>
  25. <!-- body -->
  26. <div class="review-body">
  27. <ReviewImage />
  28. </div>
  29. <!-- action -->
  30. <ReviewAction @mark="onMark" @reset="onReset" @search="onSearch" />
  31. <!-- reset confirm -->
  32. <ResetConfirmDialog
  33. ref="resetConfirmDialogRef"
  34. :subject="resetSubject"
  35. @confirm="onResetConfirm"
  36. />
  37. <!-- ReviewMarkPan -->
  38. <ReviewMarkPan v-if="reviewStore.curTask" @mark="onMark" />
  39. </div>
  40. </template>
  41. <script setup lang="ts">
  42. import {
  43. computed,
  44. ref,
  45. reactive,
  46. onMounted,
  47. onBeforeUnmount,
  48. watch,
  49. } from "vue";
  50. import {
  51. CheckCircleFilled,
  52. CloseCircleFilled,
  53. ArrowLeftOutlined,
  54. } from "@ant-design/icons-vue";
  55. import { message } from "ant-design-vue";
  56. import { reviewTaskList, reviewTaskReset, reviewTaskSave } from "@/ap/review";
  57. import { ReviewTaskListItem } from "@/ap/types/review";
  58. import { SubjectItem } from "@/ap/types/base";
  59. import { useUserStore, useReviewStore } from "@/store";
  60. import useLoading from "@/hooks/useLoading";
  61. import ReviewAction from "./ReviewAction.vue";
  62. import ReviewImage from "./ReviewImage.vue";
  63. import ResetConfirmDialog from "./ResetConfirmDialog.vue";
  64. import ReviewMarkPan from "./ReviewMarkPan.vue";
  65. const userStore = useUserStore();
  66. const reviewStore = useReviewStore();
  67. defineOptions({
  68. name: "review",
  69. });
  70. const searchModel = reactive({
  71. examId: userStore.curExam.id,
  72. subjectCode: "",
  73. });
  74. const { loading, setLoading } = useLoading();
  75. const dataList = ref<ReviewTaskListItem[]>([]);
  76. const curTaskIndex = ref(0);
  77. async function getTasks() {
  78. // TODO:假设一次取3个
  79. const res = await reviewTaskList(searchModel);
  80. dataList.value = res || [];
  81. }
  82. function setCurTask() {
  83. reviewStore.setInfo({ curTask: dataList.value[curTaskIndex.value] });
  84. }
  85. function getNextTask() {
  86. if (curTaskIndex.value >= dataList.value.length - 1) {
  87. await getTasks();
  88. if (!dataList.value.length) {
  89. message.error("没有下一个了!");
  90. reviewStore.setInfo({ curTask: null });
  91. return;
  92. }
  93. curTaskIndex.value = 0;
  94. setCurTask();
  95. return;
  96. }
  97. curTaskIndex.value++;
  98. setCurTask();
  99. }
  100. function getPrevTask() {
  101. if (loading.value) return;
  102. loading.value = true;
  103. if (curTaskIndex.value <= 0) {
  104. let result = true;
  105. await getTasks().catch(() => {
  106. result = false;
  107. });
  108. if (!result) return;
  109. loading.value = false;
  110. if (!dataList.value.length) {
  111. message.error("没有上一个了!");
  112. reviewStore.setInfo({ curTask: null });
  113. return;
  114. }
  115. curTaskIndex.value = dataList.value.length - 1;
  116. setCurTask();
  117. return;
  118. }
  119. curTaskIndex.value--;
  120. setCurTask();
  121. }
  122. // actions
  123. async function onMark(result: boolean) {
  124. if (!reviewStore.curTask) return;
  125. if (loading.value) return;
  126. loading.value = true;
  127. try {
  128. await reviewTaskSave({ id: reviewStore.curTask.id, result });
  129. reviewStore.setInfo({
  130. curTask: Object.assign({}, reviewStore.curTask, {
  131. markStatus: result,
  132. }),
  133. });
  134. if (reviewStore.tabKey === "history") return;
  135. await getNextTask();
  136. } catch (error) {
  137. loading.value = false;
  138. }
  139. }
  140. async function onSearch(subjectCode: string) {
  141. searchModel.subjectCode = subjectCode;
  142. await getTasks();
  143. curTaskIndex.value = 0;
  144. setCurTask();
  145. }
  146. const resetConfirmDialogRef = ref();
  147. const resetSubject = ref<SubjectItem | null>(null);
  148. async function onReset(data: SubjectItem | null) {
  149. resetSubject.value = data;
  150. resetConfirmDialogRef.value?.open();
  151. }
  152. function onResetConfirm() {
  153. onSearch("");
  154. }
  155. // watch
  156. watch(
  157. () => reviewStore.tabKey,
  158. (val) => {
  159. if (val === "review") {
  160. reviewStore.setInfo({
  161. curTask: dataList.value[curTaskIndex.value] || null,
  162. });
  163. }
  164. }
  165. );
  166. // 键盘事件
  167. function registKeyEvent() {
  168. document.addEventListener("keydown", keyEventHandle);
  169. }
  170. function removeKeyEvent() {
  171. document.removeEventListener("keydown", keyEventHandle);
  172. }
  173. function keyEventHandle(e: KeyboardEvent) {
  174. if (e.code === "Enter") {
  175. e.preventDefault();
  176. onMark(true);
  177. return;
  178. }
  179. }
  180. onMounted(() => {
  181. registKeyEvent();
  182. });
  183. onBeforeUnmount(() => {
  184. removeKeyEvent();
  185. });
  186. </script>