Explorar el Código

feat: 复核校验

zhangjie hace 9 meses
padre
commit
c45f701425

+ 28 - 9
src/render/ap/review.ts

@@ -10,26 +10,26 @@ import {
   ReviewTaskListItem,
   ReviewProgressResult,
   ReviewTaskSaveParams,
-  ReviewTaskHistoryResult,
+  ReviewTaskHistoryParams,
   ReviewWarningTaskExportParams,
 } from "./types/review";
 
 // 复核任务
-export const reviewTaskList = (
+export const reviewTask = (
   data: ExamSubjectParams
-): Promise<ReviewTaskListItem[]> =>
+): Promise<ReviewTaskListItem> =>
   request({
-    url: "/api/admin/check/inspect/task/get",
+    url: "/api/admin/check/assigned/task/get",
     method: "post",
     data,
   });
 
-// 复核校验进度状态
+// todo:复核校验进度状态
 export const reviewProgress = (
   data: ExamSubjectParams
 ): Promise<ReviewProgressResult> =>
   request({
-    url: "/api/admin/check/omr/arbitrate/get",
+    url: "/api/admin/check/assigned/overview",
     method: "post",
     data,
   });
@@ -37,9 +37,9 @@ export const reviewProgress = (
 // 复核校验任务历史
 export const reviewTaskHistory = (
   data: ExamParams
-): Promise<ReviewTaskHistoryResult> =>
+): Promise<ReviewTaskListItem[]> =>
   request({
-    url: "/api/admin/check/omr/arbitrate/history",
+    url: "/api/admin/check/assigned/task/history",
     method: "post",
     data,
   });
@@ -49,9 +49,19 @@ export const reviewTaskSave = (
   data: ReviewTaskSaveParams
 ): Promise<RequestActionResult> =>
   request({
-    url: "/api/admin/check/omr/arbitrate/save",
+    url: "/api/admin/check/assigned/task/save",
     method: "post",
     data,
+    headers: {
+      "Content-Type": "application/json;charset=UTF-8",
+    },
+  });
+
+// 复核校验任务释放
+export const reviewTaskRelease = (): Promise<{ success: boolean }> =>
+  request({
+    url: "/api/admin/check/assigned/task/release",
+    method: "post",
   });
 
 // 复核校验重置
@@ -63,6 +73,15 @@ export const reviewTaskReset = (
     method: "post",
     data,
   });
+// 复核校验重置状态
+export const reviewTaskResetStatus = (
+  data: ExamSubjectParams
+): Promise<{ synching: boolean }> =>
+  request({
+    url: "/api/admin/check/assigned/reset/status",
+    method: "post",
+    data,
+  });
 
 // 复核校验异常导出
 export const reviewWarningTaskExport = (

+ 1 - 0
src/render/ap/types/common.ts

@@ -17,6 +17,7 @@ export type ExamPageParams = PageParams<ExamParams>;
 export interface ExamSubjectParams {
   examId: number;
   subjectCode: string;
+  password: string;
 }
 export type ExamSubjectPageParams = PageParams<ExamSubjectParams>;
 

+ 8 - 3
src/render/ap/types/review.ts

@@ -34,13 +34,18 @@ export interface ReviewProgressResult {
 
 export interface ReviewTaskSaveParams {
   id: number;
-  result: boolean;
+  assignedSuspect: boolean;
+}
+export interface ReviewTaskHistoryParams {
+  id: number;
+  subjectCode: string;
+  pageSize: number;
 }
 
-export type ReviewTaskHistoryResult = PageResult<ReviewTaskListItem>;
+export type ReviewExportType = "STUDENT_CODE" | "EXAM_ROOM";
 
 export interface ReviewWarningTaskExportParams {
   examId: number;
   subjectCode: string;
-  type: "student" | "room";
+  type: ReviewExportType;
 }

+ 2 - 0
src/render/store/modules/review/index.ts

@@ -4,12 +4,14 @@ import { ReviewTaskListItem } from "@/ap/types/review";
 interface ReviewState {
   tabKey: "review" | "history";
   curTask: ReviewTaskListItem | null;
+  waitTask: ReviewTaskListItem | null;
 }
 
 export const useReviewStore = defineStore("review", {
   state: (): ReviewState => ({
     tabKey: "review",
     curTask: null,
+    waitTask: null,
   }),
 
   getters: {

+ 3 - 2
src/render/views/AbsentCheck/CheckAction.vue

@@ -123,6 +123,7 @@ import { message } from "ant-design-vue";
 import { useUserStore, useDataCheckStore } from "@/store";
 import { AbsentCheckListFilter, ImportType } from "@/ap/types/absentCheck";
 import { ExamStatus } from "@/ap/types/dataCheck";
+import { ReviewExportType } from "@/ap/types/review";
 
 import {
   absentCheckStudentExport,
@@ -253,12 +254,12 @@ function onExport(type: string) {
   exportTypeDialogRef.value?.open();
 }
 
-async function onExportConfirm(type: "student" | "room") {
+async function onExportConfirm(type: ReviewExportType) {
   if (downloading.value) return;
 
   setLoading(true);
   const func =
-    type === "student" ? absentCheckStudentExport : absentCheckRoomExport;
+    type === "STUDENT_CODE" ? absentCheckStudentExport : absentCheckRoomExport;
 
   const res = await func(searchModel).catch((e: Error) => {
     message.error(e.message || "下载失败,请重新尝试!");

+ 0 - 2
src/render/views/AbsentCheck/ImportTypeDialog.vue

@@ -97,8 +97,6 @@ const { start: startLoopSync, stop: stopLoopSync } = useLoop(
 
 async function checkUploadStatus() {
   const res = await absentImportStatus(userStore.curExam.id).catch(() => {});
-  console.log(res);
-
   if (res) {
     if (!res.synching) {
       stopLoopSync();

+ 3 - 2
src/render/views/DataCheck/CheckAction.vue

@@ -185,6 +185,7 @@ import { message } from "ant-design-vue";
 import { useUserStore, useDataCheckStore } from "@/store";
 import { DataCheckListFilter, ExamStatus } from "@/ap/types/dataCheck";
 import { SubjectItem } from "@/ap/types/base";
+import { ReviewExportType } from "@/ap/types/review";
 import { dataCheckStudentExport, dataCheckRoomExport } from "@/ap/dataCheck";
 import { examStatusSave } from "@/ap/absentCheck";
 import useDictOption from "@/hooks/dictOption";
@@ -376,12 +377,12 @@ function onExport(type: string) {
   exportTypeDialogRef.value?.open();
 }
 
-async function onExportConfirm(type: "student" | "room") {
+async function onExportConfirm(type: ReviewExportType) {
   if (downloading.value) return;
 
   setLoading(true);
   const func =
-    type === "student" ? dataCheckStudentExport : dataCheckRoomExport;
+    type === "STUDENT_CODE" ? dataCheckStudentExport : dataCheckRoomExport;
   const params =
     actionType.value === "common" ? searchModel : customSearchModel;
 

+ 7 - 2
src/render/views/RecognizeCheck/RecognizeImage.vue

@@ -1,9 +1,9 @@
 <template>
   <div ref="arbitrateImgRef" class="arbitrate-img" @scroll="onImgScroll">
-    <img :src="imgSrc" alt="扫描结果" @load="onImgLoad" />
+    <img :src="imgUri" alt="扫描结果" @load="onImgLoad" />
   </div>
   <div ref="imgThumbRef" class="arbitrate-img-thumb">
-    <img :src="imgSrc" alt="扫描结果" />
+    <img :src="imgUri" alt="扫描结果" />
     <div
       class="arbitrate-img-area"
       v-ele-move-directive.prevent.stop="{
@@ -19,6 +19,7 @@
 <script setup lang="ts">
 import { computed, ref, reactive, onMounted } from "vue";
 import { vEleMoveDirective } from "@/directives/eleMove";
+import { getFileUrl } from "@/utils/tool";
 
 defineOptions({
   name: "RecognizeImage",
@@ -30,6 +31,10 @@ const props = defineProps<{
 
 const arbitrateImgRef = ref();
 
+const imgUri = computed(() => {
+  return getFileUrl(props.imgSrc);
+});
+
 // img
 function updateImgAreaSize() {
   const imgBoxDom = arbitrateImgRef.value as HTMLDivElement;

+ 2 - 1
src/render/views/Review/ExportTypeDialog.vue

@@ -26,6 +26,7 @@
 </template>
 
 <script setup lang="ts">
+import { ReviewExportType } from "@/ap/types/review";
 import useModal from "@/hooks/useModal";
 
 defineOptions({
@@ -38,7 +39,7 @@ defineExpose({ open, close });
 
 const emit = defineEmits(["confirm"]);
 
-function seleted(type: "student" | "room") {
+function seleted(type: ReviewExportType) {
   emit("confirm", type);
 }
 </script>

+ 23 - 7
src/render/views/Review/ResetConfirmDialog.vue

@@ -27,8 +27,9 @@ import { message } from "ant-design-vue";
 
 import useModal from "@/hooks/useModal";
 import { useUserStore } from "@/store";
-import { reviewTaskReset } from "@/ap/review";
+import { reviewTaskReset, reviewTaskResetStatus } from "@/ap/review";
 import { SubjectItem } from "@/ap/types/base";
+import useLoop from "@/hooks/useLoop";
 
 defineOptions({
   name: "ResetConfirmDialog",
@@ -67,21 +68,36 @@ const rules: FormRules<"password"> = {
 };
 
 /* confirm */
+const { start: startLoopSync, stop: stopLoopSync } = useLoop(checkStatus, 1000);
+
+async function checkStatus() {
+  const res = await reviewTaskResetStatus({
+    examId: userStore.curExam.id,
+    subjectCode: props.subject?.subjectCode || "",
+  }).catch(() => {});
+  if (res) {
+    if (!res.synching) {
+      stopLoopSync();
+      message.success("操作成功!");
+      close();
+    }
+  } else {
+    stopLoopSync();
+    message.error("操作错误,请重新尝试!");
+  }
+}
+
 async function confirm() {
   const valid = await formRef.value?.validate().catch(() => false);
   if (!valid) return;
 
-  // TODO:校验管理员密码是否正确
-
   const res = await reviewTaskReset({
     examId: userStore.curExam.id,
     subjectCode: props.subject?.subjectCode || "",
+    password: formData.password,
   }).catch(() => false);
   if (!res) return;
 
-  message.success("操作成功");
-
-  emit("confirm", formData);
-  close();
+  startLoopSync();
 }
 </script>

+ 48 - 23
src/render/views/Review/ReviewAction.vue

@@ -34,7 +34,11 @@
             filter-option
             style="width: 140px"
           ></a-select>
-          <a-button class="m-l-8px" type="primary" @click="onSearch"
+          <a-button
+            class="m-l-8px"
+            type="primary"
+            :disabled="!searchCourseCode"
+            @click="onSearch"
             >搜索</a-button
           >
         </a-collapse-panel>
@@ -50,7 +54,11 @@
             filter-option
             style="width: 140px"
           ></a-select>
-          <a-button class="m-l-8px" :disabled="downloading" @click="onExport">
+          <a-button
+            class="m-l-8px"
+            :disabled="downloading || !exportCourseCode"
+            @click="onExport"
+          >
             导出
           </a-button>
         </a-collapse-panel>
@@ -74,7 +82,12 @@
             filter-option
             style="width: 140px"
           ></a-select>
-          <a-button class="m-l-8px" type="primary" danger @click="onReset"
+          <a-button
+            class="m-l-8px"
+            type="primary"
+            danger
+            :disabled="!resetCourseCode"
+            @click="onReset"
             >重置</a-button
           >
         </a-collapse-panel>
@@ -124,13 +137,6 @@
           </div>
         </div>
       </div>
-      <div class="history-footer">
-        <SimplePagination
-          :total="pagination.total"
-          :page-size="pagination.pageSize"
-          @change="toPage"
-        />
-      </div>
     </div>
   </div>
 
@@ -139,7 +145,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from "vue";
+import { ref, watch } from "vue";
 import { getSubjectList } from "@/ap/base";
 import {
   FilterFilled,
@@ -152,7 +158,7 @@ import { message } from "ant-design-vue";
 import { showConfirm } from "@/utils/uiUtils";
 
 import { reviewWarningTaskExport, reviewTaskHistory } from "@/ap/review";
-import { ReviewTaskListItem } from "@/ap/types/review";
+import { ReviewTaskListItem, ReviewExportType } from "@/ap/types/review";
 import { SubjectItem } from "@/ap/types/base";
 
 import useTable from "@/hooks/useTable";
@@ -166,6 +172,7 @@ defineOptions({
   name: "ReviewAction",
 });
 const emit = defineEmits(["search", "reset", "mark"]);
+defineExpose({ switchTab });
 
 const userStore = useUserStore();
 const reviewStore = useReviewStore();
@@ -179,8 +186,11 @@ async function switchTab(key: "review" | "history") {
   reviewStore.setInfo({ tabKey: key });
 
   if (key === "history") {
-    await toPage(1);
+    reviewStore.setInfo({ waitTask: reviewStore.curTask });
+    await getHistory();
     setCurTask(0);
+  } else {
+    reviewStore.setInfo({ curTask: reviewStore.waitTask, waitTask: null });
   }
 }
 
@@ -190,22 +200,26 @@ async function getCourses() {
   const res = await getSubjectList({ examId: userStore.curExam.id });
   courses.value = res || [];
 }
+getCourses();
 
 const searchCourseCode = ref("");
 const exportCourseCode = ref("");
 const resetCourseCode = ref("");
-const result = ref(1);
-const historyResult = ref(1);
+const result = ref();
+const historyResult = ref();
 
 // history
 const curHistoryTaskIndex = ref(0);
-const { dataList, pagination, loading, getList, toPage, setPageSize } =
-  useTable<ReviewTaskListItem>(
-    reviewTaskHistory,
-    { examId: userStore.curExam.id },
-    false
-  );
-setPageSize(30);
+const dataList = ref<ReviewTaskListItem[]>([]);
+async function getHistory() {
+  if (!reviewStore.waitTask?.id) return;
+  const res = await reviewTaskHistory({
+    id: reviewStore.waitTask?.id,
+    subjectCode: searchCourseCode.value,
+    pageSize: 30,
+  });
+  dataList.value = res || [];
+}
 
 function setCurTask(index: number) {
   curHistoryTaskIndex.value = index;
@@ -242,7 +256,7 @@ function onExport() {
   if (downloading.value) return;
   exportTypeDialogRef.value?.open();
 }
-async function onExportConfirm(type: "student" | "room") {
+async function onExportConfirm(type: ReviewExportType) {
   if (downloading.value) return;
 
   setLoading(true);
@@ -258,4 +272,15 @@ async function onExportConfirm(type: "student" | "room") {
   if (!res) return;
   message.success("导出成功!");
 }
+
+// watch
+watch(
+  () => reviewStore.curTask,
+  (val) => {
+    result.value = undefined;
+    if (reviewStore.tabKey === "history") {
+      historyResult.value = reviewStore.curTask.assignedSuspect;
+    }
+  }
+);
 </script>

+ 7 - 4
src/render/views/Review/ReviewImage.vue

@@ -11,17 +11,20 @@
         :data-index="index + 1"
         @scroll="onImgScroll"
       >
-        <!-- TODO:测试数据 -->
-        <img src="@/assets/imgs/paper.jpg" :alt="`第${index + 1}页`" />
-        <!-- <img :src="item.pages[0].sheetUri" :alt="`第${index + 1}页`" /> -->
+        <img
+          :src="getFileUrl(item.pages[0].sheetUri)"
+          :alt="`第${index + 1}页`"
+        />
       </div>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { useReviewStore } from "@/store";
 import { computed } from "vue";
+import { useReviewStore } from "@/store";
+
+import { getFileUrl } from "@/utils/tool";
 
 defineOptions({
   name: "ReviewImage",

+ 41 - 108
src/render/views/Review/index.vue

@@ -3,7 +3,7 @@
     <div class="review-head">
       <h2 v-if="reviewStore.curTask" class="review-title">
         {{ reviewStore.curTask.examNumber }} - {{ reviewStore.curTask.name }} -
-        CET4
+        {{ userStore.curExam?.name }}
       </h2>
 
       <div class="review-stat">
@@ -16,7 +16,11 @@
       </div>
 
       <div class="review-prev">
-        <a-button v-if="reviewStore.tabKey === 'review'" :disabled="loading">
+        <a-button
+          v-if="reviewStore.tabKey === 'review'"
+          :disabled="loading"
+          @click="onPrevTask"
+        >
           <template #icon><ArrowLeftOutlined /></template> 上一个
         </a-button>
       </div>
@@ -38,7 +42,12 @@
     </div>
 
     <!-- action -->
-    <ReviewAction @mark="onMark" @reset="onReset" @search="onSearch" />
+    <ReviewAction
+      ref="reviewActionRef"
+      @mark="onMark"
+      @reset="onReset"
+      @search="onSearch"
+    />
 
     <!-- reset confirm -->
     <ResetConfirmDialog
@@ -50,7 +59,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref, reactive, onMounted, watch } from "vue";
+import { ref, reactive, onBeforeUnmount, onMounted } from "vue";
 import {
   CheckCircleFilled,
   CloseCircleFilled,
@@ -59,10 +68,11 @@ import {
 import { message } from "ant-design-vue";
 
 import {
-  reviewTaskList,
+  reviewTask,
   reviewTaskReset,
   reviewTaskSave,
   reviewProgress,
+  reviewTaskRelease,
 } from "@/ap/review";
 import { ReviewTaskListItem } from "@/ap/types/review";
 import { SubjectItem } from "@/ap/types/base";
@@ -82,16 +92,6 @@ defineOptions({
   name: "review",
 });
 
-// 任务进度
-const progress = ref({
-  finishCount: 0,
-  todoCount: 0,
-});
-async function updateProgress() {
-  const res = await reviewProgress({ examId: userStore.curExam.id });
-  progress.value = res || {};
-}
-
 // 任务相关
 const searchModel = reactive({
   examId: userStore.curExam.id,
@@ -99,74 +99,44 @@ const searchModel = reactive({
 });
 
 const { loading, setLoading } = useLoading();
-const dataList = ref<ReviewTaskListItem[]>([]);
-const curTaskIndex = ref(0);
-
-async function getTasks() {
-  // TODO:假设一次取3个
-  const res = await reviewTaskList(searchModel);
-  dataList.value = res || [];
-}
-
-function setCurTask() {
-  reviewStore.setInfo({ curTask: dataList.value[curTaskIndex.value] });
-}
 
 async function getNextTask() {
-  if (curTaskIndex.value >= dataList.value.length - 1) {
-    await getTasks();
-    if (!dataList.value.length) {
-      message.error("没有下一个了!");
-      reviewStore.setInfo({ curTask: null });
-      return;
-    }
-    curTaskIndex.value = 0;
-    setCurTask();
+  const res = await reviewTask(searchModel);
+  reviewStore.setInfo({ curTask: res || null });
+
+  if (!reviewStore.curTask) {
+    message.error("没有下一个了!");
     return;
   }
-
-  curTaskIndex.value++;
-  setCurTask();
 }
 
-async function getPrevTask() {
-  if (loading.value) return;
-  loading.value = true;
-
-  if (curTaskIndex.value <= 0) {
-    let result = true;
-    await getTasks().catch(() => {
-      result = false;
-    });
-    if (!result) return;
-
-    loading.value = false;
-    if (!dataList.value.length) {
-      message.error("没有上一个了!");
-      reviewStore.setInfo({ curTask: null });
-      return;
-    }
-    curTaskIndex.value = dataList.value.length - 1;
-    setCurTask();
-    return;
-  }
+const reviewActionRef = ref();
+async function onPrevTask() {
+  reviewActionRef.value?.switchTab("history");
+}
 
-  curTaskIndex.value--;
-  setCurTask();
+// 任务进度
+const progress = ref({
+  finishCount: 0,
+  todoCount: 0,
+});
+async function updateProgress() {
+  const res = await reviewProgress(searchModel);
+  progress.value = res || {};
 }
 
 // actions
-async function onMark(result: boolean) {
+async function onMark(assignedSuspect: boolean) {
   if (!reviewStore.curTask) return;
 
   if (loading.value) return;
   loading.value = true;
 
   try {
-    await reviewTaskSave({ id: reviewStore.curTask.id, result });
+    await reviewTaskSave({ id: reviewStore.curTask.id, assignedSuspect });
     reviewStore.setInfo({
       curTask: Object.assign({}, reviewStore.curTask, {
-        assignedSuspect: result,
+        assignedSuspect,
       }),
     });
 
@@ -180,10 +150,8 @@ async function onMark(result: boolean) {
 
 async function onSearch(subjectCode: string) {
   searchModel.subjectCode = subjectCode;
-  await getTasks();
-
-  curTaskIndex.value = 0;
-  setCurTask();
+  await getNextTask();
+  await updateProgress();
 }
 
 const resetConfirmDialogRef = ref();
@@ -197,45 +165,10 @@ function onResetConfirm() {
   onSearch("");
 }
 
-// watch
-watch(
-  () => reviewStore.tabKey,
-  (val) => {
-    if (val === "review") {
-      reviewStore.setInfo({
-        curTask: dataList.value[curTaskIndex.value] || null,
-      });
-    }
-  }
-);
-
 onMounted(() => {
-  // test
-  dataList.value = [
-    {
-      id: 1,
-      name: "考生2",
-      studentCode: "360080241304012",
-      subjectCode: "SX0001",
-      subjectName: "数学",
-      examNumber: "360080241304012",
-      assignedSuspect: false,
-      breachCount: 1,
-      pages: [
-        {
-          index: 1,
-          uri: "",
-        },
-        {
-          index: 2,
-          uri: "",
-        },
-      ],
-    },
-  ];
-  reviewStore.setInfo({
-    curTask: dataList.value[curTaskIndex.value] || null,
-    tabKey: "review",
-  });
+  reviewStore.resetInfo();
+});
+onBeforeUnmount(async () => {
+  await reviewTaskRelease();
 });
 </script>