zhangjie 9 mesiacov pred
rodič
commit
3434312369

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

@@ -101,7 +101,6 @@ export interface DataCheckOmrFieldEditParams {
   paperNumber: string;
   pageIndex: number;
   field: OmrFiledType;
-  questionIndex: number;
   value: string;
 }
 

+ 187 - 0
src/render/components/ScanImage/RecogEditDialog.vue

@@ -0,0 +1,187 @@
+<template>
+  <a-modal
+    v-model:open="visible"
+    :width="718"
+    :footer="false"
+    :closable="false"
+    :mask="false"
+    :maskClosable="false"
+    wrapClassName="recog-edit-dialog"
+  >
+    <div class="recog-edit">
+      <a-row align="top" :gutter="8" class="m-b-8px">
+        <a-col :span="4">
+          <div class="modal-box">
+            <p class="box-title">{{ recogTitle }}</p>
+            <p class="box-cont">{{ recogTitleDesc }}</p>
+          </div>
+        </a-col>
+        <a-col :span="16">
+          <div class="modal-box modal-origin">
+            <img v-if="recogData.areaSrc" :src="recogData.areaSrc" alt="截图" />
+          </div>
+        </a-col>
+        <a-col :span="4">
+          <div class="modal-box">
+            <p class="box-title">Esc键</p>
+            <p class="box-cont">关闭</p>
+          </div>
+        </a-col>
+      </a-row>
+      <a-row align="top" :gutter="8">
+        <a-col :span="4">
+          <div class="modal-box">
+            <p class="box-title">识别结果</p>
+            <p class="box-cont">{{ recogResult }}</p>
+          </div>
+        </a-col>
+        <a-col :span="16">
+          <div class="modal-box modal-options">
+            <a-button
+              v-for="option in recogData.options"
+              :key="option"
+              :type="selectResult.includes(option) ? 'primary' : 'default'"
+              @click="selectOption(option)"
+              >{{ option }}</a-button
+            >
+          </div>
+        </a-col>
+        <a-col :span="4">
+          <div class="modal-box">
+            <p class="box-title">Enter键</p>
+            <p class="box-cont">保存</p>
+          </div>
+        </a-col>
+      </a-row>
+    </div>
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch } from "vue";
+import { message } from "ant-design-vue";
+import useModal from "@/hooks/useModal";
+import { RecogBlock } from "@/utils/recog/recog";
+
+defineOptions({
+  name: "RecogEditDialog",
+});
+
+/* modal */
+const { visible, open, close } = useModal();
+defineExpose({ open, close });
+
+const props = defineProps<{
+  recogData: RecogBlock;
+}>();
+
+const emit = defineEmits(["confirm"]);
+
+const selectResult = ref([] as string[]);
+
+const titles = {
+  question: "客观题",
+  absent: "缺考",
+  breach: "违纪",
+  paperType: "卷型号",
+};
+const recogTitle = computed(() => {
+  return titles[props.recogData.type];
+});
+
+const recogTitleDesc = computed(() => {
+  if (props.recogData.type === "question") {
+    return `#${props.recogData.index}`;
+  }
+  return "--";
+});
+
+const recogResult = computed(() => {
+  if (props.recogData.type === "question") {
+    return props.recogData.result.join("");
+  }
+  return "";
+});
+
+function selectOption(option: string) {
+  if (!props.recogData) return;
+
+  // 单选直接赋值
+  if (!props.recogData.multiple) {
+    selectResult.value = [option];
+    return;
+  }
+
+  // 多选情况
+  // 空直接赋值,空值与其他互斥
+  if (option === "#") {
+    selectResult.value = ["#"];
+    return;
+  }
+
+  let result = selectResult.value.filter((item) => item !== "#");
+  if (result.includes(option)) {
+    result = result.filter((item) => item !== option);
+  } else {
+    result.push(option);
+  }
+  // 保证result的顺序和options的顺序是一致的
+  selectResult.value = props.recogData.options.filter((item) =>
+    result.includes(item)
+  );
+}
+
+// 键盘事件
+function registKeyEvent() {
+  document.addEventListener("keydown", keyEventHandle);
+}
+function removeKeyEvent() {
+  document.removeEventListener("keydown", keyEventHandle);
+}
+
+function keyEventHandle(e: KeyboardEvent) {
+  if (e.code === "Enter") {
+    e.preventDefault();
+    onConfirm();
+    return;
+  }
+}
+
+function onConfirm() {
+  if (!selectResult.value.length) {
+    message.error("请选择答案");
+    return;
+  }
+
+  emit("confirm", selectResult.value);
+  close();
+}
+
+// init
+watch(
+  () => visible.value,
+  (val) => {
+    if (val) {
+      modalOpenHandle();
+    } else {
+      removeKeyEvent();
+    }
+  },
+  {
+    immediate: true,
+  }
+);
+
+watch(
+  () => props.recogData,
+  (val) => {
+    if (!val) return;
+    selectResult.value = [...props.recogData.result];
+  }
+);
+
+function modalOpenHandle() {
+  selectResult.value = [...props.recogData.result];
+  registKeyEvent();
+}
+</script>

+ 36 - 15
src/render/components/ScanImage/index.vue

@@ -45,6 +45,13 @@
 
   <!-- FillAreaSetDialog -->
   <FillAreaSetDialog ref="fillAreaSetDialogRef" @modified="parseRecogBlocks" />
+  <!-- RecogEditDialog -->
+  <RecogEditDialog
+    v-if="curRecogBlock"
+    ref="recogEditDialogRef"
+    :recog-data="curRecogBlock"
+    @confirm="onRecogEditConfirm"
+  />
 </template>
 
 <script setup lang="ts">
@@ -56,28 +63,27 @@ import {
   RightOutlined,
   PictureFilled,
 } from "@ant-design/icons-vue";
-import { computed, nextTick, ref } from "vue";
+import { computed, nextTick, ref, watch } from "vue";
 import { objAssign } from "@/utils/tool";
 import { vEleMoveDirective } from "@/directives/eleMove";
-import { RecognizeArea } from "@/utils/recog/recog";
+import { ImageRecogData, RecogBlock } from "@/utils/recog/recog";
 import { useUserStore } from "@/store";
 
 import FillAreaSetDialog from "./FillAreaSetDialog.vue";
+import RecogEditDialog from "./RecogEditDialog.vue";
+
+import { omit } from "lodash-es";
 
 defineOptions({
   name: "ScanImage",
 });
 
-interface ImageRecogData extends RecognizeArea {
-  [k: string]: any;
-}
-
 const props = defineProps<{
   imgSrc: string;
   recogData: ImageRecogData[];
 }>();
 
-const emit = defineEmits(["area-click", "next", "prev"]);
+const emit = defineEmits(["recogBlockModified", "next", "prev"]);
 
 const userStore = useUserStore();
 
@@ -169,12 +175,8 @@ function getImageSizePos({
 }
 
 // recogBlocks
-interface RecogBlock extends ImageRecogData {
-  fillAreaStyle: Record<string, any>;
-  fillOptionStyles: Array<Record<string, any>>;
-}
-
 const recogBlocks = ref<RecogBlock[]>([]);
+const curRecogBlock = ref<RecogBlock | null>(null);
 function parseRecogBlocks() {
   const imgDom = imgRef.value as HTMLImageElement;
   const rate = imgDom.clientWidth / imgDom.naturalWidth;
@@ -185,6 +187,7 @@ function parseRecogBlocks() {
 
   recogBlocks.value = props.recogData.map((item) => {
     const nitem: RecogBlock = { ...item };
+    nitem.areaImg = "";
 
     nitem.fillAreaStyle = {
       position: "absolute",
@@ -223,10 +226,20 @@ function parseRecogBlocks() {
   });
 }
 
-function onAreaClick(data: RecogBlock) {
-  console.log(data);
+// area click
+const recogEditDialogRef = ref();
+async function onAreaClick(data: RecogBlock) {
+  curRecogBlock.value = data;
+  // TODO:build area src img
+  await nextTick();
+  recogEditDialogRef.value?.open();
+}
+
+async function onRecogEditConfirm(result: string[]) {
+  if (!curRecogBlock.value) return;
 
-  emit("area-click", data);
+  curRecogBlock.value.result = result;
+  emit("recogBlockModified", curRecogBlock.value);
 }
 
 // img action
@@ -273,6 +286,14 @@ const fillAreaSetDialogRef = ref();
 function onSetRecogStyle() {
   fillAreaSetDialogRef.value?.open();
 }
+
+// watch
+watch(
+  () => props.recogData,
+  (val) => {
+    if (val && imgRef.value) parseRecogBlocks();
+  }
+);
 </script>
 
 <style lang="less" scoped>

+ 2 - 1
src/render/store/index.ts

@@ -2,10 +2,11 @@ import { createPinia } from "pinia";
 import { useAppStore } from "./modules/app";
 import { useUserStore } from "./modules/user";
 import { useReviewStore } from "./modules/review";
+import { useDataCheckStore } from "./modules/dataCheck";
 import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
 
 const pinia = createPinia();
 pinia.use(piniaPluginPersistedstate);
 
-export { useAppStore, useUserStore, useReviewStore };
+export { useAppStore, useUserStore, useReviewStore, useDataCheckStore };
 export default pinia;

+ 56 - 0
src/render/store/modules/dataCheck/index.ts

@@ -0,0 +1,56 @@
+import { defineStore } from "pinia";
+import { StudentPage } from "@/views/DataCheck/types";
+import { ImageType } from "@/constants/enumerate";
+import {
+  DataCheckListItem,
+  DataCheckOmrFieldEditParams,
+} from "@/ap/types/dataCheck";
+import { dataCheckOmrFieldEdit } from "@/ap/dataCheck";
+
+interface DataCheckState {
+  imageType: ImageType;
+  curPage: StudentPage | null;
+  curPageIndex: number;
+  curStudent: DataCheckListItem | null;
+}
+
+type UpdateFieldParams = Pick<DataCheckOmrFieldEditParams, "field" | "value">;
+
+export const useDataCheckStore = defineStore("review", {
+  state: (): DataCheckState => ({
+    tabKey: "ORIGIN",
+    curPage: null,
+    curPageIndex: -1,
+    curStudent: null,
+  }),
+
+  getters: {
+    dataInfo(state: DataCheckState): DataCheckState {
+      return { ...state };
+    },
+  },
+
+  actions: {
+    setInfo(partial: Partial<DataCheckState>) {
+      this.$patch(partial);
+    },
+    resetInfo() {
+      this.$reset();
+    },
+    async updateField(data: UpdateFieldParams) {
+      if (!this.curPage || !this.curStudent) return;
+
+      const params = {
+        examId: this.curPage.examId,
+        examNumber: this.curStudent.examNumber,
+        paperNumber: this.curPage.paperIndex,
+        pageIndex: this.curPage.pagePageIndex,
+        ...data,
+      };
+      await dataCheckOmrFieldEdit(params).catch(() => {});
+    },
+  },
+  persist: {
+    storage: sessionStorage,
+  },
+});

+ 71 - 0
src/render/styles/pages.less

@@ -854,3 +854,74 @@
     background-color: @background-color;
   }
 }
+
+// recog-edit-dialog
+.recog-edit-dialog {
+  // left: calc(277px + (100% - 278px - 382px) / 2);
+  top: 60px !important;
+  left: calc(50% - 52px) !important;
+  transform: translateX(-50%);
+  width: 718px !important;
+  height: 216px !important;
+  overflow: hidden !important;
+  border-radius: 12px;
+  box-shadow: 0px 10px 10px 0px rgba(54, 61, 89, 0.2);
+
+  .ant-modal {
+    width: auto !important;
+    transform-origin: 0 !important;
+    top: 0 !important;
+  }
+  .ant-modal-body {
+    padding: 0;
+  }
+}
+.recog-edit {
+  width: 718px;
+  height: 216px;
+  background: #f2f3f5;
+  box-shadow: 0px 10px 10px 0px rgba(54, 61, 89, 0.2);
+  border-radius: 12px;
+  border: 1px solid @background-color;
+  padding: 16px;
+
+  .modal-box {
+    height: 88px;
+    background: #ffffff;
+    border-radius: 6px;
+    border: 1px solid @border-color1;
+    padding: 16px;
+    color: @text-color1;
+
+    .box-title {
+      height: 22px;
+      font-weight: 400;
+      font-size: 14px;
+      color: @text-color3;
+      line-height: 22px;
+      margin-bottom: 6px;
+    }
+    .box-cont {
+      height: 28px;
+      font-weight: 500;
+      font-size: 20px;
+      line-height: 28px;
+    }
+  }
+
+  .modal-origin {
+    background-color: @background-color;
+  }
+
+  .modal-options {
+    line-height: 54px;
+    text-align: center;
+    .ant-btn {
+      margin: 0 8px;
+      padding-left: 8px;
+      padding-right: 8px;
+      text-align: center;
+      min-width: 32px;
+    }
+  }
+}

+ 22 - 5
src/render/utils/recog/recog.ts

@@ -65,6 +65,24 @@ export interface RecogDataType {
 }
 // recognize ---end >
 
+// extends
+
+export interface RecogEditData extends RecognizeArea {
+  areaSrc: string;
+  [k: string]: any;
+}
+export interface ImageRecogData extends RecognizeArea {
+  [k: string]: any;
+}
+
+export interface RecogBlock extends RecognizeArea {
+  areaImg: string;
+  fillAreaStyle: Record<string, any>;
+  fillOptionStyles: Array<Record<string, any>>;
+  [k: string]: any;
+}
+
+// functions
 export function parseRecogData(data: string) {
   if (!data) return null;
   const precogData = window.atob(data);
@@ -78,7 +96,8 @@ const abc = "abcdefghijklmnopqrstuvwxyz".toUpperCase();
 export function parseDetailSize(
   data: RecogDataFillResult,
   type: RecogAreaType,
-  index: number
+  index: number,
+  fillResult: number[]
 ): RecognizeArea {
   const result: RecognizeArea = {
     index,
@@ -118,7 +137,7 @@ export function parseDetailSize(
   result.optionSizes = result.fillPosition.map((item, index) => {
     let filled = false;
     if (type !== "paperType") {
-      filled = data.fill_option[index] === 1;
+      filled = fillResult[index] === 1;
     }
     return {
       x: item.x - result.fillArea.x,
@@ -132,9 +151,7 @@ export function parseDetailSize(
     const options = abc.substring(0, data.fill_position.length).split("");
     // 空用“#”表示
     result.options = ["#", ...options];
-    result.result = data.fill_option
-      .map((item, index) => (item ? abc[index] : ""))
-      .filter((item) => item);
+    result.result = fillResult;
     result.multiple = true;
   }
 

+ 47 - 21
src/render/views/DataCheck/CheckAction.vue

@@ -144,7 +144,11 @@
       <a-collapse-panel key="4">
         <template #header><IdcardFilled />题卡信息 </template>
 
-        <QuestionPanel v-model:questions="questions" :info="questionInfo" />
+        <QuestionPanel
+          v-model:questions="questions"
+          :info="questionInfo"
+          @change="onQuestionsChange"
+        />
       </a-collapse-panel>
     </a-collapse>
   </div>
@@ -154,7 +158,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, reactive, ref } from "vue";
+import { computed, reactive, ref, watch } from "vue";
 import { subjectList } from "@/ap/base";
 import {
   FilterFilled,
@@ -163,7 +167,7 @@ import {
 } from "@ant-design/icons-vue";
 import { message } from "ant-design-vue";
 
-import { useUserStore } from "@/store";
+import { useUserStore, useDataCheckStore } from "@/store";
 import { DataCheckListFilter } from "@/ap/types/dataCheck";
 import { SubjectItem } from "@/ap/types/base";
 import { dataCheckStudentExport, dataCheckRoomExport } from "@/ap/dataCheck";
@@ -178,9 +182,12 @@ import QuestionPanel from "./QuestionPanel.vue";
 defineOptions({
   name: "CheckAction",
 });
-const emit = defineEmits(["search", "imageTypeChange"]);
+
+const emit = defineEmits(["search"]);
 
 const userStore = useUserStore();
+const dataCheckStore = useDataCheckStore();
+
 const { optionList: dataCheckOptions } = useDictOption("DATA_CHECK_TYPE");
 const { optionList: paperTypeOptions } = useDictOption("PAPER_TYPE_STATUS");
 const { optionList: imageTypeOptions } = useDictOption("IMAGE_TYPE");
@@ -226,22 +233,6 @@ const searchDataCheckType = ref();
 const imageType = ref("" as ImageType);
 const actionType = ref("common");
 
-const questionInfo = ref({
-  examNumber: "123456789012345",
-  name: "张散散",
-  examSite: "武汉大学上交所",
-  seatNumber: "05",
-  paperType: "A",
-});
-const questions = ref([]);
-questions.value = "#"
-  .repeat(60)
-  .split("")
-  .map((item) => {
-    const abc = "ABCD";
-    return abc.substring(0, Math.ceil(3 * Math.random() * Math.random()));
-  });
-
 const examNumberCountCont = computed(() => {
   const examNumbers = (searchModel.examNumber || "")
     .split("\n")
@@ -266,7 +257,42 @@ function onCustomSearch() {
 }
 
 function onImageTypeChange() {
-  emit("imageTypeChange", imageType.value);
+  dataCheckStore.setInfo({
+    imageType: imageType.value,
+  });
+}
+
+// question panel
+const questionInfo = computed(() => {
+  return {
+    examNumber: dataCheckStore.curStudent?.examNumber,
+    name: dataCheckStore.curStudent?.name,
+    examSite: dataCheckStore.curStudent?.examSite,
+    seatNumber: dataCheckStore.curStudent?.seatNumber,
+    paperType: dataCheckStore.curStudent?.paperType,
+  };
+});
+
+const questions = ref([]);
+watch(
+  () => dataCheckStore.curPageIndex,
+  (val, oldval) => {
+    if (val !== oldval) {
+      questions.value = [...dataCheckStore.curPage?.question];
+    }
+  }
+);
+
+async function onQuestionsChange() {
+  if (!dataCheckStore.curPage) return;
+  dataCheckStore.curPage.question = [...questions.value];
+
+  await dataCheckStore
+    .updateField({
+      field: "QUESTION",
+      value: questions.value,
+    })
+    .catch(() => {});
 }
 
 // 导出

+ 1 - 8
src/render/views/DataCheck/QuestionPanel.vue

@@ -67,6 +67,7 @@ import { computed, ref, watch } from "vue";
 import { message } from "ant-design-vue";
 import { SwapOutlined } from "@ant-design/icons-vue";
 import { booleanOptionList } from "@/constants/enumerate";
+import { QuestionInfo } from "./types";
 
 import { vEleClickOutsideDirective } from "@/directives/eleClickOutside";
 
@@ -74,14 +75,6 @@ defineOptions({
   name: "QuestionPanel",
 });
 
-interface QuestionInfo {
-  examNumber: string;
-  name: string;
-  examSite: string;
-  seatNumber: number;
-  paperType: string;
-}
-
 const props = withDefaults(
   defineProps<{
     questions: string[];

+ 168 - 49
src/render/views/DataCheck/index.vue

@@ -3,59 +3,166 @@
     <div class="check-menu">
       <div class="check-menu-body">
         <ul>
-          <li v-for="item in dataList" :key="item.id">{{ item.examNumber }}</li>
+          <li
+            v-for="item in studentList"
+            :key="item.id"
+            @click="onSelectStudent(item)"
+          >
+            {{ item.examNumber }}
+          </li>
         </ul>
       </div>
       <div class="check-menu-page">
         <SimplePagination
-          :total="pagination.total"
-          :page-size="pagination.pageSize"
-          @change="toPage"
+          :total="total"
+          :page-size="pageSize"
+          @change="onChangeListPage"
         />
       </div>
     </div>
     <div class="check-body">
-      <ScanImage v-if="curRecogData.length" :recog-data="curRecogData" />
+      <ScanImage
+        v-if="dataCheckStore.curPage && isOriginImage && recogList.length"
+        :key="dataCheckStore.curPage.kid"
+        :img-src="dataCheckStore.curPage.sheetUri"
+        :recog-data="recogList"
+        @prev="onPrevPage"
+        @next="onNextPage"
+        @recog-block-modified="onRecogEditConfirm"
+      />
+      <!-- TODO: slice image show -->
+      <div v-if="dataCheckStore.curPage && !isOriginImage"></div>
     </div>
 
-    <CheckAction />
+    <CheckAction @search="onSearch" />
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted } from "vue";
+import { ref, reactive, onMounted, computed } from "vue";
 import { message } from "ant-design-vue";
 import { CaretLeftOutlined, CaretRightOutlined } from "@ant-design/icons-vue";
 
-import useTable from "@/hooks/useTable";
-
 import { DataCheckListFilter, DataCheckListItem } from "@/ap/types/dataCheck";
 import { dataCheckList } from "@/ap/dataCheck";
-import { useUserStore } from "@/store";
 
 import SimplePagination from "@/components/SimplePagination/index.vue";
 import ScanImage from "@/components/ScanImage/index.vue";
 import CheckAction from "./CheckAction.vue";
-
-import recogSampleData from "@/utils/recog/data.json";
-import { parseDetailSize } from "@/utils/recog/recog";
+import { ImageType } from "@/constants/enumerate";
+import { StudentPage } from "./types";
+import { useDataCheckStore } from "@/store";
+
+import {
+  parseRecogData,
+  parseDetailSize,
+  RecognizeArea,
+  RecogBlock,
+} from "@/utils/recog/recog";
+import { objAssign } from "@/utils/tool";
 
 defineOptions({
   name: "DataCheck",
 });
 
-const userStore = useUserStore();
+const dataCheckStore = useDataCheckStore();
+
+let searchModel = {} as DataCheckListFilter;
+const pageNumber = ref(1);
+const pageSize = ref(10);
+const total = ref(0);
+const studentList = ref<DataCheckListItem[]>([]);
+const dataList = ref<StudentPage[]>([]);
+const loading = ref(false);
+
+async function getList() {
+  loading.value = true;
+  const datas = {
+    ...searchModel,
+    pageNumber: pageNumber.value,
+    pageSize: pageSize.value,
+  };
+  const res = await dataCheckList(datas).catch(() => null);
+  loading.value = false;
+  if (!res) return;
+
+  total.value = res.totalCount;
+  studentList.value = res.result;
+
+  parseStudentPageList(res.result);
+}
 
-const searchModel = reactive({
-  examId: userStore.curExam.id,
-});
+function parseStudentPageList(students: DataCheckListItem[]) {
+  dataList.value = [];
+
+  students.forEach((student, studentIndex) => {
+    student.papers.forEach((paper, paperIndex) => {
+      if (!paper.pages) return;
+      paper.pages.forEach((page, pageIndex) => {
+        dataList.value.push({
+          ...page,
+          pageIndex,
+          paperIndex,
+          studentIndex,
+          studentId: student.id,
+          examId: searchModel.examId,
+          kid: `${student.id}-${studentIndex}-${paperIndex}-${pageIndex}`,
+        });
+      });
+    });
+  });
+}
 
-const { dataList, loading, pagination, getList, toPage, setPageSize } =
-  useTable<DataCheckListItem>(dataCheckList, searchModel, false);
+function onSelectStudent(record: DataCheckListItem) {
+  const pageIndex = dataList.value.findIndex(
+    (item) => item.studentId === record.id
+  );
+  if (pageIndex === -1) return;
 
-setPageSize(30);
+  selectPage(pageIndex);
+}
+
+// imageType
+const isOriginImage = computed(() => {
+  return dataCheckStore.imageType === "ORIGIN";
+});
+
+// table
+async function onChangeListPage(index: number) {
+  pageNumber.value = index;
+  await getList();
+  selectPage(0);
+}
+async function onSearch(datas: DataCheckListFilter) {
+  searchModel = { ...datas };
+  pageNumber.value = 1;
+  await getList();
+  selectPage(0);
+}
 
 // page
+const curStudentInfo = ref({
+  examNumber: "",
+  name: "",
+  examSite: "",
+  seatNumber: "",
+  paperType: "",
+});
+const curPageQuestions = ref<string[]>([]);
+
+function selectPage(index: number) {
+  dataCheckStore.setInfo({
+    curPage: dataList.value[index],
+    curPageIndex: index,
+  });
+
+  if (!dataCheckStore.curPage) return;
+
+  const curStudent = studentList.value[dataCheckStore.curPage.studentIndex];
+  dataCheckStore.setInfo({ curStudent });
+  updateRecogList();
+}
+
 function onPrevPage() {
   const { total, current } = pagination;
   if (current <= 1) {
@@ -66,38 +173,50 @@ function onPrevPage() {
 }
 
 function onNextPage() {
-  const { total, current } = pagination;
-  if (current >= total) {
-    message.error("没有下一页了");
-    return;
+  if (curPageIndex.value >= curPaper.value?.pages?.length) {
   }
-  toPage(current);
-}
 
-const curRecogData = ref([]);
-// 试题
-let index = 0;
-recogSampleData.question.forEach((gGroup) => {
-  gGroup.fill_result.forEach((qRecog) => {
-    qRecog.index = ++index;
+  selectPage(++curPageIndex.value);
+}
 
-    curRecogData.value.push(parseDetailSize(qRecog, "question", qRecog.index));
+// recog data
+const recogList = ref<RecognizeArea[]>([]);
+function updateRecogList() {
+  if (!dataCheckStore.curPage) return;
+
+  const regdata = parseRecogData(dataCheckStore.curPage.recogData);
+  if (!regdata) return;
+
+  recogList.value = [] as RecognizeArea[];
+  let index = 0;
+  regdata.question.forEach((gGroup) => {
+    gGroup.fill_result.forEach((qRecog) => {
+      const result = dataCheckStore.curPage.question[index - 1] || "";
+      qRecog.index = ++index;
+      // TODO: 解析其他数据
+
+      const fileResult = result ? result.split("") : [];
+      const recogItem = parseDetailSize(
+        qRecog,
+        "question",
+        qRecog.index,
+        fileResult
+      );
+      recogList.value.push(recogItem);
+    });
   });
-});
+}
 
-// TODO: 测试数据
-dataList.value = "#"
-  .repeat(30)
-  .split("")
-  .map((item, index) => {
-    return {
-      id: index + 1,
-      examNumber: `3600802404012${index}`,
-      name: `考生名${index}`,
-      studentCode: `36008${index}`,
-      subjectCode: "科目代码",
-      subjectName: "科目名称",
-      seatNumber: "11",
-    };
-  });
+async function onRecogEditConfirm(data: RecogBlock) {
+  if (data.type === "question") {
+    const index = data.index - 1;
+    dataCheckStore.curPage.question.splice(index, 1, data.result.join(""));
+    await dataCheckStore.updateField({
+      field: data.type,
+      value: dataCheckStore.curPage.question,
+    });
+  }
+
+  updateRecogList();
+}
 </script>

+ 19 - 0
src/render/views/DataCheck/types.ts

@@ -0,0 +1,19 @@
+import { PaperPageItem } from "@/ap/types/dataCheck";
+
+export interface StudentPage extends PaperPageItem {
+  examId: number;
+  studentId: number;
+  studentIndex: number;
+  paperIndex: number;
+  pageIndex: number;
+  pagePageIndex: number;
+  kid: string;
+}
+
+export interface QuestionInfo {
+  examNumber: string;
+  name: string;
+  examSite: string;
+  seatNumber: number;
+  paperType: string;
+}

+ 11 - 5
src/render/views/RecognizeCheck/RecognizeArbitrate.vue

@@ -188,7 +188,7 @@ function parseDetails(
 
     // 缺考
     details.push({
-      ...parseDetailSize(recogData.absent.fill_result[0], "absent", 0),
+      ...parseDetailSize(recogData.absent.fill_result[0], "absent", 0, []),
       result1: page.absent ? page.absent[0] : "",
       result2: page.absent ? page.absent[1] : "",
       pageIndex: page.index,
@@ -198,7 +198,7 @@ function parseDetails(
 
     // 违纪
     details.push({
-      ...parseDetailSize(recogData.breach.fill_result[0], "breach", 0),
+      ...parseDetailSize(recogData.breach.fill_result[0], "breach", 0, []),
       result1: page.breach ? page.breach[0] : "",
       result2: page.breach ? page.breach[1] : "",
       pageIndex: page.index,
@@ -208,7 +208,12 @@ function parseDetails(
 
     // 试卷类型
     details.push({
-      ...parseDetailSize(recogData.paperType.fill_result[0], "paperType", 0),
+      ...parseDetailSize(
+        recogData.paperType.fill_result[0],
+        "paperType",
+        0,
+        []
+      ),
       result1: page.paperType ? page.paperType[0] : "",
       result2: page.paperType ? page.paperType[1] : "",
       pageIndex: page.index,
@@ -223,10 +228,11 @@ function parseDetails(
         qRecog.index = ++index;
         const questionResult = page.question[qRecog.index];
         const arbitrate = questionResult && questionResult.length >= 2;
+        const fillResult = arbitrate ? [] : questionResult;
 
         details.push({
-          ...parseDetailSize(qRecog, "question", qRecog.index),
-          result: arbitrate ? [] : questionResult,
+          ...parseDetailSize(qRecog, "question", qRecog.index, fillResult),
+          result,
           result1: questionResult ? questionResult[0] : "",
           result2: questionResult ? questionResult[1] : "",
           pageIndex: page.index,