zhangjie 9 mesiacov pred
rodič
commit
0f90d67697

+ 3 - 0
src/render/ap/dataCheck.ts

@@ -50,6 +50,9 @@ export const dataCheckOmrFieldEdit = (
     url: "/api/admin/scan/answer/omr/field/edit",
     method: "post",
     data,
+    headers: {
+      "Content-Type": "application/json;charset=UTF-8",
+    },
   });
 
 // 修改答题卡识别结果

+ 24 - 9
src/render/ap/types/dataCheck.ts

@@ -7,7 +7,7 @@ export type PaperTypeStatus = "OK" | "BLANK" | "ERROR";
 export interface DataCheckListFilter {
   examId: number;
   status: DataStatus[];
-  examStatus: ExamStatus;
+  examStatus: ExamStatus[];
   examNumber: string;
   studentCode: string;
   name: string;
@@ -25,6 +25,7 @@ export interface DataCheckListFilter {
   incomplete: boolean;
   questionFilled: boolean;
   subjectiveFilled: boolean;
+  hasFilled: boolean;
   withOmrDetail: boolean;
 }
 
@@ -35,22 +36,35 @@ export interface PaperPageItem {
   sheetUri: string;
   sliceUri: string[];
   // withOmrDetail=true时有识别结果,显示最新的判定结果
-  absent: boolean;
-  breach: boolean;
-  paperType: string;
-  question: string[];
-  selective: string[];
-  recogData: string;
+  absent: {
+    type: string;
+    result: boolean;
+  };
+  breach: {
+    type: string;
+    result: boolean;
+  };
+  paperType: {
+    type: string;
+    result: string;
+  };
+  question: {
+    result: string[];
+    type: string;
+  };
+  selective: { type: string; result: string } | null;
+  recogData: string | null;
 }
 
 // 某张缺页时没有id和pages等其他字段
 export interface PaperItem {
-  id?: number;
+  // 某张缺页时没有id和pages等其他字段
+  id: number;
   number: number;
   // 是否人工绑定
   assigned?: boolean;
   // 各页图片与识别结果
-  pages?: PaperPageItem[];
+  pages: PaperPageItem[];
 }
 
 export interface DataCheckListItem {
@@ -100,6 +114,7 @@ export interface DataCheckOmrFieldEditParams {
   examNumber: string;
   paperNumber: string;
   pageIndex: number;
+  subjectCode: string;
   field: OmrFiledType;
   value: string;
 }

+ 13 - 20
src/render/components/ImportBtn/index.vue

@@ -132,14 +132,6 @@ const handleBeforeUpload: UploadProps["beforeUpload"] = async (file) => {
   loading.value = true;
 };
 
-interface UploadResultType {
-  hasError: boolean;
-  failRecords: Array<{
-    msg: string;
-    lineNum: number;
-  }>;
-}
-
 const customRequest: UploadProps["customRequest"] = (option) => {
   const { file, data } = option;
 
@@ -148,6 +140,7 @@ const customRequest: UploadProps["customRequest"] = (option) => {
   Object.entries(paramData).forEach(([k, v]) => {
     formData.append(k, v);
   });
+  formData.append("md5", headers.value.md5);
   formData.append(props.uploadFileAlias, file as File);
   emit("uploading");
 
@@ -161,19 +154,19 @@ const customRequest: UploadProps["customRequest"] = (option) => {
           ? Math.floor((100 * data.loaded) / data.total)
           : 0;
       },
-    }) as Promise<UploadResultType>
+    }) as Promise<Record<string, any>>
   )
     .then((res) => {
       // 所有excel导入的特殊处理
-      if (res.hasError) {
-        const failRecords = res.failRecords;
-        const message = failRecords
-          .map((item) => `第${item.lineNum}行:${item.msg}`)
-          .join("。");
-
-        handleError(message);
-        return;
-      }
+      // if (res.hasError) {
+      //   const failRecords = res.failRecords;
+      //   const message = failRecords
+      //     .map((item) => `第${item.lineNum}行:${item.msg}`)
+      //     .join("。");
+
+      //   handleError(message);
+      //   return;
+      // }
       handleSuccess(res);
     })
     .catch((error: AxiosError<{ message: string }> | null) => {
@@ -190,7 +183,7 @@ function handleError(message: string | undefined) {
   };
   emit("uploadError", result.value);
 }
-function handleSuccess(data: UploadResultType) {
+function handleSuccess(data: Record<string, any>) {
   canUpload.value = false;
   loading.value = false;
   result.value = {
@@ -199,7 +192,7 @@ function handleSuccess(data: UploadResultType) {
   };
   emit("uploadSuccess", {
     ...result.value,
-    data,
+    ...data,
   });
 }
 

+ 12 - 20
src/render/components/ImportDialog/index.vue

@@ -215,14 +215,6 @@ const handleBeforeUpload: UploadProps["beforeUpload"] = async (file) => {
   return false;
 };
 
-interface UploadResultType {
-  hasError: boolean;
-  failRecords: Array<{
-    msg: string;
-    lineNum: number;
-  }>;
-}
-
 const customRequest: UploadProps["customRequest"] = (option) => {
   const { file, data } = option;
 
@@ -244,19 +236,19 @@ const customRequest: UploadProps["customRequest"] = (option) => {
           ? Math.floor((100 * data.loaded) / data.total)
           : 0;
       },
-    }) as Promise<UploadResultType>
+    }) as Promise<Record<string, any>>
   )
     .then((res) => {
       // 所有excel导入的特殊处理
-      if (res.hasError) {
-        const failRecords = res.failRecords;
-        const message = failRecords
-          .map((item) => `第${item.lineNum}行:${item.msg}`)
-          .join("。");
-
-        handleError(message);
-        return;
-      }
+      // if (res.hasError) {
+      //   const failRecords = res.failRecords;
+      //   const message = failRecords
+      //     .map((item) => `第${item.lineNum}行:${item.msg}`)
+      //     .join("。");
+
+      //   handleError(message);
+      //   return;
+      // }
       handleSuccess(res);
     })
     .catch((error: AxiosError<{ message: string }> | null) => {
@@ -273,7 +265,7 @@ function handleError(message: string | undefined) {
   };
   emit("uploadError", result.value);
 }
-function handleSuccess(data: UploadResultType) {
+function handleSuccess(data: Record<string, any>) {
   canUpload.value = false;
   loading.value = false;
   result.value = {
@@ -282,7 +274,7 @@ function handleSuccess(data: UploadResultType) {
   };
   emit("uploadSuccess", {
     ...result.value,
-    data,
+    ...data,
   });
 }
 

+ 2 - 0
src/render/constants/enumerate.ts

@@ -42,3 +42,5 @@ export const booleanOptionList = [
     value: false,
   },
 ];
+
+export const abc = "abcdefghijklmnopqrstuvwxyz".toUpperCase();

+ 3 - 2
src/render/store/modules/dataCheck/index.ts

@@ -53,8 +53,9 @@ export const useDataCheckStore = defineStore("dataCheck", {
       const params = {
         examId: this.curPage.examId,
         examNumber: this.curStudent.examNumber,
-        paperNumber: this.curPage.paperIndex,
-        pageIndex: this.curPage.pagePageIndex,
+        paperNumber: this.curPage.paperNumber,
+        pageIndex: this.curPage.pageIndex,
+        subjectCode: this.curStudent.subjectCode,
         ...data,
       };
       await dataCheckOmrFieldEdit(params).catch(() => {});

+ 2 - 2
src/render/utils/recog/recog.ts

@@ -1,4 +1,5 @@
 import { maxNum, minNum } from "@/utils/tool";
+import { abc } from "@/constants/enumerate";
 
 // recognize ---start >
 // 扫描数据相关
@@ -92,7 +93,6 @@ export function parseRecogData(data: string) {
   return recogData;
 }
 
-const abc = "abcdefghijklmnopqrstuvwxyz".toUpperCase();
 export function parseDetailSize(
   data: RecogDataFillResult,
   type: RecogAreaType,
@@ -151,7 +151,7 @@ export function parseDetailSize(
     const options = abc.substring(0, data.fill_position.length).split("");
     // 空用“#”表示
     result.options = ["#", ...options];
-    result.result = fillResult;
+    result.result = options.filter((r, ind) => fillResult[ind] === 1);
     result.multiple = true;
   }
 

+ 5 - 0
src/render/utils/tool.ts

@@ -324,3 +324,8 @@ export const obj2formData = (obj: Record<string, any>): FormData => {
   });
   return formData;
 };
+
+export function getFileUrl(url: string) {
+  const baseUrl = local.get("baseUrl", "");
+  return `${baseUrl}/file/${url}`;
+}

+ 1 - 4
src/render/views/AbsentCheck/index.vue

@@ -22,13 +22,10 @@
     </div>
     <div class="check-body">
       <ScanImage
-        v-if="dataCheckStore.curPage && isOriginImage && recogList.length"
+        v-if="dataCheckStore.curPage && isOriginImage"
         :key="dataCheckStore.curPage.kid"
-        :img-src="dataCheckStore.curPage.sheetUri"
-        :recog-data="recogList"
         @prev="onPrevPage"
         @next="onNextPage"
-        @recog-block-modified="onRecogEditConfirm"
       />
       <SliceImage v-if="dataCheckStore.curPage && !isOriginImage" />
     </div>

+ 75 - 26
src/render/views/DataCheck/CheckAction.vue

@@ -10,11 +10,12 @@
         <a-form :label-col="{ style: { width: '83px' } }">
           <a-form-item label="科目">
             <a-select
-              v-model:value="searchSubjectCode"
+              v-model:value="searchModel.subjectCode"
               placeholder="请选择"
               :options="courses"
               :field-names="fieldNames"
               filter-option
+              allow-clear
               style="width: 150px"
             ></a-select>
           </a-form-item>
@@ -23,6 +24,8 @@
               v-model:value="searchDataCheckType"
               placeholder="请选择"
               :options="dataCheckOptions"
+              allow-clear
+              @change="dataCheckTypeChange"
             ></a-select>
           </a-form-item>
         </a-form>
@@ -43,7 +46,7 @@
           <a-form-item label="准考证号">
             <div class="exam-number">
               <a-textarea
-                v-model:value="searchModel.examNumber"
+                v-model:value="customSearchModel.examNumber"
                 placeholder="请输入"
                 :auto-size="{ minRows: 1, maxRows: 1 }"
               ></a-textarea>
@@ -53,18 +56,19 @@
           </a-form-item>
           <a-form-item label="姓名">
             <a-input
-              v-model:value="searchModel.name"
+              v-model:value="customSearchModel.name"
               placeholder="请输入"
               style="width: 150px"
             ></a-input>
           </a-form-item>
           <a-form-item label="科目">
             <a-select
-              v-model:value="searchCustomSubjectCode"
+              v-model:value="customSearchModel.subjectCode"
               placeholder="请选择"
               :options="courses"
               :field-names="fieldNames"
               filter-option
+              allow-clear
               style="width: 150px"
             ></a-select>
           </a-form-item>
@@ -72,7 +76,7 @@
             <a-col :span="12">
               <a-form-item label="客观题作答">
                 <a-select
-                  v-model:value="searchModel.questionFilled"
+                  v-model:value="customSearchModel.questionFilled"
                   placeholder="请选择"
                   :options="booleanOptions"
                   style="width: 85px"
@@ -83,7 +87,7 @@
             <a-col :span="12">
               <a-form-item label="主观题作答">
                 <a-select
-                  v-model:value="searchModel.subjectiveFilled"
+                  v-model:value="customSearchModel.subjectiveFilled"
                   placeholder="请选择"
                   :options="booleanOptions"
                   style="width: 100%"
@@ -94,7 +98,7 @@
             <a-col :span="12">
               <a-form-item label="有作答">
                 <a-select
-                  v-model:value="searchModel.subjectiveFilled"
+                  v-model:value="customSearchModel.hasFilled"
                   placeholder="请选择"
                   :options="booleanOptions"
                   style="width: 85px"
@@ -105,7 +109,7 @@
             <a-col :span="12">
               <a-form-item label="试卷类型">
                 <a-select
-                  v-model:value="searchModel.paperTypeStatus"
+                  v-model:value="customSearchModel.paperTypeStatus"
                   placeholder="请选择"
                   :options="paperTypeOptions"
                   style="width: 100%"
@@ -118,11 +122,12 @@
             <a-col :span="16">
               <a-form-item label="缺考">
                 <a-select
-                  v-model:value="searchModel.examStatus"
+                  v-model:value="customExamStatus"
                   placeholder="请选择"
                   :options="examStatusOptions"
                   style="width: 85px"
                   allow-clear
+                  @change="customExamStatusChange"
                 ></a-select>
               </a-form-item>
             </a-col>
@@ -188,6 +193,7 @@ import { ImageType, booleanOptionList } from "@/constants/enumerate";
 
 import ExportTypeDialog from "../Review/ExportTypeDialog.vue";
 import QuestionPanel from "./QuestionPanel.vue";
+import { objModifyAssign } from "@/utils/tool";
 
 defineOptions({
   name: "CheckAction",
@@ -211,13 +217,14 @@ async function getCourses() {
   const res = await getSubjectList({ examId: userStore.curExam.id });
   courses.value = res || [];
 }
+getCourses();
 const fieldNames = { label: "name", value: "code" };
 
 // search
 const initSearchModel = {
   examId: userStore.curExam.id,
   status: "",
-  examStatus: "",
+  examStatus: [],
   examNumber: "",
   studentCode: "",
   name: "",
@@ -235,12 +242,13 @@ const initSearchModel = {
   incomplete: null,
   questionFilled: null,
   subjectiveFilled: null,
+  hasFilled: null,
   withOmrDetail: null,
 };
 const searchModel = reactive<DataCheckListFilter>({ ...initSearchModel });
-const searchSubjectCode = ref("");
-const searchCustomSubjectCode = ref("");
+const customSearchModel = reactive<DataCheckListFilter>({ ...initSearchModel });
 const searchDataCheckType = ref();
+const customExamStatus = ref<ExamStatus>();
 const imageType = ref(dataCheckStore.imageType);
 const actionType = ref("common");
 
@@ -250,26 +258,66 @@ const isSliceImage = computed(() => {
 });
 
 const examNumberCountCont = computed(() => {
-  const examNumbers = (searchModel.examNumber || "")
+  const examNumbers = (customSearchModel.examNumber || "")
     .split("\n")
     .filter((item) => item);
   return `${examNumbers.length}/100`;
 });
 
-function getSearchModel() {
-  return { ...searchModel, subjectCode: searchSubjectCode.value };
+function dataCheckTypeChange() {
+  switch (searchDataCheckType.value) {
+    // 缺考有作答
+    case "1":
+      searchModel.examStatus = ["ABSENT"];
+      searchModel.hasFilled = true;
+      break;
+    // 客观题无作答,主观题有作答;
+    case "2":
+      searchModel.questionFilled = false;
+      searchModel.subjectiveFilled = true;
+      break;
+    // 不缺考,无条码,有作答;(正常或待审核考生,条码为空,有作答)
+    case "3":
+      searchModel.examStatus = ["OK", "UNCHECK"];
+      searchModel.paperTypeStatus = "BLANK";
+      searchModel.hasFilled = true;
+      break;
+    // 不缺考,无条码,无作答;(正常或待审核考生,条码为空,没有作答)
+    case "4":
+      searchModel.examStatus = ["OK", "UNCHECK"];
+      searchModel.paperTypeStatus = "BLANK";
+      searchModel.hasFilled = false;
+      break;
+    // 条码异常(识别的条码没与库中匹配上)
+    case "5":
+      searchModel.paperTypeStatus = "ERROR";
+      break;
+    // 缺考有条码
+    case "6":
+      searchModel.paperTypeStatus = "OK";
+      searchModel.examStatus = ["ABSENT"];
+      break;
+
+    default:
+      objModifyAssign(searchModel, {
+        ...initSearchModel,
+        subjectCode: searchModel.subjectCode,
+      });
+      break;
+  }
 }
-function getCustomSearchModel() {
-  return {
-    ...searchModel,
-    subjectCode: searchCustomSubjectCode.value,
-  };
+
+function customExamStatusChange() {
+  customSearchModel.examStatus = customExamStatus.value
+    ? [customExamStatus.value]
+    : [];
 }
+
 function onSearch() {
-  emit("search", getSearchModel());
+  emit("search", searchModel);
 }
 function onCustomSearch() {
-  emit("search", getCustomSearchModel());
+  emit("search", customSearchModel);
 }
 
 function onImageTypeChange() {
@@ -294,19 +342,20 @@ watch(
   () => dataCheckStore.curPageIndex,
   (val, oldval) => {
     if (val !== oldval) {
-      questions.value = [...dataCheckStore.curPage?.question];
+      if (!dataCheckStore.curPage || !dataCheckStore.curPage.question) return;
+      questions.value = [...dataCheckStore.curPage.question.result];
     }
   }
 );
 
 async function onQuestionsChange() {
   if (!dataCheckStore.curPage) return;
-  dataCheckStore.curPage.question = [...questions.value];
+  dataCheckStore.curPage.question.result = [...questions.value];
 
   await dataCheckStore
     .updateField({
       field: "QUESTION",
-      value: questions.value,
+      value: JSON.stringify(dataCheckStore.curPage.question),
     })
     .catch(() => {});
 }
@@ -334,7 +383,7 @@ async function onExportConfirm(type: "student" | "room") {
   const func =
     type === "student" ? dataCheckStudentExport : dataCheckRoomExport;
   const params =
-    actionType.value === "common" ? getSearchModel() : getCustomSearchModel();
+    actionType.value === "common" ? searchModel : customSearchModel;
 
   const res = await func(params).catch((e: Error) => {
     message.error(e.message || "下载失败,请重新尝试!");

+ 41 - 18
src/render/views/DataCheck/ScanImage/index.vue

@@ -8,8 +8,12 @@
         emitOriginLeftTop: true,
       }"
     >
-      <img ref="imgRef" src="./data/paper.jpg" alt="p" @load="initImageSize" />
-      <!-- <img ref="imgRef" :src="curPage?.sheetUri" alt="原图" @load="initImageSize" /> -->
+      <img
+        ref="imgRef"
+        :src="getFileUrl(curPage?.sheetUri)"
+        alt="原图"
+        @load="initImageSize"
+      />
       <div class="img-recogs">
         <div
           v-for="(item, index) in recogBlocks"
@@ -73,8 +77,8 @@ import {
   RightOutlined,
   PictureFilled,
 } from "@ant-design/icons-vue";
-import { computed, nextTick, ref } from "vue";
-import { objAssign } from "@/utils/tool";
+import { computed, nextTick, ref, unref } from "vue";
+import { objAssign, getFileUrl } from "@/utils/tool";
 import { vEleMoveDirective } from "@/directives/eleMove";
 import {
   parseRecogData,
@@ -83,6 +87,7 @@ import {
   RecogBlock,
 } from "@/utils/recog/recog";
 import { useUserStore, useDataCheckStore } from "@/store";
+import { abc } from "@/constants/enumerate";
 
 import FillAreaSetDialog from "./FillAreaSetDialog.vue";
 import RecogEditDialog from "./RecogEditDialog.vue";
@@ -103,7 +108,7 @@ const updateSheetData = computed(() => {
 
   return {
     paperId: curPage.value.paperId,
-    pageIndex: curPage.value.pageIndex,
+    pageIndex: curPage.value.pageIndex + 1,
   };
 });
 
@@ -194,32 +199,45 @@ function getImageSizePos({
   return imageSize;
 }
 
+function getNumberResult(
+  result: Array<string | boolean>,
+  sources: Array<string | boolean>
+) {
+  const nResult: number[] = [];
+  result.forEach((item) => {
+    const index = sources.indexOf(item);
+    nResult[index] = 1;
+  });
+  return Array.from(nResult).map((item) => item || 0);
+}
+
 // recog data
 const recogList = ref<RecognizeArea[]>([]);
 function updateRecogList() {
-  if (!dataCheckStore.curPage) return;
+  recogList.value = [] as RecognizeArea[];
 
+  if (!dataCheckStore.curPage || !dataCheckStore.curPage.question) return;
   const regdata = parseRecogData(dataCheckStore.curPage.recogData);
   if (!regdata) return;
 
-  recogList.value = [] as RecognizeArea[];
   let index = 0;
+  const ABC = abc.split("");
   regdata.question.forEach((gGroup) => {
     gGroup.fill_result.forEach((qRecog) => {
-      const result = dataCheckStore.curPage.question[index - 1] || "";
+      const result = dataCheckStore.curPage.question.result[index] || "";
       qRecog.index = ++index;
-      // TODO: 解析其他数据
 
-      const fileResult = result ? result.split("") : [];
+      const questionResult = result ? result.split("") : [];
       const recogItem = parseDetailSize(
         qRecog,
         "question",
         qRecog.index,
-        fileResult
+        getNumberResult(questionResult, ABC)
       );
       recogList.value.push(recogItem);
     });
   });
+  // TODO: 解析其他数据
 
   parseRecogBlocks();
 }
@@ -234,7 +252,7 @@ function parseRecogBlocks() {
     userStore.recogFillSet;
   const curBorderWidth = Math.max(1, borderWidth * rate);
 
-  recogBlocks.value = recogList.value.map((item) => {
+  recogBlocks.value = unref(recogList.value).map((item) => {
     const nitem: RecogBlock = { ...item };
     nitem.areaImg = "";
 
@@ -292,10 +310,14 @@ async function onRecogEditConfirm(result: string[]) {
 
   if (data.type === "question") {
     const index = data.index - 1;
-    dataCheckStore.curPage.question.splice(index, 1, data.result.join(""));
+    dataCheckStore.curPage.question.result.splice(
+      index,
+      1,
+      data.result.join("")
+    );
     await dataCheckStore.updateField({
-      field: data.type,
-      value: dataCheckStore.curPage.question,
+      field: "QUESTION",
+      value: JSON.stringify(dataCheckStore.curPage.question),
     });
     curRecogBlock.value.result = result;
   }
@@ -337,13 +359,14 @@ function onNext() {
 }
 
 // change image
-function updateSheetSuccess(data: { url: string }) {
+function updateSheetSuccess(data: { uri: string }) {
   if (!curPage.value) return;
   dataCheckStore.modifySheetUri({
     paperIndex: curPage.value.paperIndex,
     pageIndex: curPage.value.pageIndex,
-    uri: data.url,
+    uri: data.uri,
   });
+  message.success("上传成功!");
 }
 
 // set recog style
@@ -397,11 +420,11 @@ function onSetRecogStyle() {
     height: 32px;
     line-height: 32px;
     background: #e8f3ff;
+    padding: 0;
     border-radius: 6px;
     border: 1px solid #bedaff;
     color: #4080ff;
     text-align: center;
-    cursor: pointer;
     z-index: 9;
 
     &:hover {

+ 0 - 2
src/render/views/DataCheck/SliceImage/CutImageDialog.vue

@@ -45,8 +45,6 @@ defineOptions({
 const { visible, open, close } = useModal();
 defineExpose({ open, close });
 
-open();
-
 const props = defineProps<{
   sheetUrl: string;
   sliceSelection?: AreaSize;

+ 110 - 34
src/render/views/DataCheck/SliceImage/index.vue

@@ -6,37 +6,39 @@
         :key="pindex"
         class="image-page"
       >
-        <h3 class="image-page-title">
-          {{ getPageTitle(item.number, pindex) }}
-        </h3>
-        <div
-          v-for="(url, sindex) in page.sliceUri"
-          :key="sindex"
-          class="image-item"
-        >
-          <img :src="url" :alt="sindex + 1" />
-          <div class="image-action">
-            <import-btn
-              upload-url="/api/admin/scan/answer/slice/update"
-              :format="['jpg', 'png', 'jpeg']"
-              :upload-data="curSliceInfo"
-              @upload-success="updateSliceSuccess"
-            >
+        <template v-if="page.sliceUri">
+          <h3 class="image-page-title">
+            {{ getPageTitle(item.number, pindex) }}
+          </h3>
+          <div
+            v-for="(url, sindex) in page.sliceUri"
+            :key="sindex"
+            class="image-item"
+          >
+            <img :src="getFileUrl(url)" :alt="sindex + 1" />
+            <div class="image-action">
+              <import-btn
+                upload-url="/api/admin/scan/answer/slice/update"
+                :format="['jpg', 'png', 'jpeg']"
+                :upload-data="curSliceInfo"
+                @upload-success="updateSliceSuccess"
+              >
+                <a-button
+                  class="image-change"
+                  @click="onUpdateSlice(item.number, pindex, sindex)"
+                >
+                  <template #icon><PictureFilled /></template>
+                </a-button>
+              </import-btn>
               <a-button
-                class="image-change"
-                @click="onUpdateSlice(item.number, pindex, sindex)"
+                class="image-slice"
+                @click="onEditSlice(item.number, pindex, sindex)"
               >
-                <template #icon><PictureFilled /></template>
+                <template #icon><NumberOutlined /></template>
               </a-button>
-            </import-btn>
-            <a-button
-              class="image-slice"
-              @click="onEditSlice(item.number, pindex, sindex)"
-            >
-              <template #icon><NumberOutlined /></template>
-            </a-button>
+            </div>
           </div>
-        </div>
+        </template>
       </div>
     </div>
   </div>
@@ -44,7 +46,7 @@
   <!-- CutImageDialog -->
   <CutImageDialog
     ref="cutImageDialogRef"
-    :sheet-url="sheetUrl"
+    :sheet-url="curSliceInfo.sheetUri"
     :slice-selection="curSliceSelection"
     @confirm="cutImageModified"
   />
@@ -53,10 +55,15 @@
 <script setup lang="ts">
 import { computed, ref } from "vue";
 import { NumberOutlined, PictureFilled } from "@ant-design/icons-vue";
+import { message } from "ant-design-vue";
+
 import CutImageDialog from "./CutImageDialog.vue";
 import { uploadSlice } from "@/ap/base";
 import { getFileMD5 } from "@/utils/crypto";
 import { useDataCheckStore } from "@/store";
+import { getFileUrl } from "@/utils/tool";
+
+import ImportBtn from "@/components/ImportBtn/index.vue";
 
 defineOptions({
   name: "SliceImage",
@@ -73,13 +80,15 @@ const curSliceInfo = ref({
   paperIndex: 0,
   paperId: 0,
   pageIndex: 0,
-  index: 0,
+  index: 1, // 注意:这里的index是从1开始的
+  sheetUri: "",
 });
 
 function getPageTitle(paperNumber, pageIndex) {
   return `卡${paperNumber}${pageIndex === 0 ? "正面" : "反面"}`;
 }
 
+// 编辑
 const cutImageDialogRef = ref();
 function onEditSlice(paperNumber: number, pageIndex: number, index: number) {
   const paper = curStudent.value.papers[paperNumber - 1];
@@ -87,9 +96,10 @@ function onEditSlice(paperNumber: number, pageIndex: number, index: number) {
 
   curSliceInfo.value = {
     paperIndex: paperNumber - 1,
-    pageIndex,
+    pageIndex: pageIndex + 1,
     paperId: paper.id as number,
-    index,
+    index: index + 1,
+    sheetUri: paper.pages[pageIndex].sheetUri,
   };
   curSliceSelection.value = undefined;
   cutImageDialogRef.value?.open();
@@ -110,26 +120,92 @@ async function cutImageModified(file: File) {
 
   dataCheckStore.modifySliceUri({
     ...curSliceInfo.value,
+    pageIndex: curSliceInfo.value.pageIndex - 1,
+    index: curSliceInfo.value.index - 1,
     uri: res.uri,
   });
 }
 
+// 上传
 function onUpdateSlice(paperNumber: number, pageIndex: number, index: number) {
   const paper = curStudent.value.papers[paperNumber - 1];
   if (!paper) return;
 
   curSliceInfo.value = {
     paperIndex: paperNumber - 1,
-    pageIndex,
+    pageIndex: pageIndex + 1,
     paperId: paper.id as number,
-    index,
+    index: index + 1,
+    sheetUri: paper.pages[pageIndex].sheetUri,
   };
 }
 
 function updateSliceSuccess(data: { url: string }) {
   dataCheckStore.modifySliceUri({
     ...curSliceInfo.value,
-    uri: data.url,
+    pageIndex: curSliceInfo.value.pageIndex - 1,
+    index: curSliceInfo.value.index - 1,
+    uri: data.uri,
   });
+  message.success("上传成功!");
 }
 </script>
+
+<style lang="less" scoped>
+.slice-image {
+  padding: 16px;
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: 100%;
+
+  .image-page {
+    &-title {
+      font-weight: 500;
+      font-size: 14px;
+      color: @text-color1;
+      line-height: 22px;
+      margin-bottom: 8px;
+    }
+  }
+  .image-item {
+    margin-bottom: 16px;
+    position: relative;
+    min-height: 50px;
+
+    img {
+      display: block;
+    }
+  }
+  .image-action {
+    position: absolute;
+    top: 12px;
+    right: 12px;
+    z-index: auto;
+
+    .ant-btn {
+      width: 32px;
+      height: 32px;
+      line-height: 32px;
+      padding: 0;
+      border-radius: 6px;
+      text-align: center;
+
+      &:hover {
+        opacity: 0.8;
+      }
+
+      &.image-change {
+        background: #e8f3ff;
+        border: 1px solid #bedaff;
+        color: #4080ff;
+      }
+      &.image-slice {
+        background: #e8ffea;
+        border: 1px solid #aff0b5;
+        color: #23c343;
+        margin-left: 6px;
+      }
+    }
+  }
+}
+</style>

+ 9 - 9
src/render/views/DataCheck/index.vue

@@ -6,6 +6,7 @@
           <li
             v-for="item in studentList"
             :key="item.id"
+            :class="{ 'is-active': dataCheckStore.curStudent.id === item.id }"
             @click="onSelectStudent(item)"
           >
             {{ item.examNumber }}
@@ -22,13 +23,10 @@
     </div>
     <div class="check-body">
       <ScanImage
-        v-if="dataCheckStore.curPage && isOriginImage && recogList.length"
+        v-if="dataCheckStore.curPage && isOriginImage"
         :key="dataCheckStore.curPage.kid"
-        :img-src="dataCheckStore.curPage.sheetUri"
-        :recog-data="recogList"
         @prev="onPrevPage"
         @next="onNextPage"
-        @recog-block-modified="onRecogEditConfirm"
       />
       <SliceImage v-if="dataCheckStore.curPage && !isOriginImage" />
     </div>
@@ -45,7 +43,7 @@ import { CaretLeftOutlined, CaretRightOutlined } from "@ant-design/icons-vue";
 import { DataCheckListFilter, DataCheckListItem } from "@/ap/types/dataCheck";
 import { dataCheckList } from "@/ap/dataCheck";
 import { StudentPage } from "./types";
-import { useDataCheckStore } from "@/store";
+import { useDataCheckStore, useUserStore } from "@/store";
 
 import SimplePagination from "@/components/SimplePagination/index.vue";
 import ScanImage from "./ScanImage/index.vue";
@@ -57,6 +55,7 @@ defineOptions({
 });
 
 const dataCheckStore = useDataCheckStore();
+const userStore = useUserStore();
 
 let searchModel = {} as DataCheckListFilter;
 const pageNumber = ref(1);
@@ -83,8 +82,6 @@ async function getList() {
   parseStudentPageList(res.result);
 }
 
-getList();
-
 function parseStudentPageList(students: DataCheckListItem[]) {
   dataList.value = [];
 
@@ -94,9 +91,10 @@ function parseStudentPageList(students: DataCheckListItem[]) {
       paper.pages.forEach((page, pageIndex) => {
         dataList.value.push({
           ...page,
-          paperId: paper.id as number,
-          pageIndex,
+          paperId: paper.id,
           paperIndex,
+          paperNumber: paper.number,
+          pageIndex,
           studentIndex,
           studentId: student.id,
           examId: searchModel.examId,
@@ -134,6 +132,8 @@ async function onSearch(datas: DataCheckListFilter) {
   selectPage(0);
 }
 
+onSearch({ examId: userStore.curExam.id });
+
 // page
 const curStudentInfo = ref({
   examNumber: "",

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

@@ -6,6 +6,7 @@ export interface StudentPage extends PaperPageItem {
   studentIndex: number;
   paperId: number;
   paperIndex: number;
+  paperNumber: number;
   pageIndex: number;
   pagePageIndex: number;
   kid: string;