Browse Source

feat: 仲裁页面

zhangjie 9 months ago
parent
commit
727ca09df6

+ 9 - 9
src/render/ap/recognizeCheck.ts

@@ -9,7 +9,7 @@ import {
   RecognizeCheckBuildTaskResult,
   RecognizeCheckResetTaskResult,
   RecognizeCheckDeleteTaskResult,
-  RecognizeArbitrateListItem,
+  RecognizeArbitrateItem,
   RecognizeArbitrateSaveParams,
   RecognizeArbitrateSaveResult,
   RecognizeArbitrateProgressResult,
@@ -85,17 +85,17 @@ export const recognizeCheckTaskDelete = (
   });
 
 // 仲裁
-// 识别对照仲裁任务获取
-export const recognizeArbitrateListPage = (
+// 仲裁任务获取
+export const recognizeArbitrateTask = (
   groupId: number
-): Promise<RecognizeArbitrateListItem> =>
+): Promise<RecognizeArbitrateItem> =>
   request({
     url: "/api/admin/check/omr/arbitrate/get",
     method: "post",
     data: { groupId },
   });
 
-// 识别对照仲裁结果提交
+// 仲裁结果提交
 export const recognizeArbitrateSave = (
   data: RecognizeArbitrateSaveParams
 ): Promise<RecognizeArbitrateSaveResult> =>
@@ -105,7 +105,7 @@ export const recognizeArbitrateSave = (
     data,
   });
 
-// 识别对照仲裁进度状态
+// 仲裁进度状态
 export const recognizeArbitrateProgress = (
   groupId: number
 ): Promise<RecognizeArbitrateProgressResult> =>
@@ -115,7 +115,7 @@ export const recognizeArbitrateProgress = (
     data: { groupId },
   });
 
-// 识别对照仲裁任务释放
+// 仲裁任务释放
 export const recognizeArbitrateRelease = (
   groupId: number
 ): Promise<{ success: boolean }> =>
@@ -125,10 +125,10 @@ export const recognizeArbitrateRelease = (
     data: { groupId },
   });
 
-// 识别对照仲裁任务历史
+// 仲裁任务历史
 export const recognizeArbitrateHistory = (
   data: RecognizeArbitrateHistoryParams
-): Promise<RecognizeArbitrateListItem> =>
+): Promise<RecognizeArbitrateItem> =>
   request({
     url: "/api/admin/check/omr/arbitrate/history",
     method: "post",

+ 69 - 15
src/render/ap/types/recognizeCheck.ts

@@ -70,22 +70,21 @@ export interface RecognizeCheckDeleteTaskResult
 }
 
 // 仲裁
-
-export interface RecognizeArbitrateListItemPage {
+export interface RecognizeArbitrateItemPage {
   index: number;
   uri: string;
   // 字段为null表示不需要仲裁处理
-  absent: boolean | null;
-  breach: boolean | null;
-  paperType: string;
+  absent: boolean[] | null;
+  breach: boolean[] | null;
+  paperType: string[] | null;
   // key为识别结果集中的序号,从1开始
   // 仲裁任务value数组有两个结果,分别表示一评和二评结果
-  question: Record<number, string>;
-  selective: null | boolean;
+  question: Record<number, string[]>;
+  // selective: string[] | null;
   recogData: string;
 }
 
-export interface RecognizeArbitrateListItem {
+export interface RecognizeArbitrateItem {
   // 任务ID
   id: number;
   examNumber: string;
@@ -97,21 +96,18 @@ export interface RecognizeArbitrateListItem {
   cardNumber: number;
   paperId: number;
   paperNumber: number;
-  pages: RecognizeArbitrateListItemPage[];
+  pages: RecognizeArbitrateItemPage[];
 }
 
-export type RecognizeArbitrateListPageResult =
-  PageResult<RecognizeArbitrateListItem>;
-
 export interface RecognizeArbitrateSavePage {
   index: number;
   // 与任务对应,有需要处理的才传递结果,否则为null
   // 所有需要仲裁的处理项需要全部提交
   absent: boolean | null;
   breach: boolean | null;
-  paperType: string;
+  paperType: string | null;
   question: Record<number, string>;
-  selective: null | boolean;
+  selective: boolean | null;
 }
 export interface RecognizeArbitrateSaveParams {
   id: number;
@@ -124,7 +120,7 @@ export interface RecognizeArbitrateSaveResult
 }
 
 export interface RecognizeArbitrateProgressResult {
-  finishCOunt: number;
+  finishCount: number;
   todoCount: number;
 }
 
@@ -133,3 +129,61 @@ export interface RecognizeArbitrateHistoryParams {
   id?: number;
   next?: boolean;
 }
+
+// 扫描数据相关
+// 参考: /views/RecognizeCheck/data/recogData.json
+export interface RecognizeArbitrateTaskDetail {
+  type: "absent" | "breach" | "paperType" | "question";
+  index: number;
+  fillPosition: Array<{ x: number; y: number }>;
+  fillSize: {
+    w: number;
+    h: number;
+  };
+  fillArea: { x: number; y: number; w: number; h: number };
+  options: string[];
+  result: string[];
+  result1: string;
+  result2: string;
+  multiple: boolean;
+  pageIndex: number;
+  groupId: number;
+  arbitrate: boolean;
+}
+
+export interface RecogDataField {
+  field: string;
+  index: number;
+  content: string;
+  wrong_flag: number;
+  fill_result: RecogDataFillResult[];
+  type: string;
+  rect?: [number, number, number, number];
+}
+
+export interface RecogDataFillResult {
+  main_number: number;
+  sub_number: number;
+  // 所有试题的统一序号
+  index?: number;
+  single: number;
+  fill_option: number[];
+  suspect_flag: number;
+  fill_position: string[];
+  fill_size: [number, number];
+}
+
+export interface RecogDataType {
+  algorithm: number;
+  page_index: number;
+  error_flag: number;
+  blank_flag: number;
+  angle: number;
+  examNumber: RecogDataField;
+  absent: RecogDataField;
+  breach: RecogDataField;
+  pageNumber: RecogDataField;
+  paperType: RecogDataField;
+  question: RecogDataField[];
+  block_struct: number[];
+}

+ 4 - 0
src/render/components.d.ts

@@ -10,6 +10,7 @@ declare module 'vue' {
     AAlert: typeof import('@qmth/ui')['Alert']
     AButton: typeof import('@qmth/ui')['Button']
     ACard: typeof import('@qmth/ui')['Card']
+    ACol: typeof import('@qmth/ui')['Col']
     ADivider: typeof import('@qmth/ui')['Divider']
     AForm: typeof import('@qmth/ui')['Form']
     AFormItem: typeof import('@qmth/ui')['FormItem']
@@ -21,7 +22,10 @@ declare module 'vue' {
     AProgress: typeof import('@qmth/ui')['Progress']
     ARadio: typeof import('@qmth/ui')['Radio']
     ARadioGroup: typeof import('@qmth/ui')['RadioGroup']
+    ARow: typeof import('@qmth/ui')['Row']
     ASelect: typeof import('@qmth/ui')['Select']
+    ASpace: typeof import('@qmth/ui')['Space']
+    ASpaceCompact: typeof import('@qmth/ui')['SpaceCompact']
     ASpin: typeof import('@qmth/ui')['Spin']
     ATable: typeof import('@qmth/ui')['Table']
     ATabPane: typeof import('@qmth/ui')['TabPane']

+ 2 - 1
src/render/components/SelectCourse/index.vue

@@ -5,6 +5,7 @@
     :options="optionList"
     filter-option
     :multiple="false"
+    style="width: 200px"
     v-bind="attrs"
     @change="onChange"
   >
@@ -46,7 +47,7 @@ const search = async () => {
     return { ...item, value: item.subjectCode, label: item.subjectName };
   });
 };
-search();
+// search();
 
 const onChange = () => {
   const selectedData = optionList.value.filter(

+ 61 - 0
src/render/directives/eleMove.ts

@@ -0,0 +1,61 @@
+import type { Directive } from "vue";
+
+interface BindValue {
+  moveElement?: ({ left, top }: { left: number; top: number }) => void;
+  moveStop?: ({ left, top }: { left: number; top: number }) => void;
+  moveStart?: (e: MouseEvent) => void;
+}
+
+export const vEleMoveDirective: Directive<HTMLElement, BindValue> = {
+  mounted(el, { value, modifiers }) {
+    let [_x, _y] = [0, 0];
+    // 只允许鼠标左键触发
+    const moveHandle = (e: MouseEvent) => {
+      if (e.button !== 0) return;
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+
+      const left = e.pageX - _x;
+      const top = e.pageY - _y;
+
+      value.moveElement && value.moveElement({ left, top });
+    };
+
+    const upHandle = (e: MouseEvent) => {
+      if (e.button !== 0) return;
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+
+      const left = e.pageX - _x;
+      const top = e.pageY - _y;
+
+      value.moveStop && value.moveStop({ left, top });
+      document.removeEventListener("mousemove", moveHandle);
+      document.removeEventListener("mouseup", upHandle);
+    };
+
+    el.addEventListener("mousedown", (e: MouseEvent) => {
+      if (e.button !== 0) return;
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+      _x = e.pageX;
+      _y = e.pageY;
+      value.moveStart && value.moveStart(e);
+
+      document.addEventListener("mousemove", moveHandle);
+      document.addEventListener("mouseup", upHandle);
+    });
+  },
+};

+ 32 - 0
src/render/styles/base.less

@@ -0,0 +1,32 @@
+html,
+body {
+  height: 100%;
+  width: 100%;
+  font-size: 14px;
+  color: @text-color1;
+}
+#app {
+  height: 100%;
+  background-color: #fff;
+  // min-width: 1200px;
+}
+.check-tag {
+  height: 30px;
+  line-height: 28px;
+}
+
+// color
+.color-success {
+  color: @success-color;
+}
+.color-error {
+  color: @error-color;
+}
+.color-gray {
+  color: @text-color3;
+}
+
+// base
+.page-box {
+  padding: 20px;
+}

+ 1 - 0
src/render/styles/color.less

@@ -2,6 +2,7 @@
 @text-color2: #595959;
 @text-color3: #8c8c8c;
 @border-color1: #e5e5e5;
+@background-color: #e5e6eb;
 
 @brand-color: #165dff;
 @warning-color: #ff7d00;

+ 2 - 29
src/render/styles/index.less

@@ -1,31 +1,4 @@
 @import "animation.less";
 @import "reset.less";
-html,
-body {
-  height: 100%;
-  width: 100%;
-  font-size: 14px;
-  color: @text-color1;
-}
-#app {
-  height: 100%;
-  background-color: #fff;
-  // min-width: 1200px;
-}
-.check-tag {
-  height: 30px;
-  line-height: 28px;
-}
-
-// color
-.color-error {
-  color: @error-color;
-}
-.color-gray {
-  color: @text-color3;
-}
-
-// base
-.page-box {
-  padding: 20px;
-}
+@import "base.less";
+@import "pages.less";

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

@@ -0,0 +1,135 @@
+.arbitrate {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  overflow: hidden;
+
+  &-title {
+    flex-grow: 0;
+    flex-shrink: 0;
+    padding: 11px 16px;
+    height: 54px;
+    position: relative;
+  }
+
+  &-back {
+    position: absolute;
+    top: 11px;
+    left: 16px;
+    z-index: 9;
+  }
+  &-stat {
+    height: 100%;
+    text-align: center;
+    line-height: 32px;
+
+    > p {
+      display: inline-block;
+
+      &:first-child {
+        margin-right: 25px;
+      }
+
+      .anticon {
+        margin-right: 5px;
+      }
+    }
+  }
+
+  &-body {
+    flex-grow: 2;
+    overflow: hidden;
+    background-color: @background-color;
+    position: relative;
+  }
+
+  &-img {
+    height: 100%;
+    overflow: auto;
+    img {
+      display: block;
+      width: calc(100% - 150px * 2);
+      margin: 0 auto;
+      height: auto;
+      max-height: none;
+    }
+  }
+
+  &-img-thumb {
+    position: absolute;
+    border-radius: 8px;
+    border: 8px solid rgba(89, 89, 89, 0.6);
+    background-color: #fff;
+    width: 136px;
+    height: auto;
+    min-height: 80px;
+    z-index: 9;
+    bottom: 0;
+    right: 0;
+  }
+
+  &-img-area {
+    position: absolute;
+    border: 4px solid @error-color;
+    width: 100%;
+    height: 40px;
+    z-index: auto;
+    cursor: move;
+    top: 0;
+  }
+
+  &-modal {
+    position: absolute;
+    width: 1002px;
+    height: 216px;
+    top: 8px;
+    left: 50%;
+    margin-left: -501px;
+    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 #e5e5e5;
+      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;
+      }
+    }
+  }
+}

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

@@ -305,3 +305,14 @@ export function objModifyAssign<T extends Record<string, any>>(
     }
   });
 }
+
+/** 获取数组最大数 */
+export function maxNum(dataList: number[]): number {
+  if (!dataList.length) return 0;
+  return Math.max.apply(null, dataList);
+}
+/** 获取数组最小数 */
+export function minNum(dataList: number[]): number {
+  if (!dataList.length) return 0;
+  return Math.min.apply(null, dataList);
+}

+ 2 - 0
src/render/utils/uiUtils.ts

@@ -13,6 +13,8 @@ export async function showConfirm(option: ConfirmOption): Promise<"confirm"> {
       title: option.title || "系统通知",
       icon: createVNode(InfoCircleFilled),
       content: option.content,
+      okText: "确定",
+      cancelText: "取消",
       onOk() {
         resolve("confirm");
       },

+ 534 - 1
src/render/views/RecognizeCheck/RecognizeArbitrate.vue

@@ -1,9 +1,542 @@
 <template>
-  <div></div>
+  <div class="arbitrate">
+    <div class="arbitrate-title">
+      <a-button class="arbitrate-back" @click="goback">
+        <template #icon><SwapLeftOutlined /> </template>后退
+      </a-button>
+      <div class="arbitrate-stat">
+        <p class="color-success">
+          <CheckCircleFilled />已处理:{{ progress.finishCount }}
+        </p>
+        <p class="color-error">
+          <CloseCircleFilled />未处理:{{ progress.todoCount }}
+        </p>
+      </div>
+    </div>
+
+    <div class="arbitrate-body">
+      <RecognizeImage />
+
+      <!-- arbitrate action modal -->
+      <div v-if="curArbitrateTaskDetail" class="arbitrate-modal">
+        <a-row align="top" :gutter="8" class="m-b-8px">
+          <a-col :span="6">
+            <div class="modal-box">
+              <p class="box-title">客观题</p>
+              <p class="box-cont">{{ curTaskDetailName }}</p>
+            </div>
+          </a-col>
+          <a-col :span="12">
+            <div class="modal-box modal-origin"></div>
+          </a-col>
+          <a-col :span="6">
+            <div class="modal-box" @click="changePrevTaskDetail">
+              <p class="box-title">左键</p>
+              <p class="box-cont">上一个</p>
+            </div>
+          </a-col>
+        </a-row>
+        <a-row align="top" :gutter="8">
+          <a-col :span="3">
+            <div class="modal-box">
+              <p class="box-title">一评结果</p>
+              <p class="box-cont">{{ curArbitrateTaskDetail.result1 }}</p>
+            </div>
+          </a-col>
+          <a-col :span="3">
+            <div class="modal-box">
+              <p class="box-title">二评结果</p>
+              <p class="box-cont">{{ curArbitrateTaskDetail.result2 }}</p>
+            </div>
+          </a-col>
+          <a-col :span="12">
+            <div class="modal-box modal-options">
+              <a-button
+                v-for="option in curArbitrateTaskDetail.options"
+                :key="option"
+                :type="
+                  curArbitrateTaskDetail.value.result.includes(option)
+                    ? 'primary'
+                    : 'default'
+                "
+                @click="selectOption(option)"
+                >{{ option }}</a-button
+              >
+            </div>
+          </a-col>
+          <a-col :span="6">
+            <div class="modal-box">
+              <p class="box-title">Enter键</p>
+              <p class="box-cont">下一个</p>
+            </div>
+          </a-col>
+        </a-row>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts">
+import { computed, ref, reactive, onMounted, onBeforeUnmount } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import {
+  CheckCircleFilled,
+  CloseCircleFilled,
+  SwapLeftOutlined,
+} from "@ant-design/icons-vue";
+import { message } from "ant-design-vue";
+
+import RecognizeImage from "./RecognizeImage.vue";
+
+import {
+  recognizeArbitrateTask,
+  recognizeArbitrateSave,
+  recognizeArbitrateProgress,
+  recognizeArbitrateHistory,
+} from "@/ap/recognizeCheck";
+import {
+  RecognizeArbitrateItem,
+  RecognizeArbitrateTaskDetail,
+  RecogDataType,
+  RecogDataFillResult,
+  RecognizeArbitrateSavePage,
+} from "@/ap/types/recognizeCheck";
+import { maxNum, minNum } from "@/utils/tool";
+import { nextTick } from "vue";
+
 defineOptions({
   name: "RecognizeArbitrate",
 });
+
+const router = useRouter();
+const route = useRoute();
+const groupId = route.params.groupId ? Number(route.params.groupId) : 0;
+
+// 任务进度
+const progress = ref({
+  finishCount: 0,
+  todoCount: 0,
+});
+async function updateProgress() {
+  const res = await recognizeArbitrateProgress(groupId);
+  progress.value = res || {};
+}
+
+const curArbitrateTaskDetails = ref([] as RecognizeArbitrateTaskDetail[]);
+const curArbitrateTaskDetailIndex = ref(0);
+const curTaskDetails = ref([] as RecognizeArbitrateTaskDetail[]);
+const curArbitrateTaskDetail = ref<RecognizeArbitrateTaskDetail | null>(null);
+
+// TODO: 缓存所有仲裁结果
+// const cacheArbitrateTaskDetailResults: Record<string, string[]> = {}
+
+const nextArbitrateTaskDetail = computed(() => {
+  return curArbitrateTaskDetails.value[curArbitrateTaskDetailIndex.value + 1];
+});
+// 下一个细分任务是否是另一个任务
+const nextDetailIsAnotherTask = computed(() => {
+  return !nextArbitrateTaskDetail.value;
+});
+// 下一个细分任务是否是另一页的
+const nextDetailIsAnotherPage = computed(() => {
+  if (!curArbitrateTaskDetail.value) return false;
+
+  return (
+    !nextArbitrateTaskDetail.value ||
+    nextArbitrateTaskDetail.value.pageIndex !==
+      curArbitrateTaskDetail.value.pageIndex
+  );
+});
+
+const curTaskDetailName = computed(() => {
+  if (!curArbitrateTaskDetail.value) return "";
+
+  if (curArbitrateTaskDetail.value.type === "question") {
+    return `#${curArbitrateTaskDetail.value.index}`;
+  }
+  return "-";
+});
+
+// 获取任务
+let curTask: RecognizeArbitrateItem | null = null;
+async function getTask() {
+  const res = await recognizeArbitrateTask(groupId).catch(() => false);
+  curTask = res || null;
+  if (!curTask) {
+    message.error("获取任务失败");
+    return;
+  }
+
+  curTaskDetails.value = parseDetails(curTask);
+  curArbitrateTaskDetails.value = curTaskDetails.value.filter(
+    (item) => item.arbitrate
+  );
+}
+
+// 解析仲裁任务详情
+function parseDetails(
+  data: RecognizeArbitrateItem
+): RecognizeArbitrateTaskDetail[] {
+  const details: RecognizeArbitrateTaskDetail[] = [];
+  data.pages.forEach((page) => {
+    if (!page.recogData) return;
+    const precogData = window.atob(page.recogData);
+    const recogData: RecogDataType | null = precogData
+      ? JSON.parse(precogData)
+      : null;
+    if (!recogData) return;
+
+    // 缺考
+    details.push({
+      index: 0,
+      ...parseDetailSize(recogData.absent.fill_result[0], "absent"),
+      multiple: false,
+      result1: page.absent ? page.absent[0] : "",
+      result2: page.absent ? page.absent[1] : "",
+      pageIndex: page.index,
+      groupId: groupId,
+      arbitrate: Boolean(page.absent),
+    });
+
+    // 违纪
+    details.push({
+      index: 0,
+      ...parseDetailSize(recogData.breach.fill_result[0], "breach"),
+      multiple: false,
+      result1: page.breach ? page.breach[0] : "",
+      result2: page.breach ? page.breach[1] : "",
+      pageIndex: page.index,
+      groupId: groupId,
+      arbitrate: Boolean(page.breach),
+    });
+
+    // 试卷类型
+    details.push({
+      index: 0,
+      ...parseDetailSize(recogData.paperType.fill_result[0], "paperType"),
+      multiple: false,
+      result1: page.paperType ? page.paperType[0] : "",
+      result2: page.paperType ? page.paperType[1] : "",
+      pageIndex: page.index,
+      groupId: groupId,
+      arbitrate: Boolean(page.paperType),
+    });
+
+    // 试题
+    let index = 0;
+    recogData.question.forEach((gGroup) => {
+      gGroup.fill_result.forEach((qRecog) => {
+        qRecog.index = ++index;
+        const questionResult = page.question[qRecog.index];
+        const arbitrate = questionResult && questionResult.length >= 2;
+
+        details.push({
+          index: qRecog.index,
+          ...parseDetailSize(qRecog, "question"),
+          multiple: true,
+          result: arbitrate ? [] : questionResult,
+          result1: questionResult ? questionResult[0] : "",
+          result2: questionResult ? questionResult[1] : "",
+          pageIndex: page.index,
+          groupId: groupId,
+          arbitrate,
+        });
+      });
+    });
+  });
+
+  return details;
+}
+
+const abc = "abcdefghijklmnopqrstuvwxyz".toUpperCase();
+type DetailSizeResult = Pick<
+  RecognizeArbitrateTaskDetail,
+  "fillPosition" | "fillSize" | "fillArea" | "options" | "type" | "result"
+>;
+function parseDetailSize(
+  data: RecogDataFillResult,
+  type: string
+): DetailSizeResult {
+  const result: DetailSizeResult = {
+    type,
+    fillPosition: [],
+    fillSize: { w: 0, h: 0 },
+    fillArea: { x: 0, y: 0, w: 0, h: 0 },
+    options: [],
+    result: [],
+  };
+
+  if (!data) return result;
+
+  result.fillPosition = data.fill_position.map((item) => {
+    const size = item.split(",");
+    return {
+      x: size[0] ? Number(size[0]) : 0,
+      y: size[1] ? Number(size[1]) : 0,
+    };
+  });
+  result.fillSize = {
+    w: data.fill_size[0],
+    h: data.fill_size[1],
+  };
+  const xs = result.fillPosition.map((item) => item.x);
+  const maxX = maxNum(xs);
+  const minX = minNum(xs);
+  result.fillArea = {
+    x: minX + result.fillSize.w + 10,
+    y: result.fillPosition[0].y - 5,
+    w: maxX - minX + result.fillSize.w + 20,
+    h: result.fillPosition[0].y + result.fillSize.h + 5,
+  };
+
+  if (type === "question") {
+    const options = abc.substring(0, data.fill_position.length).split("");
+    // 空用“#”表示
+    result.options = ["#", ...options];
+  }
+
+  return result;
+}
+
+// 任务执行流程 ----------------- start>
+function setCurTaskDetail() {
+  curArbitrateTaskDetail.value =
+    curArbitrateTaskDetails.value[curArbitrateTaskDetailIndex.value];
+}
+const detailChanging = ref(false);
+async function changeNextTaskDetail() {
+  if (detailChanging.value) return;
+  detailChanging.value = true;
+
+  if (nextDetailIsAnotherTask.value) {
+    await submitCurTask().catch(() => {});
+    await getNextTask().catch(() => {});
+    await nextTick(() => {
+      detailChanging.value = false;
+    });
+    return;
+  }
+
+  curArbitrateTaskDetailIndex.value++;
+  setCurTaskDetail();
+  setTimeout(() => {
+    detailChanging.value = false;
+  }, 500);
+}
+
+async function changePrevTaskDetail() {
+  if (detailChanging.value) return;
+
+  if (curArbitrateTaskDetailIndex.value === 0) {
+    detailChanging.value = true;
+    await getPrevTask().catch(() => {});
+    await nextTick(() => {
+      detailChanging.value = false;
+    });
+    return;
+  }
+
+  detailChanging.value = true;
+  curArbitrateTaskDetailIndex.value--;
+  setCurTaskDetail();
+  setTimeout(() => {
+    detailChanging.value = false;
+  }, 500);
+}
+
+async function getNextTask() {
+  let result = true;
+  const res = await recognizeArbitrateHistory({
+    groupId,
+    id: curTask?.id,
+  }).catch(() => {
+    result = false;
+  });
+
+  if (!result) {
+    message.error("获取上一个任务失败!");
+    return;
+  }
+
+  if (!res) {
+    message.error("没有上一个任务了!");
+    return;
+  }
+
+  curTask = res;
+  curTaskDetails.value = parseDetails(curTask);
+  curArbitrateTaskDetails.value = curTaskDetails.value.filter(
+    (item) => item.arbitrate
+  );
+
+  curArbitrateTaskDetailIndex.value = curArbitrateTaskDetails.value.length - 1;
+  setCurTaskDetail();
+}
+
+async function getPrevTask() {
+  let result = true;
+  const res = await recognizeArbitrateHistory({
+    groupId,
+    id: curTask?.id,
+    next: true,
+  }).catch(() => {
+    result = false;
+  });
+
+  if (!result) {
+    message.error("获取下一个任务失败!");
+    return;
+  }
+
+  if (!res) {
+    message.error("没有下一个任务了!");
+    return;
+  }
+
+  curTaskDetails.value = parseDetails(curTask);
+  curArbitrateTaskDetails.value = curTaskDetails.value.filter(
+    (item) => item.arbitrate
+  );
+
+  curArbitrateTaskDetailIndex.value = 0;
+  setCurTaskDetail();
+}
+
+function selectOption(option: string) {
+  if (!curArbitrateTaskDetail.value) return;
+
+  // 单选直接赋值
+  if (!curArbitrateTaskDetail.value.multiple) {
+    curArbitrateTaskDetail.value.result = [option];
+    return;
+  }
+
+  // 多选情况
+  // 空直接赋值,空值与其他互斥
+  if (option === "#") {
+    curArbitrateTaskDetail.value.result = ["#"];
+    return;
+  }
+
+  const result = curArbitrateTaskDetail.value.result.filter(
+    (item) => item === "#"
+  );
+  if (result.includes(option)) {
+    result = result.filter((item) => item !== option);
+  } else {
+    result.push(option);
+  }
+  // 保证result的顺序和options的顺序是一致的
+  curArbitrateTaskDetail.value.result =
+    curArbitrateTaskDetail.value.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;
+  }
+}
+
+// 保存任务详情信息
+async function onConfirm() {
+  if (!curArbitrateTaskDetail.value?.result.length) {
+    message.error("请完成仲裁结果!");
+    return;
+  }
+
+  await changeNextTaskDetail();
+}
+
+// 提交当前任务仲裁数据
+function getTaskTypeResult(
+  type: "absent" | "breach" | "paperType",
+  pageIndex: number
+) {
+  const taskDetail = curArbitrateTaskDetails.value.find(
+    (item) => item.pageIndex === pageIndex && item.type === type
+  );
+
+  const result = taskDetail ? taskDetail.result.join() : null;
+
+  if (type === "paperType") return result;
+
+  return result ? result === "true" : null;
+}
+
+async function submitCurTask() {
+  if (!curTask) return;
+  const curArbitrateTaskDetailQuestionResults: Record<string, string> = {};
+  curArbitrateTaskDetails.value.forEach((item) => {
+    if (item.type !== "question") return;
+
+    const k = `${item.pageIndex}-${item.index}`;
+    curArbitrateTaskDetailQuestionResults[k] = item.result.join("");
+  });
+
+  const pages = curTask.pages.map((page) => {
+    const absentResult = getTaskTypeResult(
+      "absent",
+      page.index
+    ) as RecognizeArbitrateSavePage["absent"];
+    const breachResult = getTaskTypeResult(
+      "breach",
+      page.index
+    ) as RecognizeArbitrateSavePage["breach"];
+
+    const paperTypeResult = getTaskTypeResult(
+      "paperType",
+      page.index
+    ) as RecognizeArbitrateSavePage["paperType"];
+
+    const questionResult: Record<number, string> = {};
+    curTaskDetails.value.forEach((item) => {
+      if (item.type !== "question") return;
+
+      const k = `${item.pageIndex}-${item.index}`;
+      const result = item.arbitrate
+        ? curArbitrateTaskDetailQuestionResults[k]
+        : item.result.join("");
+      questionResult[item.index] = result;
+    });
+
+    const npage: RecognizeArbitrateSavePage = {
+      index: page.index,
+      absent: absentResult,
+      breach: breachResult,
+      paperType: paperTypeResult,
+      question: questionResult,
+      selective: null,
+    };
+    return npage;
+  });
+
+  await recognizeArbitrateSave({ id: curTask.id, pages }).catch(() => false);
+}
+
+// 任务执行流程 ----------------- end>
+
+// 返回
+function goback() {
+  router.back();
+}
+
+onMounted(() => {
+  registKeyEvent();
+});
+
+onBeforeUnmount(() => {
+  removeKeyEvent();
+});
 </script>

+ 104 - 0
src/render/views/RecognizeCheck/RecognizeImage.vue

@@ -0,0 +1,104 @@
+<template>
+  <div ref="arbitrateImgRef" class="arbitrate-img" @scroll="onImgScroll">
+    <img src="./data/202302040117-1.jpg" alt="扫描结果" @load="onImgLoad" />
+    <!-- <img :src="imgSrc" alt="扫描结果" @load="onImgLoad" /> -->
+  </div>
+  <div ref="imgThumbRef" class="arbitrate-img-thumb">
+    <img src="./data/202302040117-1.jpg" alt="扫描结果" />
+    <!-- <img :src="imgSrc" alt="扫描结果" /> -->
+    <div
+      class="arbitrate-img-area"
+      v-ele-move-directive.prevent.stop="{
+        moveStart: moveAreaStart,
+        moveElement: moveArea,
+        moveStop: moveAreaStop,
+      }"
+      :style="areaStyle"
+    ></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, reactive } from "vue";
+
+import { vEleMoveDirective } from "@/directives/eleMove";
+import { RecognizeArbitrateTaskDetail } from "@/ap/types/recognizeCheck";
+import { onMounted } from "vue";
+
+defineOptions({
+  name: "RecognizeImage",
+});
+
+const props = defineProps<{
+  imgSrc: string;
+  recogData: RecognizeArbitrateTaskDetail[];
+}>();
+
+const arbitrateImgRef = ref();
+
+// img
+function updateImgAreaSize() {
+  const imgBoxDom = arbitrateImgRef.value as HTMLDivElement;
+  const imgDom = imgBoxDom.firstChild as HTMLImageElement;
+  const { clientHeight } = imgThumbRef.value as HTMLDivElement;
+
+  const areaRate = Math.min(imgBoxDom.clientHeight / imgDom.clientHeight, 1);
+  const areaHeight = clientHeight * areaRate;
+  areaSize.height = areaHeight;
+}
+
+function onImgLoad() {
+  updateImgAreaSize();
+}
+
+let areaIsMoving = false;
+function onImgScroll(e: Event) {
+  if (areaIsMoving) {
+    e.preventDefault();
+    return;
+  }
+
+  const imgBoxDom = arbitrateImgRef.value as HTMLDivElement;
+  const scrollTop = imgBoxDom.scrollTop;
+  const imgDom = imgBoxDom.firstChild as HTMLImageElement;
+  const { clientHeight } = imgThumbRef.value as HTMLDivElement;
+  areaSize.top = (clientHeight * scrollTop) / imgDom.clientHeight;
+}
+
+// img-thumb area
+const imgThumbRef = ref();
+const areaSize = reactive({
+  height: 40,
+  top: 0,
+});
+const areaOriginTop = ref(0);
+
+const areaStyle = computed(() => {
+  return { height: `${areaSize.height}px`, top: `${areaSize.top}px` };
+});
+interface MovePos {
+  left: number;
+  top: number;
+}
+
+function moveAreaStart() {
+  areaOriginTop.value = areaSize.top;
+  areaIsMoving = false;
+}
+function moveArea(pos: MovePos) {
+  areaIsMoving = true;
+  const areaTop = pos.top + areaOriginTop.value;
+  const { clientHeight } = imgThumbRef.value as HTMLDivElement;
+  areaSize.top = Math.min(areaTop, clientHeight - areaSize.height);
+  areaSize.top = Math.max(0, areaSize.top);
+
+  const imgBoxDom = arbitrateImgRef.value as HTMLDivElement;
+  const imgDom = imgBoxDom.firstChild as HTMLImageElement;
+  imgBoxDom.scrollTop = (imgDom.clientHeight * areaSize.top) / clientHeight;
+}
+
+function moveAreaStop(pos: MovePos) {
+  moveArea(pos);
+  areaIsMoving = false;
+}
+</script>

BIN
src/render/views/RecognizeCheck/data/202302040117-1.jpg


+ 1024 - 0
src/render/views/RecognizeCheck/data/recogData.json

@@ -0,0 +1,1024 @@
+{
+  "algorithm": 0,
+  "page_index": 1,
+  "error_flag": 0,
+  "blank_flag": 2,
+  "angle": 0,
+  "examNumber": {
+    "field": "examNumber",
+    "index": 1,
+    "content": "202302040117",
+    "wrong_flag": 0,
+    "fill_result": [],
+    "type": "barcode",
+    "rect": [
+      124.000000,
+      229.000000,
+      542.000000,
+      205.000000
+    ]
+  },
+  "absent": {
+    "field": "absent",
+    "index": 1,
+    "content": "",
+    "wrong_flag": 0,
+    "fill_result": [
+      {
+        "main_number": 0,
+        "sub_number": 0,
+        "single": 6,
+        "fill_option": [
+          0
+        ],
+        "suspect_flag": 0,
+        "fill_position": [
+          "859,701"
+        ],
+        "fill_size": [
+          41,
+          23
+        ]
+      }
+    ],
+    "type": "fill_area"
+  },
+  "breach": {
+    "field": "breach",
+    "index": 1,
+    "content": "",
+    "wrong_flag": 0,
+    "fill_result": [
+      {
+        "main_number": 0,
+        "sub_number": 0,
+        "single": 6,
+        "fill_option": [
+          0
+        ],
+        "suspect_flag": 0,
+        "fill_position": [
+          "1129,701"
+        ],
+        "fill_size": [
+          41,
+          23
+        ]
+      }
+    ],
+    "type": "fill_area"
+  },
+  "pageNumber": {
+    "field": "",
+    "index": 0,
+    "content": "",
+    "wrong_flag": 0,
+    "fill_result": [],
+    "type": "",
+    "rect": [
+      0.000000,
+      0.000000,
+      0.000000,
+      0.000000
+    ]
+  },
+  "paperType": {
+    "field": "",
+    "index": 0,
+    "content": "",
+    "wrong_flag": 0,
+    "fill_result": [],
+    "type": "",
+    "rect": [
+      0.000000,
+      0.000000,
+      0.000000,
+      0.000000
+    ]
+  },
+  "question": [
+    {
+      "field": "question",
+      "index": 1,
+      "content": "",
+      "wrong_flag": 0,
+      "fill_result": [
+        {
+          "main_number": 1,
+          "sub_number": 1,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,895",
+            "222,894",
+            "260,895",
+            "298,895"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 2,
+          "single": 1,
+          "fill_option": [
+            1,
+            0,
+            0,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,923",
+            "222,923",
+            "260,923",
+            "298,923"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 3,
+          "single": 1,
+          "fill_option": [
+            0,
+            1,
+            0,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,951",
+            "222,951",
+            "260,951",
+            "298,951"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 4,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,980",
+            "223,979",
+            "260,979",
+            "298,979"
+          ],
+          "fill_size": [
+            35,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 5,
+          "single": 1,
+          "fill_option": [
+            1,
+            0,
+            0,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,1007",
+            "223,1007",
+            "260,1007",
+            "298,1007"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        }
+      ],
+      "type": "fill_area"
+    },
+    {
+      "field": "question",
+      "index": 2,
+      "content": "",
+      "wrong_flag": 0,
+      "fill_result": [
+        {
+          "main_number": 1,
+          "sub_number": 6,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "405,894",
+            "441,895",
+            "478,895",
+            "516,895"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 7,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "405,923",
+            "441,923",
+            "478,923",
+            "516,923"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 8,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "405,951",
+            "441,951",
+            "478,951",
+            "516,951"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 9,
+          "single": 1,
+          "fill_option": [
+            1,
+            0,
+            0,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "405,979",
+            "441,979",
+            "478,979",
+            "516,979"
+          ],
+          "fill_size": [
+            35,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 10,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "405,1007",
+            "441,1007",
+            "478,1007",
+            "516,1007"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        }
+      ],
+      "type": "fill_area"
+    },
+    {
+      "field": "question",
+      "index": 3,
+      "content": "",
+      "wrong_flag": 0,
+      "fill_result": [
+        {
+          "main_number": 1,
+          "sub_number": 11,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "624,894",
+            "660,894",
+            "699,894",
+            "737,894"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 12,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "624,923",
+            "660,923",
+            "699,922",
+            "737,923"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 13,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "624,951",
+            "660,951",
+            "698,951",
+            "737,951"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 14,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "624,979",
+            "662,979",
+            "699,979",
+            "737,979"
+          ],
+          "fill_size": [
+            35,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 15,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "624,1007",
+            "662,1007",
+            "699,1007",
+            "737,1007"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        }
+      ],
+      "type": "fill_area"
+    },
+    {
+      "field": "question",
+      "index": 4,
+      "content": "",
+      "wrong_flag": 0,
+      "fill_result": [
+        {
+          "main_number": 1,
+          "sub_number": 16,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "844,894",
+            "881,894",
+            "918,894",
+            "957,894"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 17,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "844,923",
+            "881,923",
+            "918,922",
+            "957,923"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 18,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "844,951",
+            "881,951",
+            "920,951",
+            "957,951"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 19,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "844,979",
+            "882,979",
+            "920,979",
+            "957,979"
+          ],
+          "fill_size": [
+            35,
+            22
+          ]
+        },
+        {
+          "main_number": 1,
+          "sub_number": 20,
+          "single": 1,
+          "fill_option": [
+            0,
+            0,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "844,1007",
+            "882,1007",
+            "920,1007",
+            "957,1007"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        }
+      ],
+      "type": "fill_area"
+    },
+    {
+      "field": "question",
+      "index": 5,
+      "content": "",
+      "wrong_flag": 0,
+      "fill_result": [
+        {
+          "main_number": 2,
+          "sub_number": 1,
+          "single": 0,
+          "fill_option": [
+            0,
+            1,
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,1107",
+            "222,1107",
+            "260,1107",
+            "298,1107"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 2,
+          "sub_number": 2,
+          "single": 0,
+          "fill_option": [
+            0,
+            1,
+            1,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,1136",
+            "222,1136",
+            "260,1136",
+            "298,1136"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 2,
+          "sub_number": 3,
+          "single": 0,
+          "fill_option": [
+            0,
+            0,
+            1,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,1164",
+            "222,1164",
+            "260,1164",
+            "298,1164"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 2,
+          "sub_number": 4,
+          "single": 0,
+          "fill_option": [
+            1,
+            1,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,1192",
+            "222,1192",
+            "259,1192",
+            "298,1192"
+          ],
+          "fill_size": [
+            35,
+            22
+          ]
+        },
+        {
+          "main_number": 2,
+          "sub_number": 5,
+          "single": 0,
+          "fill_option": [
+            1,
+            1,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,1220",
+            "222,1220",
+            "259,1220",
+            "297,1220"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        }
+      ],
+      "type": "fill_area"
+    },
+    {
+      "field": "question",
+      "index": 6,
+      "content": "",
+      "wrong_flag": 0,
+      "fill_result": [
+        {
+          "main_number": 2,
+          "sub_number": 6,
+          "single": 0,
+          "fill_option": [
+            1,
+            1,
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "405,1107",
+            "441,1107",
+            "478,1107",
+            "516,1107"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 2,
+          "sub_number": 7,
+          "single": 0,
+          "fill_option": [
+            1,
+            1,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "405,1136",
+            "441,1136",
+            "478,1136",
+            "516,1136"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 2,
+          "sub_number": 8,
+          "single": 0,
+          "fill_option": [
+            1,
+            1,
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "405,1164",
+            "441,1164",
+            "478,1164",
+            "516,1164"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 2,
+          "sub_number": 9,
+          "single": 0,
+          "fill_option": [
+            1,
+            1,
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "404,1192",
+            "441,1192",
+            "478,1192",
+            "516,1192"
+          ],
+          "fill_size": [
+            35,
+            22
+          ]
+        },
+        {
+          "main_number": 2,
+          "sub_number": 10,
+          "single": 0,
+          "fill_option": [
+            0,
+            1,
+            1,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "404,1220",
+            "441,1220",
+            "478,1220",
+            "515,1220"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        }
+      ],
+      "type": "fill_area"
+    },
+    {
+      "field": "question",
+      "index": 7,
+      "content": "",
+      "wrong_flag": 0,
+      "fill_result": [
+        {
+          "main_number": 3,
+          "sub_number": 1,
+          "single": 1,
+          "fill_option": [
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "184,1320",
+            "222,1320"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 3,
+          "sub_number": 2,
+          "single": 1,
+          "fill_option": [
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "184,1348",
+            "222,1348"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 3,
+          "sub_number": 3,
+          "single": 1,
+          "fill_option": [
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "184,1376",
+            "222,1376"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 3,
+          "sub_number": 4,
+          "single": 1,
+          "fill_option": [
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,1404",
+            "222,1404"
+          ],
+          "fill_size": [
+            35,
+            22
+          ]
+        },
+        {
+          "main_number": 3,
+          "sub_number": 5,
+          "single": 1,
+          "fill_option": [
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "185,1432",
+            "222,1432"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        }
+      ],
+      "type": "fill_area"
+    },
+    {
+      "field": "question",
+      "index": 8,
+      "content": "",
+      "wrong_flag": 0,
+      "fill_result": [
+        {
+          "main_number": 3,
+          "sub_number": 6,
+          "single": 1,
+          "fill_option": [
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "339,1319",
+            "376,1319"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 3,
+          "sub_number": 7,
+          "single": 1,
+          "fill_option": [
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "339,1347",
+            "376,1347"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 3,
+          "sub_number": 8,
+          "single": 1,
+          "fill_option": [
+            0,
+            1
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "339,1376",
+            "376,1376"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        },
+        {
+          "main_number": 3,
+          "sub_number": 9,
+          "single": 1,
+          "fill_option": [
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "339,1404",
+            "376,1404"
+          ],
+          "fill_size": [
+            35,
+            22
+          ]
+        },
+        {
+          "main_number": 3,
+          "sub_number": 10,
+          "single": 1,
+          "fill_option": [
+            1,
+            0
+          ],
+          "suspect_flag": 0,
+          "fill_position": [
+            "339,1432",
+            "376,1432"
+          ],
+          "fill_size": [
+            36,
+            22
+          ]
+        }
+      ],
+      "type": "fill_area"
+    }
+  ],
+  "block_struct": [
+    5,
+    5,
+    5,
+    5,
+    5,
+    5,
+    5,
+    5
+  ],
+  "extension": []
+}

+ 54 - 37
src/render/views/RecognizeCheck/index.vue

@@ -1,42 +1,44 @@
 <template>
-  <a-form class="m-b-16px" layout="inline">
-    <a-form-item label="科目">
-      <SelectCourse />
-    </a-form-item>
-    <a-form-item>
-      <a-button>查询</a-button>
-    </a-form-item>
-    <a-divider type="vertical" />
-    <qm-button type="primary" :icon="h(PlusCircleOutlined)" @click="onAdd"
-      >新增评卷点信息</qm-button
+  <div v-show="route.name === 'RecognizeCheck'" class="page-box">
+    <a-space class="m-b-16px" :size="8">
+      <div><span>科目:</span> <SelectCourse /></div>
+      <a-button @click="toPage(1)">查询</a-button>
+      <a-divider type="vertical" style="margin: 0" />
+      <qm-button type="primary" :icon="h(PlusCircleOutlined)" @click="onAdd"
+        >新增评卷点信息</qm-button
+      >
+    </a-space>
+
+    <a-table
+      :columns="columns"
+      :row-key="(record) => record.id"
+      :data-source="dataList"
+      :pagination="pagination"
+      :loading="loading"
+      bordered
     >
-  </a-form>
-
-  <a-table
-    :columns="columns"
-    :row-key="(record) => record.id"
-    :data-source="dataList"
-    :pagination="pagination"
-    :loading="loading"
-    bordered
-  >
-    <template #bodyCell="{ column, index }">
-      <template v-if="column.dataIndex === 'condition'">
-        {{ getConditionContent(index) }}
+      <template #bodyCell="{ column, index }">
+        <template v-if="column.dataIndex === 'condition'">
+          {{ getConditionContent(index) }}
+        </template>
+        <template v-if="column.dataIndex === 'finishCount'">
+          {{ getConditionProgress(index) }}
+        </template>
+        <template v-if="column.dataIndex === 'operation'">
+          <qm-button type="link" @click="onArbitrate(index)">仲裁</qm-button>
+          <qm-button type="link" @click="onEdit(index)">修改</qm-button>
+          <qm-button type="link" @click="onReset(index)">重置</qm-button>
+          <qm-button type="link" @click="onRecheck(index)">复查</qm-button>
+          <qm-button type="link" @click="onBuildTask(index)"
+            >生成任务</qm-button
+          >
+          <qm-button type="link" @click="onDelete(index)">删除</qm-button>
+        </template>
       </template>
-      <template v-if="column.dataIndex === 'finishCount'">
-        {{ getConditionProgress(index) }}
-      </template>
-      <template v-if="column.dataIndex === 'operation'">
-        <qm-button type="text" @click="onArbitrate(index)">仲裁</qm-button>
-        <qm-button type="text" @click="onEdit(index)">修改</qm-button>
-        <qm-button type="text" @click="onReset(index)">重置</qm-button>
-        <qm-button type="text" @click="onRecheck(index)">复查</qm-button>
-        <qm-button type="text" @click="onBuildTask(index)">生成任务</qm-button>
-        <qm-button type="text" @click="onDelete(index)">删除</qm-button>
-      </template>
-    </template>
-  </a-table>
+    </a-table>
+  </div>
+
+  <router-view />
 
   <ModifyRecognizeCheckTask
     ref="modifyRecognizeCheckTaskRef"
@@ -47,7 +49,7 @@
 
 <script setup lang="ts">
 import { ref, h } from "vue";
-import { useRouter } from "vue-router";
+import { useRoute, useRouter } from "vue-router";
 import { PlusCircleOutlined } from "@ant-design/icons-vue";
 import { message } from "ant-design-vue";
 import type { TableProps } from "ant-design-vue";
@@ -70,15 +72,18 @@ defineOptions({
 });
 
 const router = useRouter();
+const route = useRoute();
 
 const columns: TableProps["columns"] = [
   {
     title: "任务条件",
     dataIndex: "condition",
+    minWidth: "200px",
   },
   {
     title: "科目",
     dataIndex: "subjectName",
+    minWidth: "120px",
   },
   {
     title: "状态",
@@ -200,4 +205,16 @@ async function onDelete(index: number) {
   deletePageLastItem();
   message.success("操作成功");
 }
+
+// test
+dataList.value = [
+  {
+    id: 1,
+    conditions: [],
+    subjectCode: "11",
+    subjectName: "22",
+    totalCount: 10,
+    finishCount: 8,
+  },
+];
 </script>